DevOps बूटकैंप · पाठ

स्क्रिप्ट की प्रोफ़ाइलिंग और बेकार सबशेल से बचाव

स्क्रिप्ट का समय मापें और cat-grep श्रृंखला जैसे बार-बार fork करने वाले पैटर्न को अंतर्निर्मित विकल्पों से बदलें।

पाठ 1, कुल 4 में से13 चरण

स्क्रिप्ट की प्रोफ़ाइलिंग और बेकार सबशेल से बचाव, CoddyKit पर DevOps बूटकैंप का एक निःशुल्क पाठ है। यह 4 में से 1वाँ पाठ है। इस अध्ययन पथ के 3 तक कोई भी पाठ पूरा पढ़ना निःशुल्क है — इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ व्यावहारिक अभ्यास भी उपलब्ध कराता है। यह DevOps बूटकैंप सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। DevOps बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

स्क्रिप्ट का प्रदर्शन क्यों महत्वपूर्ण है

धीमी गति से चलने वाली बैश स्क्रिप्ट CI समय बर्बाद करती हैं, क्रॉन कार्यों को रोकती हैं और उपयोगकर्ताओं को निराश करती हैं। अधिकांश धीमापन जटिल तर्क से नहीं, बल्कि अनावश्यक प्रक्रिया फ़ोर्क से आता है: आपके द्वारा चलाई जाने वाली हर बाहरी कमांड एक नई चाइल्ड प्रक्रिया शुरू करती है।

इस पाठ में आप सीखेंगे:

  • time और bash -x से वास्तव में समय कहाँ व्यतीत होता है, यह मापना
  • cat का निरर्थक उपयोग जैसे फ़ोर्क-प्रधान प्रतिरूपों की पहचान करना
  • बाहरी कमांड को तेज़ शेल अंतर्निर्मित कमांड से बदलना
  • सबशेल का जानबूझकर उपयोग करना और जहाँ उनका कोई लाभ न हो, वहाँ उनसे बचना

लक्ष्य ऐसी स्क्रिप्ट लिखना है जो कम चाइल्ड प्रक्रियाओं और कम वास्तविक समय में वही काम करें।

time अंतर्निर्मित कमांड से स्क्रिप्ट का समय मापना

सबसे सरल प्रोफ़ाइलिंग उपकरण शेल की अंतर्निर्मित कमांड time है। किसी भी कमांड या पाइपलाइन के आगे इसे लगाकर तीन माप प्राप्त करें:

  • real — वास्तविक घड़ी के अनुसार बीता समय (जिसके लिए आपको सचमुच प्रतीक्षा करनी पड़ती है)
  • user — उपयोगकर्ता-क्षेत्र के कोड में लगा CPU समय
  • sys — कर्नेल में लगा CPU समय (सिस्टम कॉल, I/O)

real और user+sys के बीच बड़ा अंतर आम तौर पर दर्शाता है कि स्क्रिप्ट I/O की प्रतीक्षा कर रही है या कई चाइल्ड प्रक्रियाएँ शुरू कर रही है। किसी भी चीज़ को अनुकूलित करने से पहले पूरी स्क्रिप्ट के चारों ओर time चलाकर पुष्टि करें कि समस्या मौजूद है।

#!/usr/bin/env bash
# Time a whole script block
time {
  for i in $(seq 1 1000); do
    echo "line $i"
  done | grep -c "5"
}
# Output example:
# 271
# real  0m0.045s
# user  0m0.038s
# sys   0m0.012s

bash -x और PS4 से निष्पादन का अनुरेखण

bash -x हर कमांड को चलने से पहले प्रिंट करता है — इसे निष्पादन अनुरेखण कहते हैं। इससे पता चलता है कि कौन-सी पंक्तियाँ सबसे अधिक बार चलती हैं और बाहरी प्रोग्राम आपकी अपेक्षा से अधिक बार तो नहीं बुलाए जा रहे।

डिफ़ॉल्ट रूप से प्रत्येक अनुरेखित पंक्ति के आगे + लगा होता है। टाइमस्टैम्प शामिल करने के लिए PS4 का उपयोग करके इस उपसर्ग को अधिक जानकारीपूर्ण बनाया जा सकता है, जिससे अनुरेखण एक हल्का प्रोफ़ाइलर बन जाता है:

  • हर अनुरेखित कमांड से पहले PS4 का विस्तार किया जाता है
  • $EPOCHREALTIME (bash 5+) या $(date +%s%N) शामिल करने पर नैनोसेकंड स्तर का रिज़ॉल्यूशन मिलता है
  • मानक त्रुटि को किसी फ़ाइल में भेजें और धीमे भाग खोजने के लिए उसका बाद में प्रसंस्करण करें
#!/usr/bin/env bash
# Run with:  bash -x ./myscript.sh  2>trace.log
# Or embed tracing inside the script:
export PS4='+ [${EPOCHREALTIME}] ${BASH_SOURCE}:${LINENO}: '
set -x

slow_function() {
  local result
  result=$(cat /etc/hostname)   # fork — slow
  echo "host: $result"
}

slow_function
set +x

# trace.log now contains timestamps so you can diff
# adjacent lines to find which step took longest.

निरर्थक सबशेल क्या है

सबशेल वर्तमान शेल प्रक्रिया की चाइल्ड प्रति होती है। यह इन तरीकों से बनती है:

  • कमांड प्रतिस्थापन: $(command)
  • कोष्ठक समूह: ( commands )
  • शेल संरचना में पाइप करना: cmd | while read ...

जब आपको सचमुच अलगाव या पाइपलाइन की आवश्यकता हो, तब सबशेल आवश्यक होते हैं। जब आप उनका उपयोग केवल ऐसे बाहरी प्रोग्राम को बुलाने के लिए करते हैं जिसे शेल स्वयं संभाल सकता था, या बिना कारण किसी अंतर्निर्मित कमांड को फ़ोर्क की अतिरिक्त परत में रखते हैं, तब वे निरर्थक बन जाते हैं।

आधुनिक Linux सिस्टम पर हर सबशेल फ़ोर्क की लागत लगभग 1–5 ms होती है। 10,000 बार चलने वाले लूप में 1000 निरर्थक सबशेल केवल अतिरिक्त लागत के रूप में 1–5 सेकंड जोड़ देते हैं।

क्लासिक एंटी-पैटर्न: cat का निरर्थक उपयोग

cat file | grep pattern फ़ोर्क-प्रधान एंटी-पैटर्न का सबसे प्रसिद्ध उदाहरण है। यह पाइप से जुड़े दो प्रक्रियाएँ (cat + grep) शुरू करता है, जबकि केवल grep ही फ़ाइल को सीधे पढ़ सकता है।

समाधान सरल है: फ़ाइल का नाम सीधे उस कमांड को दें जो फ़ाइलें समझती है। जब उपकरण फ़ाइल नाम स्वीकार नहीं करता, तो इसे इनपुट पुनर्निर्देशन कहते हैं; और जब स्वीकार करता है, तो बस cat को हटा दें।

  • धीमा: cat file | grep pattern — 2 प्रक्रियाएँ, 1 पाइप
  • तेज़: grep pattern file — 1 प्रक्रिया, कोई पाइप नहीं
  • यह भी तेज़: grep pattern < file — 1 प्रक्रिया, stdin पुनर्निर्देशन (कोई पाइप बफ़र नहीं)
#!/usr/bin/env bash
# Create a sample file
seq 1 10000 > /tmp/numbers.txt

# --- Slow: useless cat ---
time cat /tmp/numbers.txt | grep -c "^5"

# --- Fast: grep reads the file directly ---
time grep -c "^5" /tmp/numbers.txt

# Both print the same count; the second is measurably faster
# because it skips the cat process and the inter-process pipe.

बाहरी कमांड को शेल की अंतर्निर्मित कमांड से बदलना

