مسیر
/
Bash
Bash
/
برنامه‌ی درسی
/
خط لوله‌ها و فهرست‌های دستور
خط

خط لوله‌ها و فهرست‌های دستور در Bash

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

درباره‌ی خط لوله‌ها و فهرست‌های دستور

دیده‌اید که چگونه دستورهای ساده بنویسیم؛ دستوری که پس از آن آرگومان‌ها می‌آیند. حالا می‌بینیم که چگونه با ترکیب دستورهای ساده، دستورهای پیچیده‌تری بسازیم.

ورودی و خروجی

پیش از شروع، یک معرفی کوتاه درباره‌ی ورودی/خروجی.

فرایندها «کانال‌های ورودی/خروجی استاندارد» دارند.

  • یک فرایند می‌تواند ورودی را از «stdin» مصرف کند.
  • یک فرایند می‌تواند خروجی را روی «stdout» تولید کند.
  • یک فرایند می‌تواند خروجی خطا را روی «stderr» تولید کند.

دستور tr نمونه‌ی بسیار خالصی از این موضوع است. تنها کاری که می‌کند این است که متن را از ورودی‌اش می‌خواند، نویسه‌ها را ترانویسی می‌کند و متن حاصل را به خروجی‌اش می‌فرستد.

بعداً در برنامه‌ی درسی، بیشتر درباره‌ی کار با کانال‌های «stdio» خواهیم دید.

پایپلاین

این یکی از ویژگی‌های برجسته‌ی برنامه‌نویسی شل است. پایپلاین‌ها به شما اجازه می‌دهند که تبدیل‌های پیچیده‌ای روی جریانی از متن انجام دهید.

برای تولید فهرستی مرتب از کاربران:

cat /etc/passwd | cut -d : -f 1 | sort

نماد پایپ (|) خروجی یک دستور را به ورودی دستور دیگر وصل می‌کند. cut خروجی cat را می‌خواند و sort خروجی cut را می‌خواند.

Advanced
  • به‌طور پیش‌فرض، هر دستور در یک پایپلاین در یک زیرشل جداگانه اجرا می‌شود. (زیرشل یک فرایند فرزند است که نسخه‌ای از شل در حال اجراست.)

  • اجرای پایپلاین‌ها هزینه‌ی کارایی دارد. اگر پایپلاین‌های طولانی‌ای از دستورهای مشابه دارید، بهتر است همه را در یک دستور ترکیب کنید. برای نمونه، پایپلاین‌هایی که از چند نمونه از grep، cut، sed، awk و tr استفاده می‌کنند، معمولاً می‌توانند برای کارایی بیشتر در یک دستور awk واحد ترکیب شوند.

  • وضعیت خروج یک پایپلاین، وضعیت خروج آخرین دستور در آن پایپلاین است. یعنی اگر دستورهای قبلی شکست خورده باشند اما دستور آخر موفق شده باشد، وضعیت خروج کل پایپلاین صفر است.

    تنظیمی در شل وجود دارد که می‌تواند این را تغییر دهد؛ اگر تنظیم «pipefail» فعال باشد (با set -o pipefail)، وضعیت خروج پایپلاین چنین خواهد بود:

    • اگر همه‌ی دستورها موفق شوند، صفر؛ در غیر این صورت
    • آخرین وضعیت خروج غیرصفر دستورهای موجود در پایپلاین.

فهرست دستور

فهرست دستور، دنباله‌ای از پایپلاین‌هاست که با ; (یا خط جدید)، && یا || از هم جدا می‌شوند.

  • A; B یک فهرست دستور است که در آن B پس از پایان A اجرا می‌شود.
  • A && B، که در آن B تنها در صورتی اجرا می‌شود که A موفق شود (با وضعیت صفر خارج شود).
  • A || B، که در آن B تنها در صورتی اجرا می‌شود که A شکست بخورد (با وضعیت غیرصفر خارج شود).

