مقدمه

در درس گذشته با ساختار شرطی if، به اسکریپت‌های خود قدرت تصمیم‌گیری بخشیدیم. حال به یکی دیگر از مفاهیم بنیادی برنامه‌نویسی می‌پردازیم: حلقه‌ها (Loops). اگر بخواهیم یک کار مشخص را چندین بار تکرار کنیم، مثلاً نام ۱۰۰ فایل را تغییر دهیم یا به ۲۰۰ سرور متصل شویم، آیا باید دستور مربوطه را به همان تعداد در اسکریپت خود کپی کنیم؟ قطعاً نه. حلقه‌ها برای حل همین مشکل به وجود آمده‌اند.

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

  • حلقه‌ی for: برای تکرار روی یک لیست مشخص از آیتم‌ها (مانند فایل‌ها، کاربران یا سرورها).
  • حلقه‌ی while: برای تکرار تا زمانی که یک شرط خاص برقرار باشد.

حلقه‌ی for: تکرار روی یک لیست

کاربرد اصلی حلقه‌ی for، اجرای یک سری دستور به ازای هر آیتم در یک لیست است. ساختار کلی آن به شکل زیر است:

for variable in item1 item2 item3 ...
do
  # commands to execute
  # here we can use $variable
done

تکرار روی فایل‌ها

یکی از قدرتمندترین کاربردهای حلقه‌ی for، انجام عملیات روی مجموعه‌ای از فایل‌ها با استفاده از wildcardها (مانند *) است. اسکریپت زیر تمام فایل‌های با پسوند .jpg در دایرکتوری فعلی را پیدا کرده و آن‌ها را به یک دایرکتوری پشتیبان منتقل می‌کند.

#!/bin/bash

mkdir -p ./backups

for img_file in *.jpg
do
  echo "Moving $img_file to backups directory..."
  mv "$img_file" ./backups
done

echo "All JPEG files moved."

توجه کنید که چگونه نام فایل در هر بار تکرار حلقه، در متغیر img_file قرار می‌گیرد و ما می‌توانیم در بدنه‌ی حلقه از آن استفاده کنیم.

حلقه‌ی while: تکرار تا زمانی که شرط برقرار است

برخلاف حلقه‌ی for که روی یک لیست متناهی اجرا می‌شود، حلقه‌ی while یک بلوک کد را تا زمانی که یک شرط خاص برقرار باشد، تکرار می‌کند. این حلقه از همان ساختار شرطی [[ ... ]] که در درس if یاد گرفتیم، استفاده می‌کند.

while [[ condition ]]
do
  # commands to execute
done

یک شمارنده‌ی ساده

مثال کلاسیک برای حلقه‌ی while، یک شمارنده است. اسکریپت زیر اعداد ۱ تا ۵ را چاپ می‌کند:

#!/bin/bash

counter=1

while [[ $counter -le 5 ]]
do
  echo "Count: $counter"
  ((counter++))  
done

در این مثال، حلقه‌ی while تا زمانی که مقدار متغیر counter کوچکتر یا مساوی ۵ باشد، ادامه پیدا می‌کند. خط ((counter++)) حیاتی است؛ این دستور که یک ساختار محاسباتی در شل است، مقدار شمارنده را در هر بار تکرار یکی افزایش می‌دهد. اگر این خط وجود نداشت، شرط حلقه همیشه برقرار بود و ما با یک حلقه‌ی بی‌نهایت مواجه می‌شدیم.

خواندن فایل به صورت خط به خط

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

اسکریپت زیر یک فایل به نام servers.txt را می‌خواند و به ازای هر خط (که نام یک سرور است) یک پیام چاپ می‌کند:

#!/bin/bash

FILENAME="servers.txt"

if [[ ! -f "$FILENAME" ]]; then
  echo "Error: File '$FILENAME' not found."
  exit 1
fi

while IFS= read -r line
do
  echo "Pinging server: $line ..."
  ping -c 1 "$line"
done < "$FILENAME"

echo "Finished processing all servers."

کالبدشکافی حلقه‌ی خواندن فایل

عبارت while IFS= read -r line یک اصطلاح استاندارد و بسیار قوی برای خواندن فایل در شل است:

  • done < "$FILENAME" : این بخش با استفاده از تغییر مسیر ورودی، محتوای فایل $FILENAME را به ورودی استاندارد کل حلقه‌ی while هدایت می‌کند.
  • read -r line: دستور read از این ورودی استاندارد، یک خط خوانده و آن را در متغیر line قرار می‌دهد. گزینه‌ی -r از تفسیر کاراکتر بک‌اسلش جلوگیری می‌کند. وقتی دیگر خطی برای خواندن وجود نداشته باشد، read با کد خروج غیر صفر پایان می‌یابد و شرط while نقض شده و حلقه تمام می‌شود.
  • IFS=: این بخش کمی پیشرفته‌تر است و برای جلوگیری از حذف ناخواسته‌ی فاصله‌ها و tabهای ابتدا و انتهای خط به کار می‌رود.