احتمالاً تاکنون دهها اسکریپت Bash نوشته شده است. در بسیاری از آنها از grep در شرطها یا از sed برای تغییر متنهای کوچک بهصورت درونخطی استفاده شده است. اما این روش در واقع بسیار ناکارآمد است. Bash ابزارهای داخلی دارد که همین کارها را بهتر انجام میدهند، ولی کمتر مورد استفاده قرار میگیرند. زمان آن رسیده که این وضعیت اصلاح شود.
استفاده از ابزارهای خارجی در جاهایی که Bash خودش عملکرد بهتری دارد، بسیار رایج بوده است. این موضوع اغراقآمیز نیست؛ در عمل بهبود کارایی واقعی ایجاد میشود و اغلب مطابق با بهترین شیوههاست. تطبیق الگو (Pattern Matching) در Bash ساده است و در بیشتر مواقع کاملاً کافی است. در ادامه توضیح داده میشود چرا grep و sed کند هستند، جایگزینهای آنها چه هستند و چگونه باید از آنها استفاده کرد.
چرا نباید از grep و sed استفاده کرد؟
هر دو ابزار grep و sed فوقالعادهاند و دهههاست که جایگاه خود را حفظ کردهاند، اما استفاده نادرست از آنها مشکلساز میشود.
برای درک مسئله اصلی، اجرای ۱۰ هزار فراخوانی متوالی grep را در نظر بگیرید:
time for ((i=0; i<10000; i++)); do
echo 'Hello, World!' | grep 'Hello' >/dev/null
done
همین آزمایش برای sed:
time for ((i = 0; i < 10000; i++)); do
echo 'Hello, World!' | sed 's/Hello/Goodbye/' >/dev/null
done
حال این نتایج را با معادلهای Bash که از تطبیق الگو استفاده میکنند مقایسه کنید. نمونهای از جایگزین grep در Bash:
time for ((i = 0; i < 10000; i++)); do
[[ 'Hello, World!' == *Hello* ]] && true
done
و جایگزین sed در Bash:
str='Hello, World!'
time for ((i = 0; i < 10000; i++)); do
result=${str/Hello/Goodbye}
done
دلیل کندتر بودن grep و sed این است که هر بار اجرای یک برنامه خارجی نیازمند موارد زیر است:
این فرایند هزینه پردازشی بالایی دارد.
البته این مثالها نماینده تمام سناریوها نیستند. grep و sed در پردازش حجم زیادی از متن یا فایلهای بزرگ بسیار قدرتمند عمل میکنند. اما هرچه کارها کوچکتر و تعداد فراخوانیها بیشتر شود، کارایی آنها کاهش مییابد.
تطبیق الگو در Bash چیست؟
تطبیق الگو یعنی بررسی رشتهها بر اساس الگوهای مشخص. این قابلیت بهصورت بومی در Bash وجود دارد، بنابراین نیازی به ایجاد فرآیندهای پرهزینه خارجی نیست.
یک الگوی ساده به این شکل است:
Hello*
این الگو هر رشتهای را که با «Hello» شروع شود، تطبیق میدهد. این نوع استفاده از ستاره احتمالاً آشناست، اما امکانات بسیار بیشتری وجود دارد.
تطبیق الگو معمولاً در ساختار case استفاده میشود:
value="Hello, World!"
case "$value" in
Hello*) echo "matched" ;;
*) echo "I match anything not matched" ;;
esac
همچنین میتوان از آن در شرطها استفاده کرد:
[[ "Hello, World!" == Hello* ]] && echo "matched"
در این حالتها میتوان grep را برای موارد ساده کنار گذاشت.
توجه شود که Hello* یک رشته معمولی نیست. اگر داخل کوتیشن قرار بگیرد، Bash آن را بهصورت literal در نظر میگیرد که مطلوب نیست. همچنین برای تطبیق الگو در شرطها باید از براکت دوتایی [[ ... ]] استفاده شود.
برای جایگزینی متن (مشابه کاری که sed انجام میدهد)، میتوان از گسترش پارامتر (Parameter Expansion) استفاده کرد:
str='Hello, World!'
echo "${str/Hello/Goodbye}"
گسترش پارامتر یعنی تبدیل یک متغیر به مقدار نهایی آن قبل از اجرای اسکریپت؛ برای مثال ${var/Foo/Bar} مقدار متغیر را با جایگزینی متن تغییر میدهد.
آشنایی با مبانی تطبیق الگو
سادهترین نوع تطبیق الگو از wildcardها استفاده میکند:
ls foo*
*: تطبیق با هر رشتهای?: تطبیق با دقیقاً یک کاراکتر
علاوه بر اینها، Bash امکانات بیشتری هم دارد. عبارات براکتی (Bracket Expressions) امکان انتخاب مجموعهای از کاراکترها را فراهم میکنند:
[a-z]
نمونههای دیگر:
[A-Z]: هر حرف بزرگ[a-zA-Z]: هر حرف کوچک یا بزرگ[۰-۹]: هر رقم[^a-z]: هر کاراکتری بهجز حروف کوچک
امکان ترکیب این الگوها با wildcardها نیز وجود دارد، مانند:
[a-z][-_0-9]*
کلاسهای کاراکتری
کلاسهای کاراکتری برای تطبیق متن مستقل از locale استفاده میشوند و با کاراکترهای غیر ASCII نیز سازگارند. مثال:
[:alnum:]
برای استفاده از این کلاسها، باید آنها را داخل یک عبارت براکتی دیگر قرار داد:
[[:alnum:]]
و حتی میتوان آنها را ترکیب کرد:
[a-z[:digit:]]
تطبیق الگو در عمل
نمونهای پیچیدهتر از تطبیق الگو:
[[ 'Hello, World!' == [Hh]ello?[[:space:]]W* ]] && echo 'matched'
در این مثال:
- رشته باید با «Hello» یا «hello» شروع شود
- سپس یک کاراکتر دلخواه بیاید
- بعد یک فاصله
- سپس حرف «W»
- و در نهایت هر رشتهای (یا حتی هیچ چیز)
نمونههایی که با این الگو تطبیق مییابند:
Hello; WIPHello_ WoopsHello& W
جمعبندی
برای استفادههای تکبار، بهکارگیری grep و sed مشکلی ایجاد نمیکند، اما از نظر کارایی بهینه نیست. هر بار اجرای این ابزارها نیازمند ایجاد و نابودی ساختارهای داخلی آنهاست. در مقابل، قابلیتهای بومی Bash بخشی از یک برنامه در حال اجرا هستند و هزینه اضافی ندارند.
همچنین استفاده از این ابزارها فقط بهخاطر عبارات منظم (Regex) نیز معمولاً ضروری نیست، زیرا Bash با عملگر =~ از Regex پشتیبانی میکند.
در نهایت، سینتکس تطبیق الگو در Bash تمیزتر، استانداردتر و مطابق با بهترین شیوههاست. مگر در زمانی که حجم بزرگی از داده پردازش میشود، استفاده از قابلیتهای داخلی Bash انتخاب بهتری خواهد بود.














