اس

اسناد Here در Bash

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

درباره‌ی اسناد Here

در اسکریپت‌نویسی Bash، یک «here document» (یا «heredoc») چندین خط ورودی را به یک دستور یا برنامه تغییر مسیر می‌دهد، طوری که گویی خودتان آن‌ها را مستقیم در ترمینال تایپ می‌کنید. ابزار قدرتمندی است برای جاسازی متن چندخطی درون اسکریپت‌های شما، بدون نیاز به فایل‌های بیرونی یا دست‌کاری پیچیده‌ی رشته‌ها.

ویژگی‌های کلیدی و نحوه‌ی نگارش

  1. جداکننده: یک heredoc با عملگر << شروع می‌شود و پس از آن یک واژه‌ی جداکننده می‌آید (که اغلب «نشانگر» یا «پایان‌دهنده» نامیده می‌شود). این جداکننده می‌تواند هر واژه‌ای باشد که انتخاب می‌کنید، اما برای وضوح معمولاً از چیزهایی مثل EOF، END یا TEXT استفاده می‌شود. برای کدی خواناتر، می‌توانید یک واژه‌ی توصیفی را به عنوان جداکننده به کار ببرید، مثلاً END_INSTALLATION_INSTRUCTIONS.

  2. محتوا: بعد از << DELIMITER اولیه، محتوایی را می‌نویسید که می‌خواهید تغییر مسیر دهید. این می‌تواند چندین خط متن، کد یا هر محتوای دیگری باشد.

  3. پایان: heredoc وقتی تمام می‌شود که واژه‌ی جداکننده دوباره در خطی جداگانه و بدون فاصله‌ی خالی در ابتدا یا انتها ظاهر شود.

نحوه‌ی نگارش پایه

command << DELIMITER
  Content line 1
  Content line 2
  ...
  Content line N
DELIMITER

چطور کار می‌کند

  • Bash همه‌ی خطوط بین << DELIMITER شروع و DELIMITER پایانی را می‌خواند.
  • Bash این محتوا را به ورودی استاندارد دستور متصل می‌کند.
  • دستور این ورودی را طوری پردازش می‌کند که گویی از صفحه‌کلید می‌آید.

مثال ۱: خروجی متن ساده

cat << EOF
This is the first line.
This is the second line.
This is the third line.
EOF

خروجی:

This is the first line.
This is the second line.
This is the third line.

در این مثال:

  • cat دستور است.
  • << EOF heredoc را با EOF به عنوان جداکننده شروع می‌کند.
  • سه خط متن، محتوا هستند.
  • EOF در خطی جداگانه، heredoc را پایان می‌دهد.
  • سپس cat محتوایی را که دریافت کرده خروجی می‌دهد.

مثال ۲: استفاده همراه با wc (شمارش واژه)

wc -l << END
Line 1
Line 2
Line 3
END

خروجی:

3

اینجا wc -l تعداد خطوط را می‌شمارد. heredoc آن سه خط را به عنوان ورودی فراهم می‌کند.

مثال ۳: انتقال داده به یک اسکریپت

اسکریپت:

#!/usr/bin/env bash

# Script to process input
while IFS= read -r line; do
  echo "Processing: $line"
done

اسکریپت را از یک اعلان bash تعاملی همراه با heredoc فراخوانی کنید:

./your_script << MY_DATA
Item 1
Item 2
Item 3
MY_DATA

خروجی:

Processing: Item 1
Processing: Item 2
Processing: Item 3

گونه‌ها و ویژگی‌های پیشرفته

محتوای تحت‌اللفظی

Bash درون یک heredoc، بسط متغیر، جایگزینی دستور و بسط حسابی را انجام می‌دهد. از این نظر، heredocها مثل رشته‌های داخل گیومه‌ی دوگانه عمل می‌کنند.

cat << EOF
The value of HOME is $HOME
The current date is $(date)
Two plus two is $((2 + 2))
EOF

خروجی:

The value of HOME is /home/glennj
The current date is Thu Apr 24 13:47:32 EDT 2025
Two plus two is 4

وقتی جداکننده در گیومه باشد (با استفاده از گیومه‌ی تک یا دوگانه)، این بسط‌ها انجام نمی‌شوند. محتوا تحت‌اللفظی در نظر گرفته می‌شود. این مثل رشته‌های داخل گیومه‌ی تک است.

cat << 'EOF'
The value of $HOME is not expanded here.
The result of $(date) is not executed.
Two plus two is calculated by $((2 + 2))
EOF

خروجی:

The value of $HOME is not expanded here.
The result of $(date) is not executed.
Two plus two is calculated by $((2 + 2))

حذف تب‌های ابتدایی

اگر به جای << از <<- (با یک خط تیره در انتها) استفاده کنید، Bash هر نویسه‌ی تب ابتدایی را از هر خط heredoc حذف می‌کند. این برای تورفتگی دادن محتوای heredoc درون اسکریپت، بدون تأثیر بر خروجی، مفید است.

# Note, the leading whitespace is tab characters only, not spaces!
# The ending delimiter can have leading tabs as well.
cat <<- END
	This line has 1 leading tab.
	  This line has a leading tab and some spaces.
		This line 2 leading tabs.
	END

خروجی با حذف همه‌ی تب‌های ابتدایی چاپ می‌شود:

