راه حل اغوا کننده، استفاده از ls برای برونداد نام فایلهای موجود، و اخذ اولین نتیجه، میباشد. به طور عادی رویکرد ls نمیتواند مناسب باشد و به خاطر احتمال برخورد با کارکترهای اختیاری(شامل سطر جدید) موجود در نام فایلها، هرگز نباید در اسکریپتها به کار برود. بنابراین، به شیوههای دیگری برای سنجش فوق دادههای(metadata) فایل، نیاز داریم.
بیشترین مورد نیاز، برای یافتن قدیمترین و جدیدترین فایل ویرایش شده در دایرکتوری جاری، میباشد. Bash و همه نگارشهای متنوع ksh میتوانند زمانهای ویرایش(mtime) را با استفاده از عملگرهای -nt و -ot در عبارت شرطی دستور مرکب، مقایسه کنند:
# Bash/ksh unset -v latest for file in "$dir"/*; do [[ $file -nt $latest ]] && latest=$file done
یا برای یافتن قدیمیترین:
# Bash/ksh unset -v oldest for file in "$dir"/*; do [[ -z $oldest || $file -ot $oldest ]] && oldest=$file done
به خاطر داشته باشید که mtime در مورد دایرکتوریها، مربوط به آن شاخهای است، که آخرین افزودن، حذف یا تغییر نام فایل در آن انجام شده است. همچنین توجه کنید که -nt و -ot توسط POSIX test تعریف نشدهاند، هر چند بسیاری از پوستهها ازقبیل dash به طریقی آنها را در خود دارند. شلهای غیرهمگروه با پوسته bourne، دارای عملگرهای مشابهی برای مقایسه atime یا ctime میباشند، همچنین شاید برای آنها نیاز به برنامههای خارجی باشد، به هرحال، فراهم نمودن خروجی، که بتواند به طور سالم تجزیه شود، یا مدیریت خروجی مذکور، بدون استفاده از ویژگیهای غیراستاندارد، تقریباً در شل غیر ممکن است .
اگر معیار مرتبسازی غیر از «قدیمیترین» و «جدیدترین» فایل باشد، ممکن است فرمان find و sort گنو، همراه باهم برای تولید لیست مرتب شدهای از نام فایلها و نشانههای زمان( timestamps)، که با کاراکترهای تهی جدا شدهاند، به کاربرود. البته، این به طور بازگشتی عمل میکند(عملگر -maxdepth فرمان find گنو میتواند از آن پیشگیری کند)، این هم تعدادی از امکاناتی که در صورت لزوم میتواند برای استفاده atime یا ctime، یا مرتبسازی معکوس، ویرایش بشوند:
# Bash + GNU find + GNU sort (To the precision possible on the given OS, but returns only one result) IFS= read -r -d '' latest \ < <(find "$dir" -type f -printf '%T@ %p\0' | sort -znr) latest=${latest#* } # remove timestamp + space
# GNU find + Bash w/ arrays (To the nearest 1s, using an undocumented "find -printf" format (%Ts).) while IFS= read -rd '' 'latest[$(read -rd "" y; echo $y)]' do : done < <(find "$dir" -type f -printf '%p\0%Ts\0') latest=${latest[-1]}
# GNU stat + Bash /w arrays (non-recursive w/o globstar, to the nearest 1s) while IFS= read -rd '' 'latest[$(read -rd "" y; echo $y)]' do : done < <(stat '--printf=%n\0%Y\0' "$dir"/*) latest=${latest[-1]}
یکی از اشکالات این رویکردها آنست که تمام لیست ذخیره میشود، در حالیکه حقیقتاً تکرار روی تمام لیست برای یافتن نشانه زمان(timestamp) حداقل و حداکثر(با فرض آنکه فقط یک فایل را میخواهیم) سریعتر خواهد بود، به هرحال، بسته به اندازه کار اشکال الگوریتم مرتبسازی در مقایسه با دردسر استفاده از یک پوسته شاید قابل گذشت باشد.
# Bash + GNU find unset -v latest time while IFS= read -r -d '' line; do t=${line%% *} t=${t%.*} # truncate fractional seconds ((t > time)) && { latest=${line#* } time=$t; } done < <(find "$dir" -type f -printf '%T@ %p\0')
خوانندگانی که این سؤال را به منظور معکوس نمودن ترتیب فایلهای ثبت وقایع(log) خود پرسیدهاند شاید در عوض مایل باشند به logrotate(1) نگاه کنند، اگر سیستم عامل آنها آنرا فراهم نموده باشد.
پرسش و پاسخ 3 (آخرین ویرایش 2012-07-19 04:03:35 توسط ormaaj)
خوب، بستگی دارد به اینکه آیا میخواهید خروجی فرمان را ذخیره کنید(هر یک از stdout یا stdout + stderr) یا وضعیت خروج آن(0 تا 255، به طور نوعی در ازای 0 به معنی موفقیت).
اگر میخواهید خروجی را تصرف نمایید، از جایگزینی فرمان استفاده کنید:
output=$(command) # stdout only; stderr remains uncaptured output=$(command 2>&1) # both stdout and stderr will be captured
اگر وضعیت خروج را میخواهید، از پارامتر ویژه $? بعد از اجرای فرمان استفاده نمایید:
command status=$?
اگر هر دو را میخواهید:
output=$(command) status=$?
تخصیص به output تأثیری بر وضعیت خروج command، که بازهم در متغیر $? است، ندارد.
اگر در واقع نمیخواهید وضعیت خروج را ذخیره کنید، اما میخواهید بر مبنای موفقیت یا شکست، عملی انجام شود، فقط از if استفاده نمایید:
if command; then echo "it succeeded" else echo "it failed" fi
یا اگر میخواهید خروجی استاندارد را ذخیره کنید و بدون ذخیره نمودن یا بررسی $? به طور صریح، عملی نیز نسبت به موفقیت یا شکست، انجام شود، از این:
if output=$(command); then echo "it succeeded" ...
اگر وضعیت خروج یک فرمان از خطلوله را میخواهید، چطور؟ اگر وضعیت خروج آخرین فرمان را میخواهید، مشکلی نیست-- در متغیر $? است، درست مثل قبل. اگر وضعیت خروج بعضی از فرمانهای دیگر را میخواهید، از آرایه PIPESTATUS (منحصر به BASH )استفاده کنید. بر فرض که شما وضعیت خروج فرمان grep در کد زیر را میخواهید:
grep foo somelogfile | head -5 status=${PIPESTATUS[0]}
Bash نگارش 3.0 گزینه pipefail را نیز اضافه نموده ، که اگر شما واقعاً میخواهید در نتیجه شکست grep عملی انجام شود، میتواند استفاده شود:
set -o pipefail if ! grep foo somelogfile | head -5; then echo "uh oh" fi
حالا، چند مورد زیرکانه. بیایید فرض کنیم فقط خروجی استاندارد خطا را میخواهید، اما خروجی استاندارد را نمیخواهید. خوب، پس اول باید تصمیم بگیرید که میخواهید خروجی استاندارد به کجا برود:
output=$(command 2>&1 >/dev/null) #.خروجی خطا ذخیره و خروجی صرفنظر میشود output=$(command 2>&1 >/dev/tty) #. ذخیره و خروجی به ترمینال ارسال میشود stderr output=$(command 3>&2 2>&1 1>&3-) #.اسکریپت میرودstderr ذخیره و خروجی به stderr
اگر چه به طور قابل ملاحظهای دشوارتر، اما برگشت دادن خروجی به جایی که بدون وجود تغییر مسیر میرفت، امکان پذیر است. این ذخیره محتوای فعلی خروجیاستاندارد را در بر میگیرد، به طوری که میتواند داخل جایگزینی فرمان به کار برده شود:
exec 3>&1 # به آن اشاره میکند stdout(1) ذخیره مکانی که output=$(command 2>&1 1>&3) # ذخیره میشود stderr .اجرای فرمان exec 3>&- # شماره 3 FD بستن # :را با اجازه عبور به خروجی از میان آن، ذخیره میکند stderr یا این جایگزین که { output=$(command 2>&1 1>&3-) ;} 3>&1
توجه نمایید، که در آخرین مثال فوق 1>&3- توصیفگر فایل 3 را دو نسخهای میکند و یک نسخه از آن را در FD شماره 1 ذخیره مینماید، و FD شماره 3 را میبندد. که به این شکل نیز میتواند نوشته شود 1>&3 3>&-.
آنچه که نمیتوانید انجام بدهید، ذخیره خروجی استاندارد در یک متغیر و stderr در دیگری، با استفاده ازتغییر مسیر FD به تنهایی است. شما باید از یک فایل موقت(یا لوله با نام) برای رسیدن به آن یک، استفاده نمایید.
خوب، میتوانید یک شکاف ناخوشآیند(مترجم: منظور جمله« ... this line is » میباشد) به کار ببرید:
result=$( { stdout=$(cmd) ; } 2>&1; echo "this line is the separator"; echo "$stdout") var_out=${result#*this line is the separator$'\n'} var_err=${result%$'\n'this line is the separator*}
به طور واضح، این قوی نیست، به دلیل آنکه هم خروجی استاندارد و هم خطای استاندارد فرمان میتوانند شامل هر تعداد جداکننده رشتهای که شما به کار بردهاید، باشند.
و اگر شما کُد وضعیت cmd خود را خواسته باشید(این اصلاح برای حالتی است که اگر خروجی cmd تهی باشد)
cmd() { curl -s -v http://www.google.fr; } result=$( { stdout=$(cmd); returncode=$?; } 2>&1; echo -n "this is the separator"; echo "$stdout"; exit $returncode) returncode=$? var_out=${result#*this is the separator} var_err=${result%this is the separator*}
یادداشت: پرسش اصلی، «چگونه میتوانم مقدار برگشتی دستوری را در متغیری ذخیره کنم؟» بود. این کلمه به کلمه یک پرسش واقعی، پرسیده شده در #bash است، مبهم و کلی.
پرسش و پاسخ 2 (آخرین ویرایش 2012-12-04 09:50:10 توسط geirha)
استفاده از "for" را امتحان نکنید . از یک حلقه while و فرمان read استفاده کنید:
while read -r line do echo "$line" done < "$file"
ترجمه راهنمای BashGuide به پایان رسید، و در ضمن به صورت یک فایل pdf نیز فراهم گردیده، که قابل دریافت میباشد، اما همانگونه که قبلاً اشاره شد، این راهنما بخشی از یک Wiki است، که گستردهتر از این راهنما میباشد.
در شروع ترجمه این راهنما به صورت یادداشتهای ادامهدار، نوشتم که پس از تکمیل ترجمه راهنما احتمالاً به ترجمه سایر بخشها خواهم پرداخت. اکنون زمان اجرای آن تعهد ضمنی فرا رسیده است، و من برای ادامه، بخش «پرسش و پاسخهای رایج Bash» از این Wiki را انتخاب نمودهام که به همان روال سابق به صورت یاداشتهای متوالی در اینجا قرار خواهم داد.
سعی خواهد شد در هر یادداشت یک پرسش و پاسخ به طور کامل ارایه شود، مگر در مواردی که پاسخ خیلی طولانی و خارج از حوصله یک یاداشت باشد، که البته مواردی اینچنین نیز وجود دارد.
اکنون در ادامه، فهرست پرسش و پاسخها را ملاحظه مینمایید.
من ترجیح میدهم پرسش و پاسخها را به ترتیب ارائه نمایم زیرا نگاه من به این ترجمهها بیشتر یک نگاه بلندمدت است، نه رفع نیاز فوری، بنابراین چنین عمل خواهم نمود، مگر آنکه درخواستهای چندگانهای برای خارج از نوبت قرار دادن برخی از پرسشها وجود داشته باشد.
خیلی وقتها، خودتان را مستأصل میبینید که چرا، اسکریپت شما آنگونه عمل نمیکند، که شما میخواهید. حل این مسئله همواره، موضوع درک عمومی و شیوههای اشکالیابی است.
|
تشخیص مشکل |
|
بدون آنکه دقیقاً بدانید مشکل چیست، به احتمال بسیار زیاد، خیلی زود نمیتوانید چارهسازی نمایید. بنابراین مطمئن شوید، که به طور دقیق میدانید چه چیز اشتباه است. علائم و پیغامهای خطا را بررسی و ارزیابی کنید.
سعی کنید مشکل را به صورت یک جمله با قاعده بیان کنید. چون اگر بخواهید از دیگران در حل مشکل کمک بگیرید نیز، این کار خیلی ضروری میباشد. شما که نمیخواهید آنها تمام اسکریپت شما را بازنویسی نمایند، همینطور هم نمیخواهید آنها سرتاسر اسکریپت شما را بازبینی یا آنرا اجرا کنند تاببینند که چه مشکلی پیش میاید. نه،
|
حداقلسازی کد اصلی |
|
اگر اشکالیابی اسکریپت را شروع میکنید، به خودتان الهام خدایی اهدا نکنید، مورد دیگری که باید انجام دهید، کوشش جهت حداقلسازی کد اصلی برای مجزا نمودن مسئله میباشد.
نگران حفظ توانایی اسکریپت خود نباشید. تنها چیزی که باید باقی نگاه دارید، منطق قطعه کد اصلی است، که به نظر مشکل آفرین میباشد.
اغلب، بهترین روش آنست که اسکریپت خود را در یک فایل جدید کپی نموده و شروع به حذف نمودن هر آن چیزی که به نظر میرسد نامربوط است، بنمایید. به طور جایگزین، میتوانید یک اسکریپت جدید که کاری مشابه همان کد انجام میدهد، بسازید، و ساختار را تا ایجاد دوباره مشکل ادامه دهید.
به مجرد اینکه، موردی که مشکل ایجاد نموده را حذف کردید، دست بکشید(یا موردی که اضافه نمودنش دوباره آن مشکل را ظاهر میکند)، شما کشف کردهاید که مشکل در کجا قرار دارد. حتی اگر به طور دقیق به آن نرسیدهاید، حداقل دیگر به یک اسکریپت حجیم خیره نمیشوید، بلکه امیدوارانه، با کوتولهای نه بیش از 3 تا 7 سطر، مواجه هستید.
برای مثال، اگر اسکریپتی دارید که باز کردن فایلهای تصویری موجود در شاخه image را برحسب تاریخ برای شما انجام میدهد، و بنا به دلایلی، تکرار روی فایلهای دایرکتوری را نمیتوانید به طور صحیح پیش ببرید، کافی است اسکریپت را تا اندازه این قطعه کُد کاهش بدهید:
for image in $( ls-R "$ imgFolder"); do echo "$ image "done
اسکریپت واقعی شما به مراتب پیچیدهتر از این خواهد بود، و درون حلقه
ما نمیتوانیم glob بازگشتی به کار ببریم(مگر در bash نگارش 4)، بنابراین باید دستور find را برای به دست آوردن نام فایلها به کار بگیریم. یک راه اصلاح آن، چنین خواهد بود:
find "$ imgFolder "-print0 | while IFS = read-r -d ''image ; do echo "$ image "done
اکنون که مشکل را در این مثال کوچک برطرف نمودهاید، برگشتن و ترکیب کردن آن با اسکریپ اصلی آسان است.
|
فعال نمودن وضعیت اشکالیابی BASH |
|
اگر بازهم خطای روشهایتان را نمیبینید، شاید وضعیت اشکالیابی BASH برای دیدن مشکل در میان کُد به شما کمک نماید.
موقعی که BASH با گزینه
سه روش برای فعال کردن این وضعیت موجود است.
اجرای اسکریپت به صورت bash
$ bash-x ./mybrokenscript
#! /bin/bash-x [.. script ..]
#! /usr/bin/env bash set-x
یا اضافه نمودن set
[..کدهای بی ارتباط..] set-x [..قطعه کد مرتبط..] set+x [..کدهای بی ارتباط..]
اگر set
# را در یک فایل کپی میکند set -x بخش # با یک نام فایل به عنوان 1$ آنرا فعال میکند # اگر پارامتری وجود نداشته باشد آن را غیر فعال میکند # شماره 4 نباید در جای دیگری از اسکریپت استفاده شده باشد fd setx_output() { if [[ $ 1 ]] ; then exec4 >> "$ 1"BASH_XTRACEFD = 4 set-x else set+x exec4 >& -fi }
اگر اسکریپتهای پیچیده و آشفتهای دارید، شاید تغییر محتوی متغیر PS4 قبل از برقراری اشکالیابی با
exportPS4 = '+$ BASH_SOURCE :$ LINENO :$ FUNCNAME : '
|
Step your code |
|
اگر خروجی اشکالیابی از نظر شما خیلی سریع عبور میکند، میتوانید کُد-مرحلهای را فعال کنید. کُد زیر از DEBUG دستور trap برای اطلاع به کاربر در باره دستوری که اجرا خواهد شد و انتظار برای تایید پیشرفت، استفاده میکند. این کُد را در محلی از اسکریپت خود که میخواهید مرحلهای بشود، قرار دهید:
trap '(read ' DEBUG-p "[$BASH_SOURCE :$LINENO ] $BASH_COMMAND ?")
|
اشکالزدای BASH |
|
پروژه اشکالزدای Bash یک اشکالیاب gdb-مانند، در آدرس /http://bashdb.sourceforge.net است.
اشکالیاب فوق به شما کمک میکند در سرتاسر اسکریپت حرکت نموده و اشکالهای آن را پیگردی و پیدا کنید.
|
بازخوانی مستندات |
|
اگر هنوز به نظر میرسد اسکریپت برایتان قابل قبول نیست، شاید ادراک شما از روش انجام کارها اشتباه است. به مستندات(یا این راهنما) رجوع کنید، برای ارزیابی آن که آیا فرمانها درست همانگونه که شما در مورد آنها میاندیشید کارمیکنند، یا دستور زبان کاربرد آنها همانطور است که شما فکر میکنید. بسیاری اوقات، اشخاص در باره چگونگی کارکرد
نکتهها را حفظ کنید و تکرارهای راهنمایی این آموزش را خوب به خاطر بسپارید. اینها غالباً برای پرهیز از مشکلات در اسکریپتها به شما کمک میکنند.
من این مطلب را در بخش اسکریپهای این راهنما نیز اشاره کردهام، اما تکرار آن در اینجا هم با ارزش است. اول از همه، اطمینان حاصل کنید که سرآیند اسکریپت شما به راستی
$ tr-d '\r' < myscript > tmp && mvtmp myscript
|
پرسش و پاسخها و Pitfallها را بخوانید |
|
صفحههای پرسش و پاسخهای رایج و دامهای Bash تصورات غلط معمول و مشکلاتی که دیگران در اسکریپتهای BASH با آنها روبرو شدهاند را شرح میدهند. احتمال بسیار دارد، مشکل شما به شکلی در آنجا تشریح شده باشد.
برای اینکه قادر به یافتن مشکل خود در آنجا باشید، باید مسئله را به طور کاملاً واضح شناسایی کرده باشید. شما باید بدانید که در جستجوی چه چیزی میباشید.
|
از ما در IRC بپرسید |
|
در 24 ساعت هفت روز هفته، اکثراً افرادی در کانال
مطمئن شوید که میدانید مشکل واقعی چیست و آنرا به صورت مرحلهای روی کاغذ بیاورید، به طوری که خوب بتوانید آنرا شرح بدهید. ما دوست نداریم در مورد مسائل حدس بزنیم. با توضیح آنکه اسکریپت شما چه کاری باید انجام بدهد شروع کنید.
نکته دیگر، لطفاً قبل از ورود به #bash صفحه : XyProblem را ملاحظه نمایید.