নতুন bash প্রোগ্রামাররা বারবার যে একটি পরামর্শ শুনবেন:
আপনার ভ্যারিয়েবল কোট করুন!
কেন এমন?
আপনি যখন কোনো ভ্যারিয়েবল (যেমন $name) এক্সপ্যান্ড করেন, তখন প্রায় সবসময়ই আপনি চান ভ্যারিয়েবলের মান একটি একক স্ট্রিং (বা সংখ্যা) হিসেবে ব্যবহৃত হোক।
আপনি চান না যে মানটি অনাকাঙ্ক্ষিতভাবে দুই বা তার বেশি আলাদা স্ট্রিং হিসেবে ব্যবহার হয়ে যায়।
কোনো লেখার অংশকে একটি একক শব্দ হিসেবে গণ্য করতে কোটিং হলো প্রধান পদ্ধতি। কোট প্রধানত দুই ধরনের হয়।
ডাবল কোটের ভেতরে প্যারামিটার এক্সপেনশন, অ্যারিথমেটিক এক্সপেনশন আর কমান্ড সাবস্টিটিউশন কাজ করে।
echo "This is bash version $BASH_VERSION"
ডাবল কোট দেওয়া স্ট্রিংয়ের ভেতরে একটি আক্ষরিক ডাবল কোট রাখতে চাইলে তার আগে একটি ব্যাকস্ল্যাশ বসান।
echo "She said \"Let me teach you about bash.\""
সিঙ্গেল কোটের ভেতরে কোনো এক্সপেনশন কাজ করে না।
echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION
সিঙ্গেল কোট দেওয়া স্ট্রিংয়ের ভেতরে একটি সিঙ্গেল কোট ঢোকানো সম্ভব নয়।
echo $name-এর আসলে কী সমস্যা?
শেল যখন কোনো কোডের লাইন পার্স করে, তখন সে ধাপে ধাপে কয়েক ধরনের এক্সপেনশন চালায়। শেষের দিকের দুটি এক্সপেনশন হলো ওয়ার্ড স্প্লিটিং আর ফাইলনেম এক্সপেনশন।
প্যারামিটার এক্সপেনশন, কমান্ড সাবস্টিটিউশন বা অ্যারিথমেটিক এক্সপেনশনের যেসব ফলাফল ডাবল কোটের ভেতরে তৈরি হয়নি, সেগুলোই "ওয়ার্ড স্প্লিটিং"-এর জন্য বিবেচিত হয়।
শেল এক্সপ্যান্ড হওয়া ফলাফলটি নেয়, তারপর $IFS-এর মানে থাকা ক্যারেক্টারের যেকোনো ধারাবাহিকতায় সেটিকে ভাগ করে।
ডিফল্টভাবে এটি হলো স্পেস, ট্যাব আর নিউলাইন।
একটি শেল লুপ ব্যবহার করে এটি কাজ করতে দেখা যায়:
sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^
আউটপুট দেয়
1 The quick brown fox jumps.
কিন্তু কোট ছাড়া:
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
আউটপুট দেয়
1 The
2 quick
3 brown
4 fox
5 jumps.
চলুন $IFS-এর একটি ভিন্ন মান দিয়ে এটি দেখি:
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
আউটপুট দেয়
first
second
third
ওয়ার্ড স্প্লিটিং বন্ধ করার একটি কৌশল হলো $IFS-কে একটি খালি স্ট্রিংয়ে সেট করা
sentence="The quick brown fox jumps."
IFS= # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
আউটপুট দেয়
1 The quick brown fox jumps.
তবে, ফাইলনেম এক্সপেনশনও বন্ধ না করলে ভ্যারিয়েবল কোট ছাড়া রেখে দেওয়া ঠিক নয়। সেটি কীভাবে করতে হয়, নিচে দেখব।
"গ্লোব" প্যাটার্ন দিয়ে একগুচ্ছ ফাইলের সাথে মেলানো যায় এমন সংক্ষিপ্ত প্যাটার্ন প্রকাশ করা হয়।
উদাহরণস্বরূপ, আপনি একটি ডিরেক্টরির সব Markdown ফাইল *.md দিয়ে মেলাতে পারেন।
ইউনিক্স আবিষ্কারের পর থেকেই গ্লোব প্যাটার্ন চলে আসছে।
গ্লোবের ওয়াইল্ডকার্ড ক্যারেক্টারগুলো হলো:
* যেকোনো ক্যারেক্টারের শূন্য বা তার বেশি অক্ষরের সাথে মেলে,
? যেকোনো ক্যারেক্টারের ঠিক একটির সাথে মেলে,
[...] বন্ধনীর ভেতরে তালিকাভুক্ত ক্যারেক্টারের সেট থেকে ঠিক একটি ক্যারেক্টারের সাথে মেলে।
[abc] ঠিক একটি ক্যারেক্টারের সাথে মেলে, যেটি a বা b বা c যেকোনো একটি।
^-এর পরে)
[][] হলো এমন একটি ক্যারেক্টার সেট যা ক্লোজ কিংবা ওপেন ব্র্যাকেট দুটির যেকোনোটির সাথে মেলে।[[\]]
[x-y] ঠিক একটি ক্যারেক্টারের সাথে মেলে, যেটি x থেকে y পর্যন্ত রেঞ্জের ভেতরে থাকে
^-এর পরে), নয়তো ক্লোজিং ব্র্যাকেটের আগে শেষ ক্যারেক্টার হতে হবে।
[-xy] বা [xy-] প্রত্যেকটি ঠিক একটি ক্যারেক্টারের সাথে মেলে, যেটি - বা x বা y যেকোনো একটি[x\-y]
[0-9A-Fa-f] একটি হেক্সাডেসিমাল ডিজিটের সাথে মেলে[^abc] ঠিক একটি ক্যারেক্টারের সাথে মেলে, যেটি a বা b বা c নয়,[^x-y] ঠিক একটি ক্যারেক্টারের সাথে মেলে, যেটি x থেকে y পর্যন্ত রেঞ্জের ভেতরে নয়
[[:character_class:]] একটি ক্যারেক্টারের সাথে মেলে, যেটি নাম দেওয়া "character_class"-এর ভেতরে আছে
lower -- ছোট হাতের অক্ষরupper -- বড় হাতের অক্ষরalpha -- অক্ষরdigit -- ডিজিটalnum -- অক্ষর আর ডিজিটspace -- হোয়াইটস্পেসblank -- অনুভূমিক হোয়াইটস্পেস (স্পেস আর ট্যাব)xdigit -- হেক্সাডেসিমাল ডিজিটword -- আইডেন্টিফায়ারে অনুমোদিত ক্যারেক্টার (অক্ষর, সংখ্যা, আন্ডারস্কোর)punct -- যতিচিহ্নcntrl -- কন্ট্রোল ক্যারেক্টারgraph -- "দৃশ্যমান" ক্যারেক্টারprint -- দৃশ্যমান ক্যারেক্টার আর স্পেস[[:alpha:]] ক্লাসটি [[:lower:][:upper:]] নিয়ে গঠিত[[:alnum:]] ক্লাসটি [[:alpha:][:digit:]] নিয়ে গঠিত[[:word:]] ক্লাসটি [[:alnum:]_] নিয়ে গঠিত[[:word:]] আর [[:punct:]]
[[:graph:]] ক্লাসটি [[:alnum:][:punct:]] নিয়ে গঠিত[[:print:]] ক্লাসটি [[:graph:] ] নিয়ে গঠিত -- শুধু স্পেস (অক্টাল 040), অন্য কোনো হোয়াইটস্পেস নয়।আপনি একটি for লুপ দিয়ে ফাইলের তালিকার উপর ইটারেশন করতে পারেন
for file in *.csv; do
do_something_with "$file"
done
লক্ষ্য করুন, "$file" ভ্যারিয়েবলটি কোট করা হয়েছে।
গ্লোব প্যাটার্ন এমন ফাইলও ফেরত দিতে পারে যাদের নামে স্পেস আছে।
আমাদের ভ্যারিয়েবলটি কোট করতে হবে, যাতে do_something_with কমান্ডটি ঠিক একটি আর্গুমেন্ট, অর্থাৎ ফাইলের নামটি পায়।
গ্লোব প্যাটার্ন রেগুলার এক্সপ্রেশন নয়।
.
?
.*
*
.* মানে "এমন একটি ফাইলনেমের সাথে মেলা যার প্রথম ক্যারেক্টার একটি আক্ষরিক ডট, তারপর যেকোনো ক্যারেক্টারের শূন্য বা তার বেশি"।.+
?*
Bash এক্সটেন্ডেড প্যাটার্ন দেয়, যেগুলো রেগুলার এক্সপ্রেশনের আরও কাছাকাছি।
এমন সময় আসে যখন আপনি ফাইলনেম এক্সপেনশন চেপে দিতে চান। ইউজার ইনপুট হ্যান্ডল করা তেমনই একটি সময়। এই কোড স্নিপেটটি দেখুন
read -p "Enter something: " user_input
echo $user_input
ইউজার যদি একটি অ্যাস্টেরিস্ক (*) লিখেন, তাহলে আউটপুট কী আসবে?
বর্তমান ডিরেক্টরির ফাইলের তালিকা।
সম্ভবত আপনি এটাই চান না।
আরেকটি উদাহরণ হলো, প্যাটার্ন প্রত্যাশা করে এমন প্রোগ্রামে আর্গুমেন্ট হিসেবে প্যাটার্ন পাঠানো।
টেক্সট ম্যানিপুলেশনের জন্য প্রায়ই tr প্রোগ্রামটি ব্যবহার করা হয়। একটি ভ্যারিয়েবলের মানকে ছোট হাতের অক্ষরে করতে আপনি এটি করতে পারেন
var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'
আউটপুট দেয়
my puppet
একটি কমন ভুল হলো tr-এর আর্গুমেন্ট কোট করতে ভুলে যাওয়া: tr [:upper:] [:lower:]
এটা কেন সমস্যা?
কোট ছাড়া গ্লোব প্যাটার্ন মিলে যাওয়া ফাইলের তালিকায় এক্সপ্যান্ড হয়ে যাবে।
ধরুন, আপনার স্ক্রিপ্ট ব্যবহারকারী ব্যক্তির বর্তমান ডিরেক্টরিতে p, u আর w নামের ফাইল আছে:
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
এতে একটি এরর হয়!
কোট ছাড়া [:upper:] প্যাটার্নটি :, u, p, e বা r যেকোনোটির সাথে মেলে।
কোট ছাড়া [:lower:] প্যাটার্নটি :, l, o, w, e বা r যেকোনোটির সাথে মেলে।
Bash প্রথম প্যাটার্নের জন্য p আর u ফাইল মেলায়, আর দ্বিতীয় প্যাটার্নের জন্য w ফাইল, তারপর tr এভাবে চালানো হয়
echo "$var" | tr p u w
আর এটা tr-এর জন্য ভুল সংখ্যক আর্গুমেন্ট।
ফাইলনেম এক্সপেনশন বন্ধ করতে আপনি set কমান্ডটি ব্যবহার করতে পারেন।
echo * # prints a list of files
set -f # disable filename expansion
echo * # prints a literal asterisk
set +f # enable filename expansion
বিল্টইন shopt কমান্ডের একাধিক সেটিং আছে যেগুলো ফাইলনেম এক্সপেনশন কীভাবে কাজ করে তা নিয়ন্ত্রণ করে।
এর মধ্যে কয়েকটি মজার সেটিং হলো:
shopt -s nocaseglob -- কেস-অসংবেদনশীল ম্যাচিং করেshopt -s extglob -- এক্সটেন্ডেড প্যাটার্ন চালু করেshopt -s nullglob -- কোনো ফাইল না মিললে প্যাটার্নটির জায়গায় কিছুই বসায় না।
ডিফল্ট আচরণ হলো প্যাটার্নটিকে আক্ষরিক স্ট্রিং হিসেবে রেখে দেওয়া।