خوب، بستگی دارد به اینکه آیا میخواهید خروجی فرمان را ذخیره کنید(هر یک از 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
exec 4 >> "$ 1"
BASH_XTRACEFD = 4
set -x
else
set +x
exec 4 >& -
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 را ملاحظه نمایید.
پوسته Bash امکان انجام کارهای بسیاری برای شما فراهم میکند، ارائه قابلیت انعطافپذیری قابل ملاحظه به شما. متأسفانه، خیلی کم شما را از سوءمصرف و دیگر رفتارهای نامطلوب، بر حذر میدارد. امید میرود، اشخاص خودشان دریابند که از برخی مسائل معین باید به هر قیمتی پرهیز نمایند.
متأسفانه بسیاری اشخاص به اندازه کافی دقیق و مراقب نیستند که بخواهند خودشان موشکافی کنند. آنها بدون اندیشیدن در مورد مسائل، مینویسند و بسیاری از اسکریپتهای خطرناک و مهیب به محیطهای تولید و یا توزیعهای لینوکس ختم میشود. نتیجه اینها، و حتی اسکریپتهای خیلی شخصی شما در یک شرایط اهمال اغلب میتواند مصیبتآمیز بشود.
برای پاکیزگی اسکریپتهایتان، و به خاطر تمام افراد بشر، هرگز هیچ موردی از سطور زیر را انجام ندهید:
ls
هرگز تجزیه خروجی فرمان ls را انجام ندهید! خروجی فرمان ls به چند دلیل نمیتواند قابل اعتماد باشد.
اول، اگر نام فایلها شامل کاراکترهای پشتیبانی نشده زبان محلی شما باشد، ls نامها را خُرد خواهد نمود. در نتیجه، خروجی حاصل از تجزیه نام فایلها توسط ls، هرگز تضمین نمیشود که واقعاً همان نامهایی که شما قادر به یافتن آنها میباشید را به شما بدهد. ls ممکن است بعضی کاراکترها در نام فایل را با کاراکتر علامت سؤال تعویض نماید.
دوم، ls سطرهای دادهها را بر اساس کاراکتر سطر جدید تفکیک میکند. به این طریق، هر تکه از اطلاعات یک فایل در یک سطر است. متأسفانه، نام فایلها نیز خودشان میتوانند شامل سطر جدید باشند. این به معنی آنست که اگر شما فایلی در دایرکتوری جاری با نام شامل کاراکتر سطر جدید داشته باشید، کاملاً نتیجه تجزیه شما را درهم میریزد و اسکریپت شکست میخورد!
آخر از همه، اما نه کم اهمیتتر، قالب خروجی فرمان ls
در موقعیتهای بسیاری جایگزینهایی برای ls وجود دارد. اگر لازم است که شما با زمان ویرایش فایل کار کنید، به طور نمونه میتوانید از بررسیهای Bash استفاده کنید. اگر هیچ یک از آنها میسر نباشد، پیشنهاد میکنم زبان متفاوتی، همچون پرل یا python انتخاب کنید.
ls
هرگز نام فایل ها را با grep بررسی یا فیلتر نکنید! غیر از آنکه الگوی grep شما واقعاً هوشمند باشد، این کار احتمالاً قابل اطمینان نخواهد بود.
در نمونه اول مثال فوق، بررسی با هر دو مورد
جایگزین آن globbing نامیده میشود(
cat
برنامه cat را برای خوراندن محتویات یک فایل منفرد به یک فیلتر به کار نبرید. cat یک ابزار مورد استفاده برای الحاق محتویات چند فایل با یکدیگر است.
برای تغذیه محتویات فایلی به یک پردازش، احتمالاً میتوانید نام فایل را به عنوان شناسه تحویل برنامه مورد نظر(مانند grep '
اگر مستندات برنامه هیچ راهی برای انجام این کار تعیین نکرده است، باید از تغییر مسیر استفاده کنید (read
از حلقه for برای خواندن سطرهای یک فایل استفاده نکنیم. به جای آن حلقه while read را به کار ببریم.
به خاطر خدا و به خاطر تمام مقدسات، از برنامه seq برای شمارش استفاده نکنید.
Bash به اندازه کافی در انجام شمارش توانمند است. نیازی به یک برنامه خارجی(مخصوصاً یک برنامه تک سکویی) برای انجام محاسبه و ارسال آن به خروجی Bash جهت تفکیک کلمه، ندارید. ترکیب دستوری
باید در Bash نگارش 3 به بعد، از این:
اگر شما عملاً یک جریانی از اعداد که با کاراکتر سطر جدید از هم جدا شدهاند، هنگام بررسی ورودی میخواهید، این مورد را در نظر بگیرید: printf
expr یک عتیقه رُم باستان است. آن را به کار نبرید.
این برنامه در اسکریپتهای نوشته شده برای پوستههایی با امکانات بسیار محدود، به کار میرفت. اساساً با استفاده از آن در حال ایجاد یک پردازش جدید هستید که برنامه C دیگری برای انجام برخی محاسبات را برایتان فراخوانی نماید و نتایج را به صورت رشته به bashتحویل بدهد. Bash تمام اینها را خودش میتواند خیلی سریعتر، و به طور قابل اعتمادتر (بدون تبدیل عدد به ->رشته -> به عدد) و در همه حال بهتر، انجام بدهد.
شما در Bash باید از این استفاده کنید: let
حتی پوسته POSIX بورن میتواند محاسبات را انجام بدهد: