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

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

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

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

پرسش و پاسخ شماره ۶



چطور می‌توانم از متغیرهای متغیر( متغیرهای غیرمستقیم، اشاره‌گرها، مرجع‌ها ) یا آرایه‌های انجمنی استفاده کنم؟

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

مندرجات

  1. چطور می‌توانم از متغیرها( متغیرهای غیر مستقیم، اشاره‌گرها، مرجع‌ها) یا آرایه‌های انجمنی استفاده کنم؟
    1. آرایه‌های انجمنی
      1. آرایه انجمنی در پوسته‌های قدیمی‌تر هک می‌شود
    2. مقدمه
      1. قبل از به کار بردن متغیر غیر مستقیم، فکر کنید
      2. ارزیابی متغیرهای غیر مستقیم/مرجع
      3. تخصیص متغیرهای غیرمستقیم و مرجع
    3. See Also

آرایه های انجمنی

ما اول آرایه‌های انجمنی را معرفی می‌کنیم، زیرا در عمده موقعیت‌هایی که افراد سعی در تخصیص و ارزیابی متغیرهای غیرمستقیم می‌نمایند، به جای آن باید از آرایه‌های انجمنی استفاده کنند. برای نمونه، ما بارها کسانی را دیده‌ایم که می‌پرسند چطور می‌توانند یک گروه متغیرهای وابسته مانند IPaddr_hostname1، IPaddr_hostname2 و به همین ترتیب، داشته باشند. روش مناسبتر برای ذخیره این داده، یک آرایه انجمنی به نام IPaddr است که با hostname(نام میزبان) شاخص‌گذاری شده باشد.

برای طرح‌ریزی از یک رشته به دیگری، به آرایه شاخص‌گذاری شده توسط رشته به جای عدد، نیاز دارید. این در AWK به عنوان «آرایه‌های انجمنی» موجود است، در پرل به عنوان«hashes»، و در Tcl به سادگی به عنوان«arrays». آنها همچنین در ksh93 وجود دارند، که در آنجا شما از آن به این شکل استفاده می‌کنید:

  •  # ksh93
     typeset -A homedir             # تعریف می‌کند ksh93 آرایه انجمنی
     homedir[jim]=/home/jim
     homedir[silvia]=/home/silvia
     homedir[alex]=/home/alex
    
     for user in "${!homedir[@]}" 
     # تمام شاخص‌ها(نامهای کاربری) را به شمار می‌آورد
     do
         echo "Home directory of user $user is ${homedir[$user]}"
     done

BASH از نگارش 4 و بالاتر، از آنها پشتیبانی می‌کند:

  •  # Bash 4 and up
     declare -A homedir
     homedir[jim]=/home/jim
     # یا
     homedir=( [jim]=/home/jim
               [silvia]=/home/silvia
               [alex]=/home/alex )
     ...

در نسخه‌های Bash قبل از نگارش 4 یا اگر نمی‌توانید از ksh93 استفاده کنید، گزینه‌های شما محدود است. یا به سوی سایر مفسرها(awk، perl، python، ruby، tcl، ...) بروید یا مشکل خود را با ساده سازی آن ارزیابی مجدد نمایید.

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

فرض کنید چند میزبان خادم با مختصر تفاوتی در پیکربندی آنها داریم، و می‌خواهیم با ssh در هر یک از آنها فرمانهایی با تفاوت جزئی را اجرا نماییم. یک روشی که می‌توانیم به کار ببریم، تنظیم یک گروه فرمانهای ssh، که به آسانی نمی‌توانند تغییر کنند(hard-code)، در توابع هر نام میزبان در یک اسکریپت منفرد و فقط اجرای آنها به طور سری یا موازی. (فوراً این را رد نکنید! ساده خوب است.) روش دیگر می‌تواند ذخیره هر گروه از دستورات به عنوان یک عضو آرایه انجمنی شاخص‌گذاری شده با نام میزبان باشد:

  •  source "$conf"
     for host in "${!commands[@]}"; do
         ssh "$host" "${commands[$host]}"
     done
    
     # :فایلی مشابه این است  "$conf" که در آن
     declare -A commands
     commands=( [host1]="mvn clean install && cd webapp && mvn jetty:run"
                [host2]="..."
     )

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

بنابراین، اغلب سزاوار است یک قدم به عقب برگردیم و به جای سایر زبانهای برنامه‌نویسی، به ضوابط پوسته ها فکر کنیم . آیا در حال اجرای اسکریپت در یک میزبان راه دور نیستیم؟ پس چرامجموعه پیکربندی‌ها را در اسکریپت‌ها ذخیره نمی‌کنیم؟ آنوقت ساده است:

  • #مشخص برای میزبانهایی که دستورات باید در آنها اجرا شوندconfیک سری فایل 
     for conf in /etc/myapp/*; do
         host=${conf##*/}
         ssh "$host" bash < "$conf"
     done
    
     # /etc/myapp/hostname is just a script:
     mvn clean install &&
     cd webapp &&
     mvn jetty:run

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

  •  parallel ssh {/} bash "<" {} ::: /etc/myapp/*

آرایه انجمنی در پوسته‌های قدیمی‌تر قابل مدیریت است

قبل از اندیشیدن به استفاده از eval برای تقلید آرایه‌های انجمنی در یک پوسته‌ قدیمی‌تر(احتمالاً با ایجاد مجموعه‌ای از نام متغیرها مشابه homedir_alex)، فکر کردن به رویکرد ساده‌تر یا کاملاً متفاوتی که می‌توانید به جای آن به کار ببرید را امتحان کنید. اگر بازهم به نطر می‌رسد که این دستیابی بهترین کار است،معایب ذیل را ملاحظه نمایید:

  1. حقیقتاً خواندن، پیگردی نمودن، و نگهداری آن دشوار است.
  2. نامهای متغیر باید با عبارت منظم ^[a-zA-Z_][a-zA-Z_0-9]* منطبق باشد-- برای مثال، یک نام متغیر نمی‌تواند شامل کاراکترهای دلخواه باشد بلکه فقط حروف، ارقام و خط‌زیر مجاز می‌باشند. نمی‌توانیم متغیری شامل نامهای کاربری یونیکس داشته باشیم، برای نمونه، -- نام کاربری hong-hu را ملاحظه کنید. خط تیره '-'نمی‌تواند بخشی از نام متغیر باشد،بنابراین تمام تلاش برای ایجاد متغیری به نام homedir_hong-hu از ابتدا محکوم به شکست است.

  3. نقل‌قول برای حصول نتیجه صحیح دشوار است. اگر محتوای رشته(نه نام متغیر) می‌تواند شامل کاراکترهای فضای سفید و نقل‌قول باشد،نقل‌قولی نمودن به طور صحیح برای حفظ آن در هر دو تجزیه پوسته دشوار است. و آن فقط برای ثابت‌ها که در زمان نوشتن برنامه معلوم هستند، شدنی است. (فرمان ‎ printf %q‎ پوسته Bash کمک می‌کند، اما مورد قابل مقایسه‌ای در پوسته‌های POSIX در دسترس نیست

  4. اگر برنامه گندزدایی، ورودی کاربر را اداره نکند، این می‌تواند خیلی خطرناک باشد!

بخش آرایه‌ها از راهنما یا پرسش و پاسخ 5 را برای توضیح عمقی و مثالهایی از چگونگی استفاده از آرایه‌ها در Bash، بخوانید.

اگر شما نیاز به یک آرایه انجمنی دارید اما پوسته شما آنها را پشتیبانی نمی کند، لطفاً به جای آن استفاده از AWK را در نظر بگیرید.


ادامه دارد ....

پرسش و پاسخ شماره ۵



چگونه می‌توانم از متغیرهای آرایه‌ای استفاده کنم؟

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

1. مقدمه

آرایه‌های یک بُعدی با شاخص عدد صحیح در پوسته Bash، Zsh، واکثر پوسته‌های متنوع Korn از جمله‎ AT&T ksh88 ‎ یا مابعد از آن، mksh، و pdksh پیاده‌سازی گردیده‌اند. آرایه‌ها توسط POSIX تعریف نشده‌اند و در میراث آن یا پوسته‌های حداقلی، مانند شل Bourne Dash در دسترس نمی‌باشند. پوسته‌های سازگار با POSIX که معمولاً در اصول اساسی آرایه‌ها توافق دارند، ولی در جزئیات تفاوتهای عمده‌ای دارند. کاربران پیشرفته پوسته‌های متعدد، باید با تحقیق نمودن ویژگی‌ها، اطمینان حاصل نمایند. Ksh93، Zsh، و Bash نگارش 4.0 علاوه براین دارای آرایه‌های انجمنی نیز می‌باشند. این مقاله بر آرایه‌های شاخص‌دار تمرکز می‌نماید، چون آنها رایج‌ترین و سودمندترین نوع هستند.

این هم کاربرد نوعیِ نمایان کننده الگو، در ‌آرایه‌ای به نام host:

# Bash  در پوسته‎

# .به آرایه‌ای با شاخص‌های ترتیبی goofy و mickey, minnie  اختصاص مقادیر
host=(mickey minnie goofy)

#  "host"تکرار روی شاخص های 
for idx in "${!host[@]}"; do
    printf 'Host number %d is %s' "$idx" "${host[idx]}"
done

"${!host[@]}"‎ به شاخص های آرایه host بسط می‌یابد، هر یک به صورت یک شناسه جداگانه. (در ادامه وارد جزئیات ترکیب دستوری--syntax-- خواهیم شد.)

آرایه‌های شاخص‌دار پراکنده هستند، و عناصر آنها می‌توانند به طور نامرتب حذف و اضافه بشوند.

# Bash و ksh   در پوسته‎

# .ترکیب دستوری ساده
arr[0]=0
arr[2]=2
arr[1]=1
arr[42]='what was the question?'

# (مترجم: با شروع از صفر، می‌شود سومین عضو ) "arr" حذف عضو شماره دو
unset -v 'arr[2]'

# الحاق عناصر به طوری که با فاصله جداشده‌اند در یک رشته منفرد و نمایش آن
echo "${arr[*]}"
# "0 1 what was the question?" :خروجی 

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

2. بارگذاری کمیت‌ها در یک آرایه

اختصاص یک عضو آرایه در یک نوبت، ساده و قابل حمل است:

# Bash و ksh  در پوسته
arr[0]=0
arr[42]='the answer'

تخصیص چندگانه کمیت‌ها به آرایه، در یک نوبت نیز امکان پذیر است، اما ترکیب دستوری آن در میان پوسته‌ها متفاوت است. Bash فقط از ترکیب ‎ arrName=(args...) ‎  پشتیبانی می‌کند. ksh88 فقط از ترکیب ‎set -A arrName -- args...‎ پشتیبانی می‌نماید. ksh93، mksh، و zsh از هردو پشتیبانی می‌کنند. اگر از نزدیک به آن نگاه کنید، در هر دو روش تفاوتهای ظریفی بین تمام این پوسته‌ها وجود دارد.

# Bash, ksh93, mksh, zsh  در پوسته‌های
array=(zero one two three four)

# ksh88/93, mksh, zsh  در پوسته‌های 
set -A array -- zero one two three four

موقع مقدار دهی اولیه به این طریق، اولین شاخص صفر است مگر اینکه شاخص دیگری تعیین شده باشد.

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

# Bash و ksh93  در پوسته
oggs=(*.ogg)

در روش تخصیص ksh88 مانند، با استفاده از set، شناسه‌ها دقیقاً مانند شناسه‌های معمولی فرمان هستند.

# Korn  در پوسته
set -A oggs -- *.ogg

#                     (بسط ابرو نیازمند نگارش 3.0 یا بالاتر است) Bash در ‎
homeDirs=(~{,root})
#      است Bash به ترتیب دیگری رخ می‌دهد و این روش محصوص ksh بسط ابرو در‎
letters=({a..z})   # در همه شل‌ها نمی‌توان با بسط-رشته حروف را به کار برد

# Korn  در پوسته
set -A args -- "$@"

2.1. بارگیری سطرها از فایل یا جریان داده

در bash نگارش 4، فرمان mapfile(که readarray نیز نامیده می‌شود) این موارد را انجام می‌دهد:

# Bash 4 در 
mapfile -t lines <myfile

# یا
mapfile -t lines < <(some command)

بخش جایگزینی پردازش و پرسش و پاسخ شماره ۲۴ را برای جزئیات بیشتر در باره ترکیت دستوری ‎ <(...)‎ ملاحظه کنید.

فرمان mapfile سطرهای خالی و همچنین فقدان سطر جدید انتهای یک جریان ورودی را با درج آنها به عنوان یک عضو تهی آرایه اداره می‌کند. این مورد، موقعی که داده‌ها به روش دیگری( بخش بعدی را ببینید) خوانده می‌شوند، می‌تواند مشکل ساز بشود. mapfile یک سری اشکال دارد: این فرمان فقط سطر جدید را می‌تواند به عنوان خاتمه دهنده سطرها مدیریت کند. تمام گزینه‌های مورد پشتیبانی فرمان read توسط mapfile، و به طور معکوس، مدیریت نمی‌شوند. به عنوان مثال mapfile نمی‌تواند فایلهای جداشده با کاراکتر NUL بواسطه فرمان find -print0`‎ را مدیریت کند. موقعی که mapfile در دسترس نباشد، برای تهیه المثنی آن باید خیلی سخت کار کنیم. روشهای بسیاری برای تهیه تقریباً موفق آن وجود دارد، اما به طرُق ظریفی شکست می‌خورند.

این مثالها رونوشتی از اکثر توانایی‌های اساسی mapfile ایجاد می‌کنند:

# Bash, Ksh93, mksh  در پوسته‌های
while IFS= read -r; do
    lines+=("$REPLY")
done <file
[[ $REPLY ]] && lines+=("$REPLY")

عملگر ‎+=‎ وقتی با پرانتزها به کار می‌رود، عنصری به آرایه اضافه می‌کند، که شماره شاخص آن برابر بالاترین شاخص موجود آرایه به اضافه یک خواهد بود.

# Korn  در پوسته‎
#  .کاهش و افزایش قبل و بعد را پشتیبانی نمی‌کند  Ksh88  
i=0
while IFS= read -r; do
    lines[i+=1,$i]=$REPLY
done <file
[[ $REPLY ]] && lines[i]=$REPLY

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

2.1.1. مدیریت سطرهای جدید(یا فقدان آنها) در انتهای فایل

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

برای واضح شدن مطلب- اکثر فایلهای متن شامل یک سطر جدید به عنوان آخرین کاراکتر فایل می‌باشند. سطرجدید توسط اکثر ویرایشگرهای متن به انتهای فایلها اضافه می‌شود، و همچنین توسطHere documentها و Here stringها نیز اضافه می‌شوند. اکثر اوقات، فقط موقع خواندن خروجی از لوله‌ها و جایگزینی پردازش، یا از فایلهای متن معیوب تولید شده توسط ابزارهای معیوب یا فاقد پیکربندی مناسب، این مورد یک مسئله است. اجازه دهید به چند مثال نگاه کنیم.

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

# !به طور صحیح عمل نمی‌کند
unset -v arr i
while IFS= read -r 'arr[i++]'; do
    :
done < <(printf '%s\n' {a..d})

متأسفانه، اگر فایل یا جریان داده شامل یک سطر جدید انتهایی باشد، یک عضو خالی به انتهای آرایه اضافه می‌شود، زیرا دستور‎ read -r arr[i++]‎ بعد از آخرین سطر شامل متن و قبل از باز گرداندن غلط، یک مرتبه اضافی اجرا می‌شود.

# !باز هم به طور صحیح عمل نمی‌کند
unset -v arr i
while read -r; do
    arr[i++]=$REPLY
done < <(printf %s {a..c}$'\n' d)

کروشه‌ها زمینه محاسباتی ایجاد می‌کنند. داخل آنها، ‎i++‎ همانطور عمل می‌کند که برنامه‌نویسان C انتظار دارند(همه غیر از ksh88).

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

# Bash, ksh93, mksh  در پوسته های
unset -v arr i
while IFS= read -r; do
    arr[i++]=$REPLY
done <file
# .الحاق سطر داده خاتمه نیافته، در صورت موجود بودن
[[ $REPLY ]] && arr[i++]=$REPLY 

این مورد به «آخرین راه حل» که ما قبلاً ارائه نمودیم، بسیار نزدیک است -- هم مدیریت سطرهای خالی داخل فایل، و هم سطر انتهایی خاتمه نیافته. IFS تهی برای ممانعت فرمان read از زدودن فضای سفید احتمالی ابتدا و انتهای سطرها در صورتی که مایل به حفظ آنها باشید، به کار می‌رود .

یک طریق دیگر عبور از این مشکل، حذف عضو خالی پس از پایان حلقه است:

# Bash
unset -v arr i
while IFS= read -r 'arr[i++]'; do
    :
done <file

# .عضو انتهایی را اگر باشد حذف می‌کند
[[ ${arr[i-1]} ]] || unset -v 'arr[--i]'

خواه شما ترجیح بدهید مقدار اضافه بخوانید و سپس یکی را حذف کنید، یا کمتر بخوانید و بعد یکی را اضافه کنید، یک انتخاب شخصی است.

توجه: برای آنکه کروشه‌ها به عنوان globها تفسیر نشوند، نقل‌قولی نمودن ‎ 'arr[i++]'‎ تحویل شده به فرمان read ضروری است. این مظلب برای سایر دستورات داخلی غیر کلیدواژه‌ای که یک نام متغیر زیرنویس‌دار می‌گیرند مانند let و unset نیز صدق می‌کند.


ادامه دارد ....

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



4. چگونه بررسی نمایم که آیا یک شاخه، خالی است یا خیر؟ چطور هر فایل ‎*.mpg‎ را بررسی کنم، یا تعداد آنها را شمارش کنم؟

در Bash، می‌توانید این کار را به طور مطمئن و به آسانی با گزینه‌های nullglob و dotglob (که رفتار globbing را تغییر می‌دهند) و یک آرایه انجام دهید:

پاسخ مشروح را در اینجا ‎[BashFAQ/004]‎ ملاحظه نمایید:

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



چطور می‌توانم آخرین(جدیدترین، قدیمی‌ترین، مسن‌ترین) فایل در یک دایرکتوری را پیدا کنم؟

راه حل اغوا کننده، استفاده از 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)‎ نگاه کنند، اگر سیستم عامل آنها آنرا فراهم نموده باشد.


CategoryShell

پرسش و پاسخ 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‎ است، مبهم و کلی.


CategoryShell

پرسش و پاسخ 2 (آخرین ویرایش‎ 2012-12-04 09:50:10 ‎ توسط geirha)