This line has 1 leading tab.
    This line has a leading tab and some spaces.
This line 2 leading tabs.
Caution

نویسنده این کاربرد را توصیه نمی‌کند. هرچند می‌تواند خوانایی اسکریپت را بهتر کند،

  1. به‌راحتی ممکن است به‌طور تصادفی نویسه‌های تب را با فاصله جایگزین کنید (ممکن است ویرایشگرتان این کار را خودکار انجام دهد)، و
  2. تشخیص تفاوت بین فاصله و تب دشوار است.

چه زمانی از here document استفاده کنیم

  • ورودی چندخطی: وقتی لازم است چندین خط متن به یک دستور بدهید.
  • فایل‌های پیکربندی: جاسازی قطعه‌های کوچک پیکربندی درون یک اسکریپت.
  • تولید کد: ساختن کد به‌صورت درجا درون یک اسکریپت.
  • تعامل‌های اسکریپتی: شبیه‌سازی ورودی کاربر برای برنامه‌های تعاملی.
  • پرهیز از فایل‌های بیرونی: وقتی می‌خواهید از ساختن فایل‌های موقت پرهیز کنید.

یک کاربرد معمول می‌تواند فراهم کردن متنی برای راهنما باشد:

#!/usr/bin/env bash

usage() {
    cat << END_USAGE
Refresh database tables.

usage: ${0##*/} [-h|--help] [-A|--no-archive]

where: --no-archive flag will _skip_ the archive jobs
END_USAGE
}

# ... parsing command line options here ...

if [[ $flag_help == "true" ]]; then
  usage
  exit 0
fi

اشکالات احتمالی

  • اسناد جاسازی‌شده‌ی بزرگ می‌توانند خواندن کد شما را سخت‌تر کنند. بهتر است اسکریپتتان را با مستندات در فایل‌های جداگانه توزیع کنید.
  • here documentها می‌توانند جریان کد را بشکنند. ممکن است در بخشی عمیقاً تودرتوی کد باشید که می‌خواهید متنی را به برنامه‌ای بدهید. تورفتگی heredoc در مقایسه با کد اطراف ممکن است ناهماهنگ به نظر برسد.

here string

مثل here documentها، _here string_ها (یا «herestringها») ورودی را به یک دستور می‌دهند. اما در حالی که heredocها به صورت یک بلوک متن داده می‌شوند، herestringها به صورت یک رشته‌ی متنی واحد داده می‌شوند. here stringها از نحوه‌ی نگارش <<< "text" استفاده می‌کنند.

tr 'a-z' 'A-Z' <<< "upper case this string"

خروجی:

UPPER CASE THIS STRING

برخلاف heredocها، نیازی به جداکننده‌ی پایانی نیست.

چرا از here string استفاده کنیم؟

می‌توان به جای here string از یک خط لوله استفاده کرد:

echo "upper case this string" | tr 'a-z' 'A-Z'

خب، چرا از here string استفاده کنیم؟

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

some_long_running_calculation | first_command
some_long_running_calculation | second_command

رویکرد کارآمدتر این است که خروجی محاسبه را (با استفاده از جایگزینی دستور) ذخیره کنید و از here stringها برای دادن ورودی به دو دستور بعدی استفاده کنید:

result=$( some_long_running_calculation )
first_command <<< "$result"
second_command <<< "$result"

این هم یک کاربرد واقعی از آن مثال:

  • پاسخ JSON یک پرس‌وجوی REST API را بگیرید (که صفحه‌بندی شده است)،
  • داده‌ی JSON را به یک برنامه‌ی jq بدهید تا نتایج را تجزیه کند و آن را در فایلی خروجی دهد، و سپس
  • داده‌ی JSON را به برنامه‌ی jq دیگری بدهید تا URL پرس‌وجوی بعدی را مشخص کند.
# initialize the output CSV file
echo "ID,VALUE" > data.csv

url='https//example.com/api/query?page=1'

while true; do
  json=$( curl "$url" )

  # convert the results part of the response into CSV
  jq -r '.results[] | [.id, .value] | @csv' <<< "$json"

  # get the URL for the next page
  url=$( jq -r '.next_url // ""' <<< "$json" )
  if [[ "$url" == "" ]]; then
    break
  fi
done >> data.csv

به موقعیت تغییر مسیر خروجی توجه کنید. تمام خروجی حلقه‌ی while به فایل data.csv افزوده می‌شود.

here documentها و here stringها به عنوان تغییر مسیر

چون این‌ها فقط شکل‌هایی از تغییر مسیر هستند، می‌توان آن‌ها را با سایر عملیات تغییر مسیر ترکیب کرد:

cat <<< END_OF_TEXT > output.txt
This is my important text.
END_OF_TEXT

awk '...' <<< "$my_var" >> result.csv

خلاصه

here documentها (یا «heredocها») روشی انعطاف‌پذیر و راحت برای مدیریت ورودی چندخطی در اسکریپت‌های Bash هستند. فرایند جاسازی متن و داده را مستقیماً درون اسکریپت‌های شما ساده‌تر می‌کنند و آن‌ها را خودکفاتر و خواناتر می‌سازند.

here stringها (یا «herestringها») مثل here documentها هستند، اما نحوه‌ی نگارش ساده‌تر و پویاتری ارائه می‌دهند.

ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود