हालांकि सॉफ्टवेयर इंजीनियर नियमित रूप से विकास के कई पहलुओं के लिए कमांड लाइन का उपयोग करते हैं, सरणी कमांड लाइन की अधिक अस्पष्ट विशेषताओं में से एक होने की संभावना है (हालांकि नहीं रेगेक्स ऑपरेटर के रूप में अस्पष्ट के रूप में =~
) लेकिन अस्पष्टता और संदिग्ध वाक्य रचना एक तरफ, बैश सरणियाँ बहुत शक्तिशाली हो सकती हैं।
रुको, लेकिन क्यों?
बैश के बारे में लिखना चुनौतीपूर्ण है क्योंकि एक लेख के लिए एक मैनुअल में विकसित करना उल्लेखनीय रूप से आसान है जो सिंटैक्स विषमताओं पर केंद्रित है। निश्चिंत रहें, हालांकि, इस लेख का उद्देश्य आपको RTFM रखने से बचना है।
एक वास्तविक (वास्तव में उपयोगी) उदाहरण
इसके लिए, आइए वास्तविक दुनिया के परिदृश्य पर विचार करें और बैश कैसे मदद कर सकता है:आप अपनी आंतरिक डेटा पाइपलाइन के रनटाइम का मूल्यांकन और अनुकूलन करने के लिए अपनी कंपनी में एक नए प्रयास का नेतृत्व कर रहे हैं। पहले चरण के रूप में, आप यह मूल्यांकन करने के लिए एक पैरामीटर स्वीप करना चाहते हैं कि पाइपलाइन थ्रेड्स का कितनी अच्छी तरह उपयोग करती है। सरलता के लिए, हम पाइपलाइन को एक संकलित C++ ब्लैक बॉक्स के रूप में मानेंगे, जहां एकमात्र पैरामीटर जिसे हम बदल सकते हैं, वह है डेटा प्रोसेसिंग के लिए आरक्षित थ्रेड्स की संख्या:./pipeline --threads 4
।
मूल बातें
पहली चीज जो हम करेंगे वह है एक सरणी को परिभाषित करना जिसमें --threads
. के मान हों पैरामीटर जिसे हम परीक्षण करना चाहते हैं:
allThreads=(1 2 4 8 16 32 64 128)
इस उदाहरण में, सभी तत्व संख्याएं हैं, लेकिन यह मामला नहीं होना चाहिए—बैश में सरणियों में संख्याएं और तार दोनों हो सकते हैं, उदा., myArray=(1 2 "three" 4 "five")
एक वैध अभिव्यक्ति है। और किसी भी अन्य बैश चर की तरह, सुनिश्चित करें कि समान चिह्न के आसपास कोई स्थान न छोड़ें। अन्यथा, बैश चर नाम को निष्पादित करने के लिए एक प्रोग्राम के रूप में मानेगा, और =
इसके पहले पैरामीटर के रूप में!
अब जब हमने ऐरे को इनिशियलाइज़ कर लिया है, तो आइए इसके कुछ एलिमेंट्स को पुनः प्राप्त करें। आप देखेंगे कि बस echo $allThreads
. करने से केवल पहला तत्व आउटपुट करेगा।
यह समझने के लिए कि ऐसा क्यों है, आइए एक कदम पीछे हटें और फिर से देखें कि हम आमतौर पर बैश में वेरिएबल कैसे आउटपुट करते हैं। निम्नलिखित परिदृश्य पर विचार करें:
type="article"
echo "Found 42 $type"
वेरिएबल कहें $type
हमें एकवचन संज्ञा के रूप में दिया गया है और हम एक s
. जोड़ना चाहते हैं हमारे वाक्य के अंत में। हम केवल एक s
नहीं जोड़ सकते हैं करने के लिए $type
चूंकि यह इसे एक भिन्न चर में बदल देगा, $types
. और यद्यपि हम echo "Found 42 "$type"s"
जैसे कोड अंतर्विरोधों का उपयोग कर सकते हैं , इस समस्या को हल करने का सबसे अच्छा तरीका घुंघराले ब्रेसिज़ का उपयोग करना है:echo "Found 42 ${type}s"
, जो हमें बैश को यह बताने की अनुमति देता है कि एक चर का नाम कहाँ से शुरू होता है और समाप्त होता है (दिलचस्प बात यह है कि यह वही सिंटैक्स है जिसका उपयोग जावास्क्रिप्ट / ES6 में टेम्पलेट शाब्दिक में चर और अभिव्यक्तियों को इंजेक्ट करने के लिए किया जाता है)।
तो जैसा कि यह पता चला है, हालांकि बैश चर को आमतौर पर घुंघराले कोष्ठक की आवश्यकता नहीं होती है, वे सरणियों के लिए आवश्यक हैं। बदले में, यह हमें एक्सेस करने के लिए इंडेक्स निर्दिष्ट करने की अनुमति देता है, उदा., echo ${allThreads[1]}
सरणी का दूसरा तत्व लौटाता है। कोष्ठक सहित नहीं, उदा.,echo $allThreads[1]
, बैश को [1]
. के इलाज के लिए प्रेरित करता है एक स्ट्रिंग के रूप में और इसे इस तरह आउटपुट करें।
हां, बैश सरणियों में अजीब सिंटैक्स होता है, लेकिन कम से कम वे शून्य-अनुक्रमित होते हैं, कुछ अन्य भाषाओं के विपरीत (मैं आपको देख रहा हूं, R
)।
सरणी से लूपिंग
यद्यपि ऊपर के उदाहरणों में हमने अपने सरणियों में पूर्णांक सूचकांकों का उपयोग किया है, आइए दो अवसरों पर विचार करें जब ऐसा नहीं होगा:पहला, यदि हम $i
चाहते थे सरणी का -वां तत्व, जहां $i
ब्याज की अनुक्रमणिका वाला एक चर है, हम उस तत्व का उपयोग करके पुनः प्राप्त कर सकते हैं:echo ${allThreads[$i]}
. दूसरा, किसी सरणी के सभी तत्वों को आउटपुट करने के लिए, हम संख्यात्मक अनुक्रमणिका को @
. से बदलते हैं प्रतीक (आप @
. के बारे में सोच सकते हैं all
. के लिए खड़ा है ):echo ${allThreads[@]}
।
सरणी तत्वों से लूपिंग
इसे ध्यान में रखते हुए, आइए $allThreads
. के माध्यम से लूप करें और --threads
. के प्रत्येक मान के लिए पाइपलाइन लॉन्च करें :
for t in ${allThreads[@]}; do
./pipeline --threads $t
done
सरणी सूचकांकों के माध्यम से लूपिंग
अगला, आइए थोड़ा अलग दृष्टिकोण पर विचार करें। सरणी तत्वों . पर लूप करने के बजाय , हम सरणी सूचकांक . पर लूप कर सकते हैं :
for i in ${!allThreads[@]}; do
./pipeline --threads ${allThreads[$i]}
done
आइए इसे तोड़ें:जैसा कि हमने ऊपर देखा, ${allThreads[@]}
हमारे सरणी में सभी तत्वों का प्रतिनिधित्व करता है। इसे ${!allThreads[@]}
. बनाने के लिए विस्मयादिबोधक चिह्न जोड़ना सभी सरणी सूचकांकों की सूची लौटाएगा (हमारे मामले में 0 से 7)। दूसरे शब्दों में, for
लूप सभी सूचकांकों के माध्यम से लूप कर रहा है $i
और $i
. पढ़ना -वें तत्व $allThreads
. से --threads
. का मान सेट करने के लिए पैरामीटर।
यह आंखों पर ज्यादा कठोर है, इसलिए आप सोच रहे होंगे कि मैं इसे पहले स्थान पर क्यों पेश कर रहा हूं। ऐसा इसलिए है क्योंकि ऐसे समय होते हैं जहां आपको लूप के भीतर इंडेक्स और वैल्यू दोनों को जानने की आवश्यकता होती है, उदाहरण के लिए, यदि आप किसी सरणी के पहले तत्व को अनदेखा करना चाहते हैं, तो इंडेक्स का उपयोग करके आप एक अतिरिक्त वैरिएबल बनाने से बचाते हैं जिसे आप लूप के अंदर बढ़ाते हैं ।
आबादी सरणियाँ
अब तक, हम प्रत्येक --threads
. के लिए पाइपलाइन लॉन्च करने में सक्षम रहे हैं ब्याज की। अब, मान लेते हैं कि हमारी पाइपलाइन का आउटपुट सेकंड में रनटाइम है। हम प्रत्येक पुनरावृत्ति पर उस आउटपुट को कैप्चर करना चाहते हैं और इसे किसी अन्य सरणी में सहेजना चाहते हैं ताकि हम अंत में इसके साथ विभिन्न जोड़तोड़ कर सकें।
कुछ उपयोगी सिंटैक्स
लेकिन कोड में गोता लगाने से पहले, हमें कुछ और सिंटैक्स पेश करने की आवश्यकता है। सबसे पहले, हमें बैश कमांड के आउटपुट को पुनः प्राप्त करने में सक्षम होना चाहिए। ऐसा करने के लिए, निम्नलिखित सिंटैक्स का उपयोग करें:output=$( ./my_script.sh )
, जो हमारे कमांड के आउटपुट को वेरिएबल $output
. में स्टोर करेगा ।
सिंटैक्स का दूसरा बिट जो हमें चाहिए वह यह है कि हमने अभी-अभी एक सरणी में प्राप्त मूल्य को कैसे जोड़ा है। ऐसा करने के लिए वाक्य-विन्यास परिचित लगेगा:
myArray+=( "newElement1" "newElement2" )
पैरामीटर स्वीप
सब कुछ एक साथ रखकर, हमारे पैरामीटर स्वीप को लॉन्च करने के लिए हमारी स्क्रिप्ट यहां दी गई है:
allThreads=(1 2 4 8 16 32 64 128)
allRuntimes=()
for t in ${allThreads[@]}; do
runtime=$(./pipeline --threads $t)
allRuntimes+=( $runtime )
done
और आवाज!
आपको और क्या मिला?
इस लेख में, हमने पैरामीटर स्वीप के लिए सरणियों का उपयोग करने के परिदृश्य को कवर किया है। लेकिन मैं वादा करता हूं कि बैश सरणियों का उपयोग करने के और भी कारण हैं—यहां दो और उदाहरण हैं।
लॉग अलर्ट करना
इस परिदृश्य में, आपका ऐप मॉड्यूल में विभाजित है, प्रत्येक की अपनी लॉग फ़ाइल है। कुछ मॉड्यूल में परेशानी के संकेत होने पर हम सही व्यक्ति को ईमेल करने के लिए क्रॉन जॉब स्क्रिप्ट लिख सकते हैं:
# List of logs and who should be notified of issues
logPaths=("api.log" "auth.log" "jenkins.log" "data.log")
logEmails=("jay@email" "emma@email" "jon@email" "sophia@email")
# Look for signs of trouble in each log
for i in ${!logPaths[@]};
do
log=${logPaths[$i]}
stakeholder=${logEmails[$i]}
numErrors=$( tail -n 100 "$log" | grep "ERROR" | wc -l )
# Warn stakeholders if recently saw > 5 errors
if [[ "$numErrors" -gt 5 ]];
then
emailRecipient="$stakeholder"
emailSubject="WARNING: ${log} showing unusual levels of errors"
emailBody="${numErrors} errors found in log ${log}"
echo "$emailBody" | mailx -s "$emailSubject" "$emailRecipient"
fi
done
API क्वेरीज़
मान लें कि आप कुछ एनालिटिक्स जेनरेट करना चाहते हैं जिसके बारे में उपयोगकर्ता आपके माध्यम पोस्ट पर सबसे अधिक टिप्पणी करते हैं। चूंकि हमारे पास सीधे डेटाबेस एक्सेस नहीं है, इसलिए SQL का कोई प्रश्न नहीं है, लेकिन हम API का उपयोग कर सकते हैं!
एपीआई प्रमाणीकरण और टोकन के बारे में लंबी चर्चा में शामिल होने से बचने के लिए, हम इसके बजाय JSONPlaceholder का उपयोग करेंगे, जो एक सार्वजनिक-सामना करने वाली API परीक्षण सेवा है, जो हमारे समापन बिंदु के रूप में है। एक बार जब हम प्रत्येक पोस्ट को क्वेरी कर लेते हैं और टिप्पणी करने वाले सभी लोगों के ईमेल प्राप्त कर लेते हैं, तो हम उन ईमेल को अपने परिणाम सरणी में जोड़ सकते हैं:
endpoint="https://jsonplaceholder.typicode.com/comments"
allEmails=()
# Query first 10 posts
for postId in {1..10};
do
# Make API call to fetch emails of this posts's commenters
response=$(curl "${endpoint}?postId=${postId}")
# Use jq to parse the JSON response into an array
allEmails+=( $( jq '.[].email' <<< "$response" ) )
done
यहां ध्यान दें कि मैं jq
. का उपयोग कर रहा हूं कमांड लाइन से JSON को पार्स करने के लिए टूल। jq
. का सिंटैक्स इस लेख के दायरे से बाहर है, लेकिन मैं अत्यधिक अनुशंसा करता हूं कि आप इसे देखें।
जैसा कि आप कल्पना कर सकते हैं, ऐसे अनगिनत अन्य परिदृश्य हैं जिनमें बैश सरणियों का उपयोग करने से मदद मिल सकती है, और मुझे आशा है कि इस लेख में उल्लिखित उदाहरणों ने आपको विचार के लिए कुछ भोजन दिया है। अगर आपके पास अपने काम से साझा करने के लिए अन्य उदाहरण हैं, तो कृपया नीचे एक टिप्पणी छोड़ दें।
लेकिन रुकिए, और भी बहुत कुछ है!
चूंकि हमने इस लेख में काफी कुछ सरणी सिंटैक्स को कवर किया है, इसलिए हमने जो कुछ भी कवर किया है उसका सारांश यहां दिया गया है, साथ ही कुछ और उन्नत ट्रिक्स जिन्हें हमने कवर नहीं किया है:
वाक्यविन्यास | परिणाम |
---|---|
arr=() | खाली सरणी बनाएं |
arr=(1 2 3) | सरणी प्रारंभ करें |
${arr[2]} | तीसरा तत्व प्राप्त करें |
${arr[@]} | सभी तत्वों को पुनः प्राप्त करें |
${!arr[@]} | सरणी सूचकांक पुनर्प्राप्त करें |
${#arr[@]} | सरणी आकार की गणना करें |
arr[0]=3 | पहला तत्व अधिलेखित करें |
arr+=(4) | मान जोड़ें |
str=$(ls) | ls सहेजें एक स्ट्रिंग के रूप में आउटपुट |
arr=( $(ls) ) | ls सहेजें फ़ाइलों की एक सरणी के रूप में आउटपुट |
${arr[@]:s:n} | n तत्वों को पुनः प्राप्त करें starting at index s |
एक आखिरी विचार
जैसा कि हमने पाया है, बैश सरणियों में निश्चित रूप से अजीब वाक्यविन्यास है, लेकिन मुझे आशा है कि इस लेख ने आपको आश्वस्त किया है कि वे बेहद शक्तिशाली हैं। एक बार जब आप सिंटैक्स को समझ लेते हैं, तो आप अपने आप को अक्सर बैश सरणियों का उपयोग करते हुए पाएंगे।
बैश या पायथन?
जो प्रश्न पूछता है:आपको अन्य स्क्रिप्टिंग भाषाओं जैसे कि पायथन के बजाय बैश सरणी का उपयोग कब करना चाहिए?
मेरे लिए, यह सब निर्भरता के लिए उबलता है - यदि आप केवल कमांड-लाइन टूल पर कॉल का उपयोग करके समस्या को हल कर सकते हैं, तो आप बैश का भी उपयोग कर सकते हैं। लेकिन कई बार जब आपकी स्क्रिप्ट एक बड़े पायथन प्रोजेक्ट का हिस्सा होती है, तो आप पाइथन का भी इस्तेमाल कर सकते हैं।
उदाहरण के लिए, हम पैरामीटर स्वीप को लागू करने के लिए पायथन की ओर रुख कर सकते थे, लेकिन हमने बैश के चारों ओर एक आवरण लिखना समाप्त कर दिया होगा:
import subprocess
all_threads = [1, 2, 4, 8, 16, 32, 64, 128]
all_runtimes = []
# Launch pipeline on each number of threads
for t in all_threads:
cmd = './pipeline --threads {}'.format(t)
# Use the subprocess module to fetch the return output
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
output = p.communicate()[0]
all_runtimes.append(output)
चूंकि इस उदाहरण में कमांड लाइन के आसपास कोई नहीं है, इसलिए सीधे बैश का उपयोग करना बेहतर है।
बेशर्म प्लग का समय
यह लेख मेरे द्वारा OSCON में दिए गए एक भाषण पर आधारित है, जहां मैंने लाइव-कोडिंग कार्यशाला यू डोंट नो बैश प्रस्तुत की थी। . कोई स्लाइड नहीं, कोई क्लिकर नहीं - बस मैं और दर्शक कमांड लाइन पर टाइप कर रहे हैं, बैश की चमत्कारिक दुनिया की खोज कर रहे हैं।