آموزش‌های خط فرمانی

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

آموزش‌های خط فرمانی

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

ممانعت دوجانبه


چطور می‌توانم مطمئن شوم که فقط یک نمونه از اسکریپت در یک زمان معین در حال اجرا است(ممانعت دوطرفه)؟

برخی وسائل ممانعت دوجانبه را لازم داریم. یک روش، استفاده از "lock" است: هر تعداد از پردازشها می‌توانند به طور همزمان برای بدست آوردن قفل تلاش نمایند، اما فقط یکی از آنها موفق می‌شود.

چگونه می‌توانیم این کار را با استفاده از اسکریپت‌های شل انجام بدهیم؟ بعضی افراد ایجاد یک فایل قفل، و کنترل وجود آنرا پیشنهاد می‌دهند:

  •  # مثال قفل کردن -- اشتباه‎
     lockfile=/tmp/myscript.lock
     if [ -f "$lockfile" ]
     then                      # قفل قبلاً تصرف شده‎
         echo >&2 "cannot acquire lock, giving up: $lockfile"
         exit 0
     else                      # هیچ کس مالک قفل نیست
         > "$lockfile"         # ایجاد فایل قفل‎
         #...ادامه اسکریپت
     fi

این مثال کار نمی‌کند، زیرا این یک Race Condition می‌باشد: یک دریچه زمانی بین بررسی و ایجاد فایل در مدتی که سایر برنامه‌ها ممکن است عمل کنند. فرض کنید دو پردازش در یک زمان در حال اجرای این کُد می‌باشند. هر دو وجود فایل قفل را بررسی می‌کنند، و هر دو نتیجه می‌گیرند که فایل وجود ندارد. حالا هر دو پردازش گمان می‌کنند که قفل را به دست آورده‌اند -- ناکامی در شرف وقوع است. ما به یک روند بررسی و ایجاد اتمی(تجزیه ناپذیر) نیاز داریم ، و خوشبختانه یکی وجود دارد: mkdir، فرمان ایجاد یک دایرکتوری:

  •  # مثال قفل کردن -- صحیح
     # Bourne
     lockdir=/tmp/myscript.lock
     if mkdir "$lockdir"
     then    # دایرکتوری موجود نبود بنابراین با موفقیت ایجاد شد
         echo >&2 "successfully acquired lock: $lockdir"
         # ادامه اسکریپت
     else
         echo >&2 "cannot acquire lock, giving up on $lockdir"
         exit 0
     fi

در اینجا حتی وقتی دو پردازش در یک زمان فرمان mkdir را احضار کنند، فقط حداکثر یک پردازش می‌تواند موفق گردد. این حالت اتمی بررسی و ایجاد در سطح هسته سیستم عامل تأمین می‌شود.

به جای استفاده از mkdir همچنین می‌توانستیم برنامه ایجاد پیوند نمادین را به کار ببریم، ‎ln -s‎. سومین امکان، داشتن برنامه حذفِ فایل قفلِ از قبل موجود با rm است. قفل با ایجاد مجدد فایل هنگام خروج آزاد می‌شود.

توجه نمایید که نمی‌توانیم از ‎mkdir -p‎ برای ایجاد خودکار اجزا غایب تشکیل دهنده مسیر استفاده کنیم: اگر دایرکتوری از قبل موجود باشد ‎mkdir -p‎ خطایی صادر نمی‌کند، بلکه خصیصه‌ایست که ما برای اطمینان از ممانعت دوطرفه به آن استناد می‌کنیم(مترجم: مقصود، ویژگی اتمی بودن mkdir است).

حالا بیایید خودکار نمودن حذف قفل هنگام خاتمه یافتن اسکریپت را چاشنی این مثال کنیم:

  •  # POSIX (maybe Bourne?)
     lockdir=/tmp/myscript.lock
     if mkdir "$lockdir"
     then
         echo >&2 "successfully acquired lock"
    
         #  حذف می‌شود lockdir ،وقتی اسکریپت به پایان می‌رسد یا موقع دریافت سیگنال ‎
         trap 'rm -rf "$lockdir"' 0    # حذف دایرکتوری موقع پایان یافتن اسکریپت‎
    
         # به طور اختیاری فایلهای موقتی در این دایرکتوری ایجاد می‌شود، چون‎
         #                              .آنها به طور خودکار حذف خواهند شد
         tmpfile=$lockdir/filelist
    
     else
         echo >&2 "cannot acquire lock, giving up on $lockdir"
         exit 0
     fi