कई एक-पंक्ति रूपांतरणों के लिए ऐसा अंतर्निर्मित विकल्प उपलब्ध होता है जो फ़ोर्क की आवश्यकता पूरी तरह समाप्त कर देता है। इन सामान्य प्रतिस्थापनों की तुलना करें:

  • echo ${#var}, echo "$var" | wc -c के बजाय — स्ट्रिंग की लंबाई
  • ${var^^} और ${var,,}, echo "$var" | tr 'a-z' 'A-Z' के बजाय — अक्षर-रूपांतरण (bash 4+)
  • ${var//search/replace}, echo "$var" | sed 's/search/replace/' के बजाय — सरल प्रतिस्थापन
  • [[ "$var" =~ pattern ]], echo "$var" | grep -q pattern के बजाय — नियमित अभिव्यक्ति मिलान
  • read -r line < file, line=$(head -n1 file) के बजाय — पहली पंक्ति पढ़ना

इनमें से कोई भी अंतर्निर्मित कमांड चाइल्ड प्रक्रिया को फ़ोर्क नहीं करती। हर कॉल पर बचत छोटी होती है, लेकिन लूप के भीतर यह बहुत बढ़ जाती है।

#!/usr/bin/env bash
sentence="hello world from bash"

# --- Fork-heavy ---
upper_slow=$(echo "$sentence" | tr 'a-z' 'A-Z')
length_slow=$(echo "$sentence" | wc -c)

# --- Builtin equivalents (zero extra processes) ---
upper_fast=${sentence^^}
length_fast=${#sentence}

echo "Slow upper : $upper_slow"
echo "Fast upper : $upper_fast"
echo "Slow length: $length_slow"
echo "Fast length: $length_fast"

लूप के भीतर सबशेल से बचना

लूप के भीतर कमांड प्रतिस्थापन फ़ोर्क की लागत को पुनरावृत्तियों की संख्या से बढ़ा देता है। 500 बार चलने वाला लूप, जिसमें $(date) की एक कॉल हो, केवल टाइमस्टैम्प के लिए 500 चाइल्ड प्रक्रियाएँ शुरू करता है।

लूप का अतिरिक्त भार घटाने की रणनीतियाँ:

  • स्थिर कमांड को लूप के बाहर ले जाएँ (एक बार गणना करें, बार-बार उपयोग करें)
  • अंकगणितीय विस्तार $(( expr )) को प्राथमिकता दें — यह अंतर्निर्मित है, फ़ोर्क नहीं
  • जब केवल फ़ॉर्मैटिंग आवश्यक हो, तब date को बुलाने के बजाय printf का उपयोग करें
  • बाहरी कॉल को समूह में करें: पहले डेटा एकत्र करें, फिर लूप के बाहर एक बार में उसका प्रसंस्करण करें
#!/usr/bin/env bash
# Demonstrate: compute-once vs fork-per-iteration

# Bad: $(date) forks 1000 times
time (
  for i in $(seq 1 1000); do
    ts=$(date +%s)   # fork each iteration
    echo "$i $ts" > /dev/null
  done
)

# Good: capture once, reuse
time (
  ts=$(date +%s)   # fork exactly once
  for i in $(seq 1 1000); do
    echo "$i $ts" > /dev/null
  done
)

पाइप सबशेल और चर के दायरे की समस्या

बैश में (ksh/zsh के विपरीत), पाइपलाइन में प्रत्येक कमांड अपने सबशेल में चलती है। इसका अर्थ है कि पाइप के भीतर निर्धारित चर पाइप समाप्त होने के बाद खो जाते हैं।

यह शुद्धता की त्रुटि के साथ-साथ प्रदर्शन की समस्या भी है — हो सकता है कि आप डेटा एकत्र करने की अपेक्षा से while read में पाइप करें, लेकिन बाद में चर खाली मिले।

दो समाधान:

  • प्रक्रिया प्रतिस्थापन का उपयोग करें while read line; do ...; done < <(command) — while लूप वर्तमान शेल में चलता है, सबशेल में नहीं
  • lastpipe विकल्प का उपयोग करें (shopt -s lastpipe) — इससे पाइपलाइन का अंतिम खंड वर्तमान शेल में चलता है (bash 4.2+)
#!/usr/bin/env bash
count=0

# --- Bug: count is always 0 after pipe (subshell) ---
seq 1 5 | while read -r n; do
  (( count++ ))
done
echo "After pipe   : count=$count"   # prints 0

# --- Fix 1: process substitution (no subshell for while) ---
count=0
while read -r n; do
  (( count++ ))
done < <(seq 1 5)
echo "Process sub  : count=$count"   # prints 5

# --- Fix 2: lastpipe option ---
shopt -s lastpipe
count=0
seq 1 5 | while read -r n; do
  (( count++ ))
done
echo "lastpipe     : count=$count"   # prints 5

माइक्रोबेंचमार्क से सबशेल की लागत मापना

एक छोटे बेंचमार्क से सबशेल के अतिरिक्त भार को आसानी से सिद्ध किया जा सकता है। $(( )) (अंतर्निर्मित कमांड) द्वारा किए गए अंकगणितीय ऑपरेशन की तुलना उसी ऑपरेशन से करें, जिसे expr (बाहरी प्रक्रिया) के माध्यम से पाइप किया गया हो।

एक सामान्य Linux मशीन पर परिणाम दिखाते हैं कि expr को 10,000 बार कॉल करने में लगभग 5 सेकंड लगते हैं, जबकि $(( )) की उतनी ही कॉल में 0.1 सेकंड से कम समय लगता है — समान आउटपुट के लिए 50 गुना अंतर।

यह बेंचमार्क पैटर्न तब भी उपयोगी है जब आप किसी अनुकूलन को मापना चाहते हों: दोनों संस्करणों को लूप में N बार चलाएँ और time से तुलना करें।

#!/usr/bin/env bash
N=500

# External command (fork per call)
time (
  x=0
  for ((i=0; i<N; i++)); do
    x=$(expr $x + 1)   # forks expr each time
  done
  echo "expr result: $x"
)

# Arithmetic builtin (no fork)
time (
  x=0
  for ((i=0; i<N; i++)); do
    (( x++ ))          # pure builtin
  done
  echo "builtin result: $x"
)

echo पाइप से बचने के लिए here-string का उपयोग करना

किसी चर को stdin के रूप में देने के लिए echo "$var" | command एक सामान्य पैटर्न है। यह दो प्रक्रियाएँ (echo + command) फ़ोर्क करता है और एक पाइप बनाता है। here-string (<<<) केवल एक प्रक्रिया के साथ वही परिणाम देता है — बाहरी कमांड कर्नेल द्वारा प्रबंधित अस्थायी बफ़र से पढ़ती है।

  • grep pattern <<< "$var" — एक प्रक्रिया, कोई पाइप नहीं
  • read -r field1 field2 <<< "$line" — किसी बाहरी उपकरण के बिना चर को विभाजित करें
  • wc -w <<< "$sentence" — किसी चर से शब्दों की गिनती

here-string विशेष रूप से उन तंग लूप में उपयोगी होते हैं जहाँ हर फ़ोर्क महत्वपूर्ण होता है।

#!/usr/bin/env bash
data="The quick brown fox"

# --- Fork-heavy: echo spawns a child ---
word_count_slow=$(echo "$data" | wc -w)
echo "Slow word count: $word_count_slow"

# --- Fast: here-string, only wc spawns ---
word_count_fast=$(wc -w <<< "$data")
echo "Fast word count: $word_count_fast"

# --- Even better: use parameter expansion (zero forks) ---
# Split into array, count elements
read -ra words <<< "$data"
echo "Zero-fork count: ${#words[@]}"

व्यावहारिक पुनर्गठन: पहले और बाद में

आइए एक वास्तविक जैसी स्क्रिप्ट को समझें, जो लॉग फ़ाइल का प्रसंस्करण करती है, और अब तक सीखी गई सभी बातों को लागू करें। मूल संस्करण cat, grep, awk और tr को पाइप से जोड़ता है। पुनर्गठित संस्करण प्रक्रियाओं की संख्या 8 से घटाकर 2 कर देता है।

किए गए मुख्य परिवर्तन:

  • cat हटाया — grep फ़ाइल को सीधे पढ़ता है
  • tr '[:lower:]' '[:upper:]' को ${var^^} से बदला
  • echo "$line" | grep -q को [[ $line =~ ]] से बदला
  • पाइप किए गए while लूप के बजाय प्रक्रिया प्रतिस्थापन के साथ read -r का उपयोग किया

पुनर्गठन के बाद सुधार की पुष्टि करने के लिए फिर से time ./script.sh चलाएँ। हमेशा मापें — अनुमान न लगाएँ।

#!/usr/bin/env bash
# Create a sample log
printf 'ERROR: disk full\nINFO: started\nERROR: timeout\nINFO: done\n' \
  > /tmp/sample.log

# === BEFORE (fork-heavy) ===
time (
  cat /tmp/sample.log \
    | grep 'ERROR' \
    | while read -r line; do
        label=$(echo "$line" | tr '[:lower:]' '[:upper:]')
        echo "[ALERT] $label"
      done
)

# === AFTER (builtin-first) ===
time (
  while IFS= read -r line; do
    echo "[ALERT] ${line^^}"
  done < <(grep 'ERROR' /tmp/sample.log)
)

ज्ञान जाँच: उप-शेल का क्षेत्र

पाइपलाइन के उप-शेल और पाइप के अंदर किए गए चर के परिवर्तनों को खोने से बचने का तरीका समझने की अपनी क्षमता जाँचिए।

पाठ का पुनरावलोकन: पहले प्रोफ़ाइल बनाएँ, कम फ़ोर्क करें

इस पाठ में आपने bash स्क्रिप्टों में अनावश्यक प्रक्रियाएँ बनाए जाने के सबसे सामान्य स्रोतों की पहचान करना और उन्हें हटाना सीखा।

मुख्य बातें:

  • अनुकूलन करने से पहले मापने के लिए time और PS4-युक्त bash -x का उपयोग करें
  • बेकार cat सबसे व्यापक प्रति-प्रतिरूप है — जिन कमांड में फ़ाइल नाम सीधे दिए जा सकते हैं, उनमें फ़ाइल नाम सीधे दें
  • echo "$var" | command को हियर-स्ट्रिंग (command <<< "$var") या अंतर्निर्मित कमांड से बदलें
  • पैरामीटर विस्तार (${var^^}, ${var//s/r}, ${#var}) कई tr, sed और wc कॉल का स्थान ले लेते हैं
  • पाइपलाइन के उप-शेल चरों में किए गए बदलावों को निगल जाते हैं — प्रक्रिया प्रतिस्थापन या shopt -s lastpipe का उपयोग करें
  • अपरिवर्तनीय कमांड कॉल को लूप के बाहर ले जाएँ; expr की तुलना में $(( )) अंकगणित को प्राथमिकता दें

सामान्य नियम: पहले मापें, जहाँ संभव हो बाहरी कमांड को अंतर्निर्मित कमांड से बदलें, और दूसरी माप से सुधार की पुष्टि करें।

शुरुआत निःशुल्क

एआई शिक्षक के साथ DevOps बूटकैंप सीखें — निःशुल्क

अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।

पाठ्यक्रम
142
पाठ
568

अक्सर पूछे जाने वाले प्रश्न

क्या “स्क्रिप्ट की प्रोफ़ाइलिंग और बेकार सबशेल से बचाव” पाठ निःशुल्क है?

हाँ — DevOps बूटकैंप अध्ययन पथ के 3 तक कोई भी पाठ, जिसमें “स्क्रिप्ट की प्रोफ़ाइलिंग और बेकार सबशेल से बचाव” भी शामिल है, यहाँ वेब पर पूरा पढ़ना निःशुल्क है। इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ इंटरैक्टिव अभ्यास भी उपलब्ध कराता है। DevOps बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

“स्क्रिप्ट की प्रोफ़ाइलिंग और बेकार सबशेल से बचाव” में मैं क्या सीखूँगा?

स्क्रिप्ट का समय मापें और cat-grep श्रृंखला जैसे बार-बार fork करने वाले पैटर्न को अंतर्निर्मित विकल्पों से बदलें। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ DevOps बूटकैंप का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।

क्या DevOps बूटकैंप शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?

पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर DevOps बूटकैंप शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 1वाँ पाठ है।

“स्क्रिप्ट की प्रोफ़ाइलिंग और बेकार सबशेल से बचाव” पाठ पूरा करने में कितना समय लगता है?

CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।

क्या मैं इस DevOps बूटकैंप पाठ में कोड लिख और चला सकता हूँ?

हाँ। हर DevOps बूटकैंप पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।

इस पाठ्यक्रम के सभी पाठ

  1. स्क्रिप्ट की प्रोफ़ाइलिंग और बेकार सबशेल से बचाव
  2. xargs -P और बैकग्राउंड जॉब से समानांतरता
  3. GNU parallel से कार्यभार का संचालन
  4. थ्रूपुट के लिए स्ट्रीमिंग पाइपलाइन और नामित पाइप
← DevOps बूटकैंप पर वापस जाएँ