مسیر
/
Bash
Bash
/
برنامه‌ی درسی
/
اهمیت نقل‌قول‌گذاری
اه

اهمیت نقل‌قول‌گذاری در Bash

{one: "۱ تمرین", other: "%{count} تمرین"}

درباره‌ی اهمیت نقل‌قول‌گذاری

یک توصیه که برنامه‌نویسان تازه‌کار 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.

با این حال، نقل‌قول نکردن متغیرها توصیه نمی‌شود، مگر اینکه بسط نام فایل را هم غیرفعال کنید. خواهیم دید که چطور این کار را در ادامه انجام دهیم.

بسط نام فایل

الگوهای «Glob» برای بیان یک الگوی مختصر جهت تطبیق با مجموعه‌ای از فایل‌ها به کار می‌روند. برای مثال، می‌توانید همه‌ی فایل‌های Markdown یک پوشه را با *.md تطبیق دهید. الگوهای Glob از زمان اختراع Unix وجود داشته‌اند.

نویسه‌های عام Glob عبارت‌اند از:

  • * با صفر یا چند نویسه از هر نوع تطبیق می‌یابد،

  • ? دقیقاً با یک نویسه از هر نوع تطبیق می‌یابد،

  • [...] دقیقاً با یک نویسه از مجموعه‌ی نویسه‌های فهرست‌شده درون کروشه تطبیق می‌یابد.

    Advanced
    جزئیات زیادی درباره‌ی مجموعه‌های نویسه وجود دارد. برای نمایش آن‌ها اینجا کلیک کنید.
    • [abc] دقیقاً با یک نویسه تطبیق می‌یابد که یا a باشد یا b یا c.
      • برای گنجاندن یک کروشه‌ی بسته‌ی تحت‌اللفظی در عبارت، آن را اولین نویسه پس از کروشه‌ی باز قرار دهید (پس از یک ^ اختیاری)
        • [][] یک مجموعه‌ی نویسه است که با کروشه‌ی بسته یا باز تطبیق می‌یابد.
        • یا آن را با یک بک‌اسلش escape کنید: [[\]]
    • [x-y] دقیقاً با یک نویسه تطبیق می‌یابد که در بازه‌ی x تا y باشد
      • برای گنجاندن یک نویسه‌ی خط تیره‌ی تحت‌اللفظی در یک مجموعه‌ی نویسه، باید یا اولین نویسه پس از کروشه‌ی باز باشد (پس از یک ^ اختیاری) یا آخرین نویسه پیش از کروشه‌ی بسته.
        • [-xy] یا [xy-] هرکدام دقیقاً با یک نویسه تطبیق می‌یابند که - یا x یا y باشد
        • یا آن را با یک بک‌اسلش escape کنید: [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:] ] تشکیل شده است -- فقط فاصله (اکتال ۰۴۰) و نه هیچ فاصله‌ی سفید دیگری.
پیمایش روی فهرستی از فایل‌ها

می‌توانید با یک حلقه‌ی for روی فهرستی از فایل‌ها پیمایش کنید

for file in *.csv; do
    do_something_with "$file"
done

دقت کنید که متغیر "$file" نقل‌قول شده است. الگوی Glob ممکن است فایل‌هایی را بازگرداند که در اسمشان فاصله دارند. باید متغیر را نقل‌قول کنیم تا دستور do_something_with دقیقاً یک آرگومان دریافت کند: اسم فایل.

الگوهای Glob عبارت باقاعده نیستند

الگوهای Glob عبارت باقاعده نیستند.

  • تطبیق با دقیقاً یک نویسه
    • regex .
    • glob ?
  • تطبیق با صفر یا چند نویسه
    • regex .*
    • glob *
      • الگوی glob .* یعنی «با اسم فایلی تطبیق بیاب که اولین نویسه‌اش یک نقطه‌ی تحت‌اللفظی باشد و پس از آن صفر یا چند نویسه از هر نوع بیاید».
  • تطبیق با یک یا چند نویسه
    • regex .+
    • glob ?*
  • مجموعه‌های نویسه‌ی regex و مجموعه‌های نویسه‌ی glob یکسان عمل می‌کنند.
Advanced

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:]

Caution

چرا این مسئله‌ساز است؟ الگوهای Glob بدون نقل‌قول به فهرست فایل‌های منطبق بسط داده می‌شوند. فرض کنید شخصی که از اسکریپت شما استفاده می‌کند، فایل‌هایی با اسم‌های 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 -- اگر هیچ فایلی منطبق نشود، الگو را پاک می‌کند. رفتار پیش‌فرض این است که الگو را به‌صورت یک رشته‌ی تحت‌اللفظی سر جایش بگذارد.
ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود