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

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

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

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

RaceCondition


یک race condition وضعیتی است که در آن دو یا چند مورد به طور همزمان در حال وقوع می‌باشند، و نتیجه نهایی به زمان‌بندی دقیق رویدادها بستگی دارد.

برای مثال، دو برنامه را در نظر بگیرید که در یک زمان اجرا می‌شوند:

  #!/bin/sh
  # برنامه اول‎
  read number < file
  number=$(($number + 1))
  echo $number > file

  #!/bin/sh
  # برنامه دوم‎
  read number < file
  number=$(($number - 1))
  echo $number > file

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

به طور ساده‌انگارانه، انتظار خواهیم داشت مقدار نهایی در فایل 42 بشود. اگر سیستم عامل ابتدا برنامه اول را زمان‌بندی کند، آنوقت آن برنامه 43 را در فایل می‌نویسد، سپس برنامه دوم 43 را می‌خواند، یک را از آن کم می‌کند و 42 را در فایل می‌نویسد. همچنین، اگر سیستم‌عامل ابتدا برنامه دوم را در زمان‌بندی قرار دهد، انتظار خواهیم داشت فایل محتوی 41 و سپس موقعی که برنامه اول تمام می‌شود، 42 بشود. درست است؟

خوب، به طور حتم این یک پیامد محتمل می‌باشد. اما تنها نتیجه ممکن نیست، به علت اینکه برنامه‌ها atomic نیستند -- یعنی، آنها تمام مراحل خود را بدون انقطاع انجام نمی‌دهند.

یک توالی احتمالی دیگر وقوع رویدادها چنین است:

  1. برنامه اول عدد 42 را از فایل می‌خواند.
  2. برنامه دوم عدد 42 را از فایل می‌خواند.
  3. برنامه اول 1 را به عددش اضافه می‌کند، و اکنون در حافظه 43 را دارد.
  4. برنامه دوم 1 را از عددش کم می‌کند، و حالا 41 را در حافظه دارد.
  5. برنامه اول 43 را در فایل می‌نویسد.
  6. برنامه دوم 41 را در فایل می‌نویسد. نتیجه نهایی: 41

اگر دو سطر انتهایی به طور برعکس رخ بدهند، آنوقت جواب نهایی 43 خواهد شد. بنابراین، به نسبت تمایل زمان بندی سیستم‌عامل، می‌توانیم نتایج انتهایی 41, 42 یا ‏43 را داشته باشیم.

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

  • RaceCondition (آخرین ویرایش ‎2009-02-04 20:56:56‎ توسط GreyCat)

fork بمب


من این دستور را در جایی دیدم: ‎:(){ :|:& }‎ (بمب خوشه‌ای). این چطور کار می‌کند؟

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

بمب خوشه‌ای یک شکل ساده تکذیب سرویس(یا حمله DoS) است که بر مبنای نام فراخوان سیستم یونیکسیِ ‎fork(2)‎ نام گذاری گردیده است. برنامه‌ای است که توسط انشعاب کپی‌های خودش به طور مکرر، که فرزندان نیز به طور بازگشتی همان کار را می‌کنند، به سرعت منابع سیستم را تحلیل می‌برد. در بسیاری از سیستم‌هایِ بدون محدودیت صحیحِ منابع، این مورد ممکن است شما را در یک وضعیت اصلاح ناپذیر غیرپاسخگو رها کند.

این تعریف ویژه از بمب fork در Bash بنا به دلایلی چنان مشهور است که گاهی اوقات بمب خوشه‌ای نامیده می‌شود.

در اینجا رایج‌ترین شکل مورد پسند عامه کد آن آمده است:

:(){ :|:& };:

و از طرف دیگر، با قواعد مناسب برای خوانایی به این صورت:

#!/usr/bin/env bash
:() { 
    : | : &
}

:

این کد تابعی به نام : تعریف می‌کند. بدنه تابع یک لوله را تنظیم می‌کند، که در Bash متشکل از دو پوسته فرعی می‌باشد، خروجی استاندارد اولی توسط یک لوله به ورودی استاندارد دومی متصل گردیده است. تابع (پوسته والد لوله) لوله را پس‌زمینه‌ای می‌سازد، تابع باز می‌گردد و پوسته با رها نمودن job در پس زمینه، خاتمه می‌یابد. نتیجه نهایی دو پردازش جدید است که هر کدام پردازش : را برای تکرار فرآیند، فراخوانی می‌کنند.

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

bomb() {
    bomb | bomb &
}

bomb

به طور نظری، هر شخصی که در کامپیوتر شما به پوسته دسترسی دارد، می‌تواند از چنین تکنیکی برای نابود کردن منابعی که به آن دسترسی دارد استفاده کند. در اینجا یک ‎chroot(2)‎ کمک نخواهد بود. اگر منابع کاربر نامحدود باشد، آنوقت در چند ثانیه‌ای تمام منابع سیستم شما(پردازش‌ها، حافظه مجازی، فایلهای باز، وغیره.) استفاده خواهد شد و احتمالاً خودش دچار وقفه می‌شود. هر کوشش به عمل آمده توسط کرنل برای آزاد‌سازی منابع بیشتر فقط اجازه می‌دهد نمونه‌های بیشتری از تابع ایجاد بشود.

در نتیجه، تنها راه محافظت از خودتان در برابر چنین سوءاستفاده‌ای، محدودیتِ حداکثرِ مجازِ استفاده از منابع برای کاربرانتان می‌باشد. چنین منابعی به وسیله فراخوان سیستمی ‎setrlimit(2)‎ مقرر می‌شود. واسط این قابلیت در Bash و پوسته Korn فرمان ‎ulimit‎ است. همچنین ممکن است سیستم عامل شما فایلهای پیکربندی ویژه‌ای برای کمک به مدیریت این منابع داشته باشد( برای مثال، فایل ‎/etc/security/limits.conf‎ در دبیان، یا ‎/etc/login.conf‎ در OpenBSD). برای جزئیات، مستندات سیستم خود را کنکاش نمایید.

چرا ‎'':(){ :|:& };:''‎ روش نامساعدی برای تعریف یک بمب خوشه‌ای است

این تعریف عامه پسند به علت یک ترکیب غیرعادی جزئیات (در پوسته‌هایی که من با آنها تست کرده‌ام) که فقط در Bash غیر POSIX و Zsh(تمام حالت‌های شبیه‌سازی) پیش می‌آید، کار می‌کند.

  1. پوسته باید تعریف نامهای تابع، ماورای آنها که به واسطه یک ‎POSIX "Name"‎ مجاز هستند را اجازه بدهد. این مطلب بلافاصله ksh93 و Bash در وضعیت POSIX و Dash و Posh ( Posh یک شاخه قدیمی pdksh است که دیگر پشتیبانی نمی‌شود)، و ‎Busybox sh‎ را رد می‌کند.

  2. یا باید پوسته:
    • به طور نادرست توابعی مقرر کند که داخلی‌های ویژه را پیش از داخلی خودش بارگذاری می‌کند. جستجو و اجرای فرمان را ببینید. mksh در این مرحله(به طور صحیح) ناموفق می‌شود، واقعاً دستور : داخلی را اجرا می‌کند. حتی اگر شما تابع را به طور موفقیت آمیز تعریف کنید، فراخوانی تابع غیر ممکن است. Bash در وضعیت غیرPOSIX و Zsh (حتی شبیه‌ساز POSIX)مطابق این ضوابط هستند.

    • یا فراهم نمودن وسیله غیر فعال‌سازی فرمان داخلی. Bash و Ksh93 انجام می‌دهند. که Bash ناموفق است (درست مطابق مستنداتش). Ksh93 موفق می‌شود( به طور نادرست بر اساس مستنداتش).
          $ bash -c 'enable -d :; type -p :'
          bash: line 0: enable: :: not dynamically loaded
          $ ksh -c 'builtin -d :; whence -v :'
          ksh: whence: :: not found
      احتمالاً یک باگ:
        -d    **.هر داخلی تعریف شده را حذف می‌کند. **داخلی ویژه نمی‌تواند حذف بشود‎
      در هر صورت، نامربوط است زیرا ksh93 قبلاً در مرحله اول ناموفق شده است. اکنون شما فقط یک داخلی غیرقابل دسترس دارید.

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

 :(){ :|:& };: 
  • -- حتماً، گمان ‌کنم می‌توانید آن را تایپ کنید.

پرسش و پاسخ 59 (آخرین ویرایش ‎2013-01-08 18:58:36‎ توسط GreyCat)


رفتار Bash با داده‌های باینری


آیا bash می‌تواند داده‌های باینری را اداره کند؟

به طور اساسی پاسخ خیر است....

در حالیکه bash مشکلاتی به زیادی پوسته‌های قدیمی‌تر با آنها ندارد، باز هم نمی‌تواند داده‌های باینری اختیاری را پردازش نماید، و به طور اخص، متغیرهای پوسته ‎ 100%‎ باینری خالص نیستند، بنابراین نمی‌توانید فایلهای باینری را در آنها ذخیره کنید.

شما می‌توانید داده‌های اسکی کُدگذاری شده یونیکس به یونیکس(uuencoded) را به این طریق در متغییر قرار دهید:

    var=$(uuencode /bin/ls ls)
    cd /somewhere/else
    uudecode <<<"$var"  # نقل‌قولها را فراموش نکنید
  • توجه: تفاوت سترگی میان uuencode یا uudecode گنو و یونیکس وجود دارد. با uudecode یونیکس، شما نمی‌توانید فایل خروجی تعیین کنید، همواره از نام فایل کدگذاری شده در داده اسکی استفاده می‌کند. من مثال قبلی را اصلاح کرده‌ام به طوری که در سیستم‌های یونیکس کار می‌کند. اگر شما تغییرات بیشتری ایجاد می‌کنید، لطفاً رویه‌گرایی گنو (GNUisms) را به کار نبرید. متشکرم. --GreyCat

یک نمونه که چنین موردی ممکن است گاهی در آن سودمند باشد، ذخیره کردن نقشه‌بیتی‌های کوچک موقتی، در هنگام کار کردن با netpbm است... در اینجا من به یک pnmnoraw زیرنویس 1 اضافی در لوله متوسل شدم که باعث ایجاد فایلهای اسکی بزرگتر می‌شود و bash با ذخیره آنها مشکلی ندارد.

اگر احساس ماجراجویی دارید، این تجربه را ملاحظه نمایید:

# bindec.bash, سعی می‌کند داده‌های باینری را به اسکی دسیمال رمزگشایی کند‎
IFS=
while read -n1 x ;do
  case "$x" in
     '') echo empty ;;
     # ‏۲۵۶ سطر تولید شده توسط سطر فرمان زیر را در اینجا درج کنید ‎
     # for x in $(seq 0 255) ;do echo "     $'\\$(printf %o $x)') echo $x;;" ;done
  esac
done

و سپس داده باینری را به آن لوله‌کشی کنید، شاید اینطور :

    for x in $(seq 0 255) ;do echo -ne "\\$(printf %o $x)" ;done | bash bindec.bash | nl | less

این کد ایجاب می‌کند که کاراکتر 0 به طور کلی از قلم انداخته شود، زیرا ما نمی‌توانیم آنرا با تولید کننده ورودی ایجاد کنیم، به راحتی برای خراب کردن اکثر فایلهای باینری که می‌خواهیم پردازش کنیم، کفایت می‌کند.

  • بلی، Bash به زبان C نوشته شده، و از معناشناسی این زبان برای مدیریت رشته‌ها -- شامل بایت‌های NUL به عنوان حدفاصل رشته‌ها -- در متغیرهایش استفاده می‌کند. شما نمی‌توانید به طور معقولی NUL را در متغیرهای Bash ذخیره نمایید. در حقیقت هرگز هم قرار نبوده به این صورت به کار برود. - GreyCat

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

برای cat کردن فایل باینری تنها با دستورات داخلی bash موقعی که برنامه خارجی در دسترس نباشد(یکبار وقتی نام فایل‎ /lib/libgcc_s.so.1‎ تغییر کرده بود، استفاده از این ترفند کارم را راه انداخت):

# باینری مطمئن bash فقط با دستورات داخلی cat شبیه‌سازی 
IFS=
while read -d '' -r -n1 x ; do
    case "$x" in
        '') printf "\x00";;
        *) printf "%s" "$x";;
    esac
done
  • من ترجیح خواهم داد از cat استفاده کنم. همچنین، آن ‎-n1‎ واقعاً لازم بود؟ -GreyCat

    • بدون ‎-n1‎ شما باید برای کار کردن با داده‌های بعد از آخرین ‎ \0‎ خیلی با احتیاط باشید، موردی مانند این ‎[[ $x ]] && printf "%s" "%x"‎ بعد از حلقه. من این را بررسی نکرده‌ام که بدانم آیا کار می‌کند یا اینکه کافی هست. همچنین من نمی‌دانم اگر شما یک فایل بزرگ بدون هیچ ‎ \0‎ را بخوانید چه اتفاقی می‌افتد --pgas


پرسش و پاسخ 58 (آخرین ویرایش ‎ 2009-03-09 08:26:26 ‎ توسط pgas)


  1. مترجم: pnmnoraw‎ بخشی ازبرنامه Netpbm است که خود جعبه ابزار دستکاری و تبدیل فایلهای تصویری می‌باشد و شامل 220 ابزار با قابلیت تبدیل 80نوع قالب تصویری به یکدیگر است. pnmnoraw که یکی از این ابزارهامی‌باشد، یک فایل تصویری ‎.pnm یا ‎‎(portable any map)‎ به صورت raw(باینری)‎ را به عنوان ورودی می‌خواند و خروجی‌اش را به صورت خام(ascii) می‌نویسد. البته این ابزار در Netpbm نگارش 8.2 با pnmtoplainpnm تعویض گردید که همان برنامه است و فقط نام آن تغییر کرده به طوری که نام آن مبدل قالب بودن را می‌رساند، و این نیز در Netpbm نگارش 10.23(جولای 2004) با pnmtopnm جایگزین گردیده که کلی‌تر است و در دوجهت عمل می‌کند(از raw به ascii و بالعکس). (1)


گروه‌بندی اقلام داخل یک فایل


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

چنان که شخصی بخواهد این فایل را:

    foo: entry1
    bar: entry2
    foo: entry3
    baz: entry4

تبدیل کند به این

    foo: entry1 entry3
    bar: entry2
    baz: entry4

دو روش ساده عمومی برای انجام این کار وجود دارد:

  1. مرتب نمودن فایل، و سپس تکرار روی آن، جمع آوری مدخل‌ها تا موقعی که پیشوند تغییر کند، و سپس چاپ مدخل‌های جمع آوری شده با پیشوند قبلی
  2. تکرار روی یک فایل، جمع آوری مدخل ها برای هر پیشوند در یک آرایه شاخص‌گذاری شده با پیشوند

یک پیاده‌سازی اساسی از روش a در bash:

old=xxx ; stuff=
(sort file ; echo xxx) | while read prefix line ; do 
        if [[ $prefix = $old ]] ; then
                stuff="$stuff $line"
        else
                echo "$old: $stuff"
                old="$prefix"
                stuff=
        fi
done 

و یک پیاده‌سازی اساسی از روش b در awk، با استفاده از آرایه چند بعدی:

    {
      a[$1,++b[$1]] = $2;
    }

    END {
      for (i in b) {
        printf("%s", i);
        for (j=1; j<=b[i]; j++) {
          printf(" %s", a[i,j]);
        }
        print "";
      }
    }

نوشته شده به صورت یک سطر دستور مفصل در پوسته:

    awk '{a[$1,++b[$1]]=$2} END {for (i in b) {printf("%s", i); for (j=1; j<=b[i]; j++) printf(" %s", a[i,j]); print ""}}' file

پرسش و پاسخ 57 آخرین ویرایش ‎ 2012-03-29 20:36:56 ‎ توسط ormaaj)


untar یا unzip چند فایل با هم


چطور می‌توانم چند فایل را به طور یکجا untar (یا unzip) نمایم؟

چون فرمان tar در اصل طوری طراحی شده بود که از دستگاه‌های نوار گردان مغناطیسی بخواند یا در آن بنویسد(کلمه tar از ‎ Tape ARchiver‎ اخذ شده)، شما به طور خاص فقط می‌توانید نام فایلها را در بایگانی قرار دهید(نوشتن در نوار) یا آنها را از یک آرشیو استخراج نمایید(خواندن از نوار).

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

    tar -x -f backup.tar myfile.txt
    # ( IMHO یا(ترکیب دستوری عمومی‌تر
    tar xf backup.tar myfile.txt

حال در اینجا یک اشتباه رایج هست -- تصور کنید دایرکتوری شامل فایلهای بایگانی زیر است و می‌خواهید همه را به یکباره استخراج کنید:

    $ ls
    backup1.tar backup2.tar backup3.tar

شاید فکر کنید با دستور ‎ tar xf *.tar‎ انجام بدهید. بیایید ببینیم:

    $ tar xf *.tar
    tar: backup2.tar: Not found in archive
    tar: backup3.tar: Not found in archive
    tar: Error exit delayed from previous errors

جه اتفاقی رخ می‌دهد؟ پوسته ‎ *.tar‎ شما را با فایلهای قابل انطباق تعویض می‌کند. در حقیقت شما نوشته‌اید:

    tar xf backup1.tar backup2.tar backup3.tar

و به طوری که فبلاً دیدیم، به معنی آنست که: «فایلهای backup2.tar و backup3.tar را از فایل بایگانی backup1.tar استخراج کن», که البته فقط موقعی موفق خواهد شد که چنین نام فایلهایی در فایل بایگانی ذخیره شده باشند.

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

    for tarname in ./*.tar; do
      tar xf "$tarname"
    done

چه اتفاقی می‌افتد؟ حلقه for روی تمام نام فایلها که با ‎ *.tar‎ منطبق می‌شوند تکرار می‌کند و ‎ tar xf را برای هر یک از آنها فرامی‌خواهد. به این طریق شما تمام فایلهای بایگانی را یک به یک استخراج می‌کنید و درست آن را به طور خودکار انجام می‌دهید.

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

    for zipfile in ./*.zip; do
      unzip "$zipfile"
    done

کافی نیست؟ Ok. گزینه دیگری همراه unzip وجود دارد: این گزینه می‌تواند الگوهای شل-مانند را برای تعیین نام فایلهای ZIP قبول کند. و برای پرهیز از تفسیر آن الگوها توسط پوسته، لازم است آنها را نقل‌قولی کنید. در این حالت خود unzip و نه پوسته، عبارت ‎ *.zip‎ را تفسیر می‌کند:

    unzip "*.zip"
    # :یا برای شفاف کردن آن که چه کار می‌کنیم 
    unzip \*.zip

(این ویژگی دستور unzip در اصل از خاستگاه آن به عنوان یک برنامه MS-DOS ناشی می‌گردد. مفسر فرمان MS-DOS بسط‌های glob انجام نمی‌دهد، بنابراین هر برنامه MS-DOS باید قادر باشد فوق کاراکترها را به لیستی از نام فایلها بسط بدهد. این ویژگی در نگارش یونیکس باقی گذاشته شد، و به طوری که نمایش دادیم، گاهی اوقات می‌تواند مفید باشد.)


CategoryShell

پرسش و پاسخ 56 (‎ 2011-02-11 19:26:45 ‎ توسط GreyCat)