যদিও সফ্টওয়্যার ইঞ্জিনিয়াররা ডেভেলপমেন্টের অনেক দিকের জন্য কমান্ড লাইন ব্যবহার করে, অ্যারেগুলো কমান্ড লাইনের আরও অস্পষ্ট ফিচারের একটি (যদিও রেগেক্স অপারেটর =~ চেয়ে কম অস্পষ্ট)। কিন্তু অস্পষ্টতা এবং প্রশ্নার্থক সিনট্যাক্স পাশাপাশি, Bash অ্যারে খুব পাওয়ারফুল হতে পারে।
কেন?
Bash-এ লেখা চ্যালেঞ্জিং কারণ একটি আর্টিকেল খুব সহজে সিনট্যাক্সের অদ্ভুততাগুলোতে ফোকাস করে একটি ম্যানুয়ালে পরিণত হতে পারে। নিশ্চিন্ত থাকুন, এই আর্টিকেলের উদ্দেশ্য হলো আপনাকে RTFM করা থেকে বাঁচানো।
একটি আসল (আসলে উপযোগী) উদাহরণ
সেটি ধরে রেখে, আসুন একটি রিয়েল-ওয়ার্ল্ড সিনেরিও এবং Bash কীভাবে সাহায্য করতে পারে সেটি বিবেচনা করি: আপনি আপনার কোম্পানিতে একটি নতুন প্রচেষ্টা লিড করছেন আপনার ইন্টারনাল ডেটা পাইপলাইনের রানটাইম ইভ্যালুয়েট এবং অপ্টিমাইজ করার জন্য। প্রথম ধাপ হিসেবে, আপনি একটি প্যারামিটার সুইপ করতে চান ইভ্যালুয়েট করার জন্য পাইপলাইনটি থ্রেড কতটা ভালো ব্যবহার করে। সিমপ্লিসিটির জন্য, আমরা পাইপলাইনে একটি কম্পাইল্ড C++ ব্ল্যাক বক্স হিসেবে বर्तাব করব যেখানে একমাত্র প্যারামিটার যা আমরা টুইক করতে পারি সেটি হলো ডেটা প্রসেসিং-এর জন্য রিজার্ভড থ্রেডের সংখ্যা: ./pipeline --threads 4।
বেসিকস
প্রথম কাজ আমরা করব --threads প্যারামিটারের যে ভ্যালুগুলো টেস্ট করতে চাই সেগুলো ধারণকারী একটি অ্যারে ডিফাইন করা:
allThreads=(1 2 4 8 16 32 64 128)
এই উদাহরণে, সব এলিমেন্ট নম্বর, কিন্তু এটা আবশ্যক নয়—Bash-এ অ্যারে নম্বর এবং স্ট্রিং দুটোই ধারণ করতে পারে, যেমন myArray=(1 2 "three" 4 "five") একটি বৈধ এক্সপ্রেশন। এবং যেকোনো অন্য Bash ভেরিয়েবলের মত, ইকুয়াল সাইন চারপাশে কোনো স্পেস না থাকা নিশ্চিত করুন। অন্যথায়, Bash ভেরিয়েবল নামকে একটি প্রোগ্রাম হিসেবে বিবেচনা করবে, এবং =-কে এর প্রথম প্যারামিটার!।
এখন আমরা অ্যারেটি ইনিশিয়ালাইজ করেছি, আসুন এর কয়েকটি এলিমেন্ট রিট্রিভ করি। আপনি লক্ষ্য করবেন সিম্পলি echo $allThreads করলে শুধু প্রথম এলিমেন্ট আউটপুট হবে।
কেন সেটি হয় সেটি বুঝতে, আসুন একটু পেছনে যাই এবং রিভিজিট করি আমরা সাধারণত Bash-এ ভেরিয়েবল কীভাবে আউটপুট করি। নিচের সিনেরিও বিবেচনা করুন:
type="article" echo "Found 42 $type"
বলুন ভেরিয়েবল $type আমাদের সিঙ্গুলার নাউন হিসেবে দেয়া হয়েছে এবং আমরা আমাদের সেন্টেন্সের শেষে একটি s যোগ করতে চাই। আমরা সিম্পলি $type-এ s যোগ করতে পারি না কারণ সেটি সেটিকে একটি ভিন্ন ভেরিয়েবলে রূপান্তর করবে, $types। এবং যদিও আমরা কোড কন্টর্সন ব্যবহার করতে পারতাম যেমন echo "Found 42 "$type"s", এই সমস্যা সমাধান করার সেরা পদ্ধতি হলো কার্লি ব্রেস ব্যবহার করা: echo "Found 42 ${type}s", যা আমাদের Bash-কে বলে দেয় ভেরিয়েবল নাম কোথায় শুরু এবং কোথায় শেষ (এটা চমৎকারভাবেই জাভাস্ক্রিপ্ট/ES6-এ টেমপ্লেট লিট্রেলে ভেরিয়েবল এবং এক্সপ্রেশন ইনজেক্ট করার একই সিনট্যাক্স)।
তাহলে 밝혀 পড়ল, যদিও Bash ভেরিয়েবল সাধারণত কার্লি ব্র্যাকেট দরকার নয়, অ্যারেতে এগুলো আবশ্যক। একেrando, এটি আমাদের ইনডেক্স স্পেসিফাই করতে দেয় অ্যাক্সেস করার জন্য, যেমন echo ${allThreads[1]} অ্যারের দ্বিতীয় এলিমেন্ট রিটার্ন করে। ব্র্যাকেট ছাড়া, যেমনecho $allThreads[1], Bash [1]-কে একটি স্ট্রিং হিসেবে বিবেচনা করে এবং সেটি প্রিন্ট করে।
হ্যাঁ, Bash অ্যারের অদ্ভুত সিনট্যাক্স আছে, কিন্তু কমপক্ষে সেগুলো জিরো-ইনডেক্সড, কিছু অন্য ল্যাঙ্গুয়েজের মত নয় (আমি তোমাকেই লক্ষ্য করছি, R)।
অ্যারে থ্রু লুপ করা
যদিও উপরের উদাহরণগুলোর মধ্যে আমরা অ্যারেতে ইন্টিজার ইনডেক্স ব্যবহার করেছি, আসুন দুটি okazিয়ন বিবেচনা করি যখন সেটি কেস নয়: প্রথম, যদি আমরা অ্যারেতে $i-th এলিমেন্ট চাই, যেখানে $i ইনডেক্স অফ ইন্টারেস্ট ধারণকারী একটি ভেরিয়েবল, আমরা সেটি রিট্রিভ করতে পারি ব্যবহার করে: echo ${allThreads[$i]}। দ্বিতীয়, একটি অ্যারের সকল এলিমেন্ট আউটপুট করতে, আমরা নিউমেরিক ইনডেক্সকে @ সিম্বল দিয়ে রিপ্লেস করি (আপনি @-কে all হিসেবে ভাবতে পারেন): echo ${allThreads[@]}।
অ্যারে এলিমেন্ট থ্রু লুপ করা
সেটি ধরে রেখে, আসুন $allThreads থ্রু লুপ করি এবং পাইপলাইন লঞ্চ করি --threads-এর প্রতিটি ভ্যালু için:
for t in ${allThreads[@]}; do
./pipeline --threads $t
done
অ্যারে ইনডেক্স থ্রু লুপ করা
পরবর্তী, আসুন একটু আলাদা একটি আপপ্রোচ বিবেচনা করি। অ্যারে এলিমেন্টস ওভার লুপ করার বদলে, আমরা অ্যারে ইনডেক্সেস ওভার লুপ করতে পারি:
for i in ${!allThreads[@]}; do
./pipeline --threads ${allThreads[$i]}
done
এটিকে ব্রেক ডাউন করি: যেমন আমরা উপরে দেখেছি, ${allThreads[@]} আমাদের অ্যারেতে সকল এলিমেন্ট রিপ্রেজেন্ট করে। একটি এক্সক্লামেশন মার্ক যোগ করে এটিকে ${!allThreads[@]} বানাতে হবে সকল অ্যারে ইনডেক্সের লিস্ট রিটার্ন করবে (আমাদের কেসে ০ থেকে ৭)। অন্য কথায়, for লুপটি সকল ইনডেক্স $i থ্রু লুপ করছে এবং $allThreads- থেকে $i-th এলিমেন্ট পড়ে --threads প্যারামিটারের ভ্যালু সেট করছে।
এটি চোখে খুব কঠোর, তাই আপনি চিন্তা করতে পারেন কেন আমি এটিকে ভাবি প্রথম অবস্থায়। সেটি কারণ সময় থাকে যখন আপনাকে লুপের ভিতরে ইনডেক্স এবং ভ্যালু দুটোই জানতে হয়, যেমন, আপনি যদি একটি অ্যারের প্রথম এলিমেন্ট উপেক্ষা করতে চান, ইনডেক্স ব্যবহার করলে আপনাকে একটি অডিশনাল ভেরিয়েবল তৈরি করতে হবে না যা আপনি লুপের ভিতরে ইনক্রিমেন্ট করবেন।
অ্যারে পপুলেট করা
এতটুকু, আমরা প্রতিটি ইন্টারেস্টের --threads জন্য পাইপলাইন লঞ্চ করতে পেরেছি। এখন, ধরি পাইপলাইন আউটপুট হলো সেকেন্ডে রানটাইম। আমরা চাই সেই আউটপুট প্রতিটি ইটেরেশনে ক্যাপচার করা এবং অন্য একটি অ্যারেতে সংরক্ষণ করা যাতে আমরা শেষে সেটির সাথে বিভিন্ন ম্যানিপুলেশন করতে পারি।
কিছু উপযোগী সিনট্যাক্স
কোডে ঢুকতে তার আগে, আমাদের কিছু আরো সিনট্যাক্স পরিচয় করাতে হবে। প্রথম, আমাদের একটি Bash কমান্ডের আউটপুট রিট্রিভ করতে পারতে হবে। সেটি করতে, নিচের সিনট্যাক্স ব্যবহার করুন: 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
এতটুকু!
আর কী আছে?
এই আর্টিকেলে, আমরা প্যারামিটার সুইপের জন্য অ্যারে ব্যবহার করার সিনেরিও কভার করেছি। কিন্তু আমি প্রতিজ্ঞা করি Bash অ্যারে ব্যবহার করার আরও কারণ আছে—এখানে আরো দুটি উদাহরণ।
লগ অ্যালার্টিং
এই সিনেরিওতে, আপনার অ্যাপ মডিউলে ভাগ করা হয়েছে, প্রতিটির নিজস্ব লগ ফাইল রয়েছে। আমরা একটি ক্রন জব স্ক্রিপ্ট লিখতে পারি সঠিক ব্যক্তিকে ইমেইল করার জন্য যখন নির্দিষ্ট মডিউলে ট্রাবলের সাইন থাকে:
# 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 ব্যবহার করতে পারি!
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-এর সিনট্যাক্স এই আর্টিকেলের স্কোপ বাইরে, কিন্তু আমি দৃঢ়ভাবে রিকমেন্ড করি এটিকে লুক করতে।
যেমন আপনি কল্পনা করতে পারেন, Bash অ্যারে ব্যবহার করা সাহায্য করতে পারে অসংখ্য অন্য সিনেরিওতে, এবং আশা করি এই আর্টিকেলে আউটলাইন করা উদাহরণগুলো আপনাকে কিছু চিন্তার খাদ্য দিয়েছে। আপনার নিজস্ব কাজ থেকে আরো উদাহরণ শেয়ার করুন কমেন্টে।
বিট নয়, আরো আছে!
যেহেতু এই আর্টিকেলে আমরা khá বেশি অ্যারে সিনট্যাক্স কভার করেছি, এখানে যা কভার করা হয়েছে তার একটি সারাংশ, পাশাপাশি কিছু অ্যাডভান্সড ট্রিক যা আমরা কভার করিনি:
| সিনট্যাক্স | ফলাফল |
|---|---|
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 |
একটি শেষ চিন্তা
যেমন আমরা আবিষ্কার করেছি, Bash অ্যারের সিনট্যাক্স নিঃসন্দেহে স্ট্রেইঞ্জ, কিন্তু আশা করি এই আর্টিকেল আপনাকে কনভিন্স করলো যে সেগুলো অত্যন্ত পাওয়ারফুল। একবার আপনি সিনট্যাক্সের অভ্যস্ত হলে, আপনি নিজেকে Bash অ্যারে প্রায়শই ব্যবহার করতে পাবেন।
Bash নাকি Python?
যে প্রশ্নটি জন্মায়: আপনি কখন Bash অ্যারে ব্যবহার করবেন Python-এর মত অন্যান্য স্ক্রিপ্টিং ল্যাঙ্গুয়েজের বদলে?
আমার কাছে, এটা সব ডিপেন্ডেন্সির ওপর নিবর্ভর করে—আপনি যদি হাতে আত্মীয় সমস্যা সমাধান করতে পারেন কেবল কমান্ড-লাইন টুল কল দিয়ে, আপনি ঠিকই Bash ব্যবহার করতে পারেন। কিন্তু যখন আপনার স্ক্রিপ্ট একটি বড় Python প্রজেক্টের অংশ, তখন আপনি ঠিকই Python ব্যবহার করতে পারেন।
উদাহরণস্বরূপ, আমরা প্যারামিটার সুইপ ইমপ্লিমেন্ট করতে Python-এ যেতে পারতাম, কিন্তু আমরা শেষে শুধু Bash-এর চারপাশে একটি র্যাপার লিখতাম:
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)
যেহেতু এই উদাহরণে কমান্ড লাইন থেকে বের হওয়ার কোনো উপায় নেই, সরাসরি Bash ব্যবহার করা প্রেফারেবল।
শেমলেস প্লাগের টাইম
এই আর্টিকেলটি OSCON-এ আমার দিয়া একটি টক উপর ভিত্তি করে, যেখানে আমি লাইভ-কোডিং वर्कশপ You Don't Know Bash প্রেজেন্ট করেছিলাম। কোনো স্লাইড নেই, কোনো ক্লিকারের দরকার নেই—শুধু আমি এবং দর্শক কমান্ড লাইনে টাইপ করছি, Bash-এর অদ্ভুত বিশ্ব এক্সপ্লোর করছি।