این مثال خیلی بهتر است. بازهم این مشکل وجود دارد که وقتی اسکریپت بدون یک سیگنال اخذ شده،(یا سیگنال 9، SIGKILL) خاتمه می‌یابد، قفل کهنه‌ای می‌تواند باقی‌ بماند، یا می‌تواند توسط کاربری(به طور تصادفی یا بدخواهانه) ایجاد بشود، اما این قدم خوبی در جهت ممانعت دوطرفه است. Charles Duffy یک مثال همکارانه دارد که ممکن است مشکل «قفل کهنه» را برطرف نماید .

اگر در لینوکس هستید، می‌توانید از کاربرد‎ flock(1)‎ نیز بهره‌مند شوید. ‎ flock(1)‎ توصیف‌گر فایلی را به یک فایل قفل متصل می‌کند. روشهای چندگانه‌ای برای استفاده از آن موجود است، یک امکان حل مشکلِ نمونه‌های چندتایی، این است:

  •   exec 9>/path/to/lock/file
      if ! flock -n 9  ; then
         echo "another instance is running";
         exit 1
      fi
      #    اکنون این تحت قفل تا بسته شدن 9 اجرا می‌شود‎
      #   (وقتی اسکریپت تمام می‌شود  9 به طور خودکار بسته می‌شود)

flock همچنین می‌تواند فقط قسمتی از اسکریپت شما را محافظت کند، برای اطلاعات بیشتر صفحه manرا ببینید.

مباحثه

من معتقدم استفاده از ‎ if (set -C; >$lockfile); then ...‎ کاملاً مطمئن است، اگر که مطمئن‌تر نباشد. منبع Bash از ‎ open(filename, flags|O_EXCL, mode);‎ استفاده می‌کند که تقریباً در تمام سکوها تجزیه‌ناپذیر خواهد بود(به استثنای برخی نگارشهای NFS که در آنها mkdir هم نمی‌تواند اتمی باشد). من نه مسیر نشانه‌های متغیر را دنبال کرده‌ام، که باید محتوی O_CREAT باشند، نه در پوسته‌های دیگر رسیدگی نموده‌ام. من استفاده از این را تا موقعی که شخص دیگری ادعای مرا پشتیبانی نکند، پیشنهاد نمی‌کنم. --Andy753421

  • استفاده از کُدهای ‎set -C‎ با ksh88 کار نمی‌کند. Ksh88 موقعی که شما ‎ ‎noclobber (-C)‎ را تنظیم کنید از O_EXCL استفاده نمی‌کند. --jrw32982

    شما اطمینان دارید که mkdir با اتمی شدن روی NFS مشکل دارد؟ من گمان می‌کنم فقط تحت تأثیر بازشدن واقع شده، اما واقعاً مطمئن نیستم. -- BeJonas 2008-07-24 01:22:59

برای مباحثه بیشتر در باره این موضوعات، مدیریت پردازش را ببینید.


CategoryShell

پرسش و پاسخ 45 (آخرین ویرایش ‎2013-01-11 20:08:22‎ توسط GreyCat)


ایجاد نوار پیشروی


چطور می‌توانم نوار پیشروی ایجاد کنم؟ چگونه می‌توانم موقع کپی یا انتقال فایلها، نمایشگر پیشروی را ببینم؟

آسان‌ترین روش افزودن نوار پیشرفت به اسکریپت خودتان، استفاده از ‎ dialog --gauge‎ است. در اینجا مثالی آورده‌ایم که متکی به ویژگی‌های BASH می‌باشد:

# Bash
# .در شاخه جاری را پردازش می‌کند *.zip تمام فایلهای
files=(*.zip)
dialog --gauge "Working..." 20 75 < <(
   n=${#files[*]}; i=0
   for f in "${files[@]}"; do
   # قرار بدهید "sleep 1"‎ را قرار بدهید، برای تست ‎"$f"‎ در اینجا دستورات پردازش ‎
      echo $((100*(++i)/n))
   done
)

این هم توضیح آن که چکار می‌کند:

  • یک آرایه به نام files با تمام فایلهایی که می‌خواهیم پردازش کنیم، دارای عضو می‌شوند.

  • dialog فراخوانی می‌شود، و ورودی آن به جایگزینی پردازش تغییر مسیر داده می‌شود. ( لوله نیز می‌توانست در اینجا به کار برود، ما می‌بایست به سادگی فرمان dialog و حلقه را برعکس می‌نمودیم.)

  • پردازش حلقه روی آرایه تکرار می‌شود.
  • هر نوبت که فایلی پردازش می‌شود، شمارشگر(i) را افزایش می‌دهد، و درصد تکمیل را در خروجی می‌نویسد.

برای مثالهای بیشتری از کاربرد dialog، پرسش و پاسخ شماره 40 را ببینید.

نوار پیشرفت ساده‌ای نیز می‌تواند بدون استفاده از dialog برنامه ریزی بشود. رویکردهای متفاوت زیادی برای این مورد وجود دارد که بستگی به نوع نمایشی دارد که شما در جستجوی آن می‌باشید.

یک رویکرد سنتی چرخنده است که یک پاره خط در حال دوران را برای دلالت بر مشغول بودن نمایش می‌دهد. این در اصل میزان پیشرفت نیست چون اطلاعاتی در مورد آنکه چطور برنامه به تکمیل شدن نزدیک می‌شود، ارائه نمی‌کند.

مرحله بعدی نمایش یک مقدار عددی بدون حرکت در صفحه نمایش است. استفاده از سطر نورد برای حرکت نشانگر جهت تبدیل شدن به یک سطر(در ترمینال گرافیکی، نه یک دورنگار...) و ننوشتن سطر جدید تا به پایان رسیدن پردازش:

i=0
while ((i < 100)); do
  printf "\r%3d%% complete" $i
  ((i += RANDOM%5+2))
  # را از جایی معنی‌دار دریافت می‌کنیم i البته در زندگی واقعی، ما‎
  sleep 1
done
echo

نکته در اینجا تعیین کننده قالب ‎%3d‎ در printf است. استفاده از فیلد با عرض ثابت برای نمایش اعداد اهمیت دارد، مخصوصاً اگر شمارش معکوس انجام شود(اول 10 و بعد 9 نمایش داده شود). البته ما در اینجا شمارش رو به بالا انجام دادیم، اما به طور عمومی همیشه به این حالت نیست. اگر فیلد با عرض ثابت مطلوب نیست، آنوقت چاپ یک گروه فاصله در انتها می‌تواند به زدودن هر گونه درهم‌برهمی با سطرهای قبلی کمک کند.

اگر بجای یک عدد، یک نوار واقعی مورد نظر باشد، آنوقت شاید شخصی با استفاده از کاراکترهای اسکی آن را رسم کند:

bar="=================================================="
barlength=${#bar}
i=0
while ((i < 100)); do
  # .تعداد قطعات نوار برای رسم کردن
  n=$((i*barlength / 100))
  printf "\r[%-${barlength}s]" "${bar:0:n}"
  ((i += RANDOM%5+2))
  # را از جایی معنی‌دار دریافت می‌کنیم i البته در زندگی واقعی، ما‎
  sleep 1
done
echo

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

files=(*)
width=${COLUMNS-$(tput cols)}
rev=$(tput rev)

n=${#files[*]}
i=0
printf "$(tput setab 0)%${width}s\r"
for f in "${files[@]}"; do
   # قرار بدهید "sleep 1"‎ را قرار بدهید، برای تست ‎"$f"‎ در اینجا دستورات پردازش 
   printf "$rev%$((width*++i/n))s\r" " "
done
tput sgr0
echo

هنگام کپی یا انتقال فایلها

نمی‌توانید با‎ cp(1)‎ نمایشگر پیشروی به دست آورید، اما شما می‌توانید :

  • یا خودتان با ابزاری از قبیل pv یا clpbar یکی بسازید

  • یا ابزار دیگری به کار ببرید، به عنوان مثال vcp.

شاید بخواهید از ‎ pv(1)‎ استفاده کنید چون برای بسیاری از سیستم‌ها به صورت بسته‌بندی شده موجود است. در این حالت، اگر تابع یا اسکریپتی برای پوشاندن آن ایجاد نمایید، مناسب است.

برای مثال:

pv "$1" > "$2/${1##*/}"

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

همچنین می‌توانید از rsync استفاده کنید:

rsync -avx --progress --stats "$1" "$2"

لطفاً توجه نمایید هر دفعه که rsync وارد یک دایرکتوری می‌شود و فایلهای کمتر یا بیشتر از مورد انتظارش پیدا می‌کند، تعداد کل فایلها می‌تواند تغییر نماید، اما حداقل بیش از cp مطلع است. پیشرفت Rsync برای انتقالهای بزرگ با تعداد فایل کم مناسب است.


CategoryShell

پرسش و پاسخ 44 (آخرین ویرایش ‎ 2010-06-25 20:12:14 ‎ توسط MatthiasPopp)


پرسش و پاسخ شماره ۴۳


چرا این job من در crontab ناموفق است؟
0 0 * * * some command > /var/log/mylog.`date +%Y%m%d`‎

در بسیاری از نگارش‌های crontab، با علامت‎ (%)‎ به طور ویژه‌ای رفتار می‌شود، و بنابراین باید با کاراکتر گریز \ پوشش داده شود:

0 0 * * * some command > /var/log/mylog.`date +\%Y\%m\%d`

برای توضیحات بیشتر مستندات سیستم خود را ببینید(‎crontab(5)‎ یا ‎crontab(1)‎). توجه: در سیستم‌هایی که راهنمای crontab را به دو قسمت تقسیم می‌کنند، ممکن است شما برای خواندن بخش مورد نیاز خود لازم باشد ‎man 5 crontab‎ یا ‎man -s 5 crontab‎ را تایپ کنید.


CategoryShell

پرسش و پاسخ 43 (آخرین ویرایش ‎ 2010-06-25 20:11:42 ‎ توسط MatthiasPopp)


آیا پردازش در حال اجرا می‌باشد؟


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

فرمان kill برای ارسال سیگنالها به پردازش در حال اجرا به کار می‌رود. به عنوان یک عمل راحت، سیگنال "0" که موجود نیست می‌تواند برای پی بردن به در حال اجرا بودن یک پردازش به کار برود:

# Bourne
myprog &          # برنامه را در پس زمینه آغاز می‌کند
daemonpid=$!      # ...و شماره پردازش آن را ذخیره می‌کند

while sleep 60
do
    if kill -0 $daemonpid       # آیا پردازش هنوز زنده است؟
    then
        echo >&2 "OK - process is still running"
    else
        echo >&2 "ERROR - process $daemonpid is no longer running!"
        break
    fi
done

توجه: هر آنچه شمابرای شناسایی پردازشی که به طور ذاتی معیوب گردیده، انجام دهید به شماره شناسایی پرازشها(PID) متکی می‌باشد. اگر پردازشی بمیرد، به معنای آنست که PID آن تعریف نشده است. پردازش دیگری که بعد از آن آغاز می‌شود می‌تواند همان PID پردازش مرده را بگیرد. این موضوع می‌تواند باعث شود مثال قبلی فکر کند پردازش هنوز زنده است(شماره شناسایی آن وجود دارد!) ولواینکه آن پردازش مرده و از بین رفته است. به همین دلیل است که هیچ کس دیگری غیر از والد پردازش نباید سعی در مدیریت آن پردازش نماید. بخش مدیریت پردازش را بخوانید.

این یکی از آن پرسشهایی است که معمولاً مسئله عمیق‌تری را پوشش می‌دهد. نادر است که شخصی بخواهد بداند آیا پردازشی هنوز واقعاً در حال اجرا می‌باشد برای اینکه چراغ سبز یا قرمزی را برای متصدی روشن کند.

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

# POSIX
while true
do
  myprog && break
  sleep 1
done

این قطعه کُد برنامه myprog را اگر با کُد وضعیت دیگری غیر از صفر خاتمه یابد(نشان دهنده وجود یک اشتباه)، شروع مجدد (restart) می‌کند. اگر کُد وضعیت صفر است(به طور موفقیت آمیز تمام شود) حلقه پایان می‌یابد. (اگر پردازش شما سقوط می‌کند اما کد وضعیت صفر نیز برمی‌گرداند، پس کُد را طبق آن تنظیم کنید.) توجه نمایید که myprog باید در پیش زمینه اجرا شود. اگر به طور خودکار خودش را به حالت daemon می‌برد، شما مست هستید.

برای مباحثه بسیار بهتر در باره این مسائل مدیریت پردازش یا پرسش و پاسخ شماره 33 را ببینید.


CategoryShell

پرسش و پاسخ 42 (آخرین ویرایش ‎ 2012-10-27 10:38:13 ‎ توسط a88-114-128-29)


پرسش و پاسخ شماره ۴۱



چطور می‌توانم تعیین نمایم که آیا یک متغیر شامل یک زیر رشته هست؟

در BASH:

  # Bash
  if [[ $foo = *bar* ]]

مورد فوق کمابیش در تمام نگارشهای Bash کار می‌کند. Bash نگارش 3 (و بالاتر) همچنین عبارتهای منظم را هم مجاز می‌داند:

  # Bash
  my_re='ab*c'
  if [[ $foo =~ $my_re ]]   # bash 3, matches abbbbcde, or ac, etc.

برای اشارات بیشتر در مورد دستکاری رشته‌ها در Bash، پرسش و پاسخ شماره 100 را ببینید.

اگر شما به جای Bash در BourneShell برنامه نویسی می‌کنید، ترکیب دستوری قابل حمل‌تر(اما نا زیباتری) وجود دارد:

  # Bourne
  case "$foo" in
    *bar*) .... ;;
  esac

case به شما اجازه می‌دهد متغیرها را در مقابل الگوهای globbingشکل (به انضمام globهای توسعه یافته) در صورتی که پوسته شما آنها را ارائه می‌کند،مطابقت دهید. اگر شما به روش قابل حمل برای مطابقت متغیرها در مقابل عبارات منظم نیاز دارید، از grep یا egrep استفاده کنید.

  # Bourne
  if echo "$foo" | grep bar >/dev/null 2>&1; then ...


CategoryShell

پرسش وپاسخ 41 (آخرین ویرایش ‎ 2010-11-29 22:24:01 ‎ توسط GreyCat)