وضعیت خروج یک فهرست دستور، وضعیت خروج آخرین دستوری است که اجرا شده است.

عملگرهای && و || را می‌توان زنجیره‌ای به کار برد، طوری که دستور بعدی بر اساس وضعیت دستورهای پیشین به‌صورت شرطی اجرا شود. برای نمونه

A && B && C && D || E
  • اگر A موفق شود، B اجرا می‌شود،
  • اگر A و B موفق شوند، C اجرا می‌شود،
  • اگر A و B و C موفق شوند، D اجرا می‌شود،
  • اگر هر یک از A، B، C یا D شکست بخورد، E اجرا می‌شود.
Caution

از این عملگرهای منطقی کم استفاده کنید. آن‌ها می‌توانند به‌سرعت به کدی ناخوانا یا منطقی دشوارفهم منتهی شوند.

برای نمونه، ممکن است فکر کنید این دو یکی هستند:

if A; then B; else C; fi
A && B || C

تفاوت اینجاست: C چه زمانی اجرا می‌شود؟

  • در قطعه‌ی اول (دستور شرطی)، C تنها در صورتی اجرا می‌شود که A شکست بخورد.
  • در قطعه‌ی دوم، اگر A شکست بخورد یا اگر A موفق شود ولی B شکست بخورد، C اجرا می‌شود!

کاربردهای فهرست دستور

در ادامه چند نمونه می‌بینید که فهرست‌های دستور می‌توانند کد را ساده‌تر کنند.

خواندن بلوک‌هایی از خطوط از یک فایل

فرض کنید فایل داده‌ای دارید که داده‌هایی درباره‌ی مثلث‌ها در آن است و هر مثلث با سه خط جداگانه نمایش داده می‌شود که اضلاع آن مثلث را در خود دارند.

$ cat triangle.dat
3
4
5
9
12
14

می‌توانید از یک حلقه‌ی while استفاده کنید که شرطش سه دستور read جداگانه است:

while read a && read b && read c; do
    if is_pythagorean "$a" "$b" "$c"; then
        echo "$a:$b:$c is pythagorean"
    else
        echo "$a:$b:$c is not pythagorean"
    fi
done < triangle.dat

با فرض اینکه is_pythagorean دستوری باشد که تعیین می‌کند آیا سه ضلع در معادله‌ی فیثاغورس صدق می‌کنند یا نه، خروجی چنین خواهد بود:

3:4:5 is pythagorean
9:12:14 is not pythagorean
ادعاها

بسیاری از زبان‌های برنامه‌نویسی نوعی ادعا دارند که در آن اگر شرطی برآورده نشود، یک استثنا پرتاب می‌شود

assert(user == "Administrator", "wrong user");

می‌توانیم از یک عملگر OR در Bash استفاده کنیم تا آن تابع را شبیه‌سازی کنیم:

die () { # a little convenience function
    echo "$*" >&2
    exit 1
}

[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]]   || die "incorrect password"

ملاحظات سبک

فهرست‌های دستور طولانی خیلی زود ناخوانا می‌شوند. استفاده‌ی فراوان از خطوط جدید می‌تواند بسیار کمک کند.

نمونه‌ای را در نظر بگیرید که در آن اگر دو شرط برآورده شوند، واژه‌ای به یک آرایه اضافه می‌شود.

[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")

Bash به شما اجازه می‌دهد پس از یک پایپ یا یک عملگر منطقی، خط جدید اضافه کنید.

[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")

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

[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
Note

ادامه‌ی خط دنباله‌ی دو نویسه‌ای «بک‌اسلش» و «خط جدید» است (\ + \n). وقتی Bash این دنباله را می‌بیند، به‌سادگی از کد حذف می‌شود و بدین‌ترتیب خط جاری با خط بعدی ادامه می‌یابد. مراقب باشید بین بک‌اسلش و خط جدید هیچ فاصله‌ای نگذارید.

نمونه‌ی دیگری هم هست

printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort

یا

printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort
ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود