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

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

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

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

رفتار 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)


انواع redirection و تفاوت آنها


به طور کلی در باره ‎2>&1‎ بگویید -- تفاوت بین ‎2>&1 >foo‎ و ‎>foo 2>&1‎ چیست، و چه‌وقت می‌توانم از هرکدام استفاده کنم؟

تمام پردازشهای Bash به ترتیب از چپ به راست تغییر مسیر داده می‌شوند. و ترتیب معنا دار است. انحراف از آن در یک فرمان ممکن است نتایج آن فرمان را تغییر بدهد.

اگر تمام آنچه می‌خواهید ارسال هر دو خروجی استاندارد و خطای استاندارد به یک فایل است، این مورد را به کار ببرید:

# Bourne
foo >file 2>&1          # هر دو را به فایل ارسال می‌کند stdout و stderr 

این هم یک نمایش ساده از آنچه اتفاق می‌افتد:

# POSIX
foo() {
  echo "This is stdout"
  echo "This is stderr" 1>&2
}
foo >/dev/null 2>&1             # هیج خروجی ارائه نمی‌کند 
foo 2>&1 >/dev/null             # "This is stderr" در نمایشگر می‌نویسد

چرا نتایج اختلاف دارند؟ در حالت اول،‎>/dev/null‎ ابتدا انجام می‌شود، و بنابراین، خروجی استاندارد فرمان به ‎ /dev/null‎ فرستاده می‌شود. سپس، ‎ 2>&1‎ انجام می‌شود، که باعث می‌شود استاندارد خطا به همان جایی ارسال شود که خروجی استاندارد از قبل می‌رفت. بنابراین هر دو خروجی، دور انداخته می‌شوند.

در مثال دوم،‎ 2>&1‎ اول انجام می‌شود. این به معنای آن است که ابتدا خطای استاندارد به جایی ارسال می‌شود که خروجی استاندارد به آنجا می‌رود --در این حالت، به ترمینال کاربر. پس از آن، خروجی استاندارد به ‎ /dev/null‎ فرستاده می‌شود و بنابراین دور انداخته می‌شود. از آن جهت موقعی که ما foo را دفعه دوم اجرا می‌کنیم، فقط خطای استانداردش را می‌بینیم، نه خروجی استاندارد آن را.

فصل تغییر مسیر در راهنما شرح می‌دهد که چرا ما دونسخه‌ای نمودن توصیف‌گر فایل را به جای دوبار باز کردن ‎ /dev/null‎ به کار می‌بریم. در حالت خاصِ ‎ /dev/null ‎  واقعاً مسئله‌ای نیست زیرا تمام نوشته‌ها رها می‌شوند، اما موقعی که ما در فایل ثبت وقایع می‌نویسیم، به راستی خیلی زیاد با اهمیت است.

مواقعی هست که ما به راستی می‌خواهیم ‎ 2>&1‎ اول ظاهر شود -- برای یک مثال از این مورد، پرسش و پاسخ شماره 40 را ببینید.

مواقع دیگری هست که شاید از ‎ 2>&1‎ بدون هر تغییر مسیر دیگری استفاده کنیم. ملاحظه نمایید:

# Bourne
find ... 2>&1 | grep "some error"

در این مثال، می‌خواهیم خطای استاندارد find را (به علاوه خروجی استاندارد آن)برای رشته "some error" جستجو کنیم. ‎2>&1‎ در فرمان لوله‌کشی شده خطای استاندارد را مجبور می‌کند همراه با خروجی استاندارد به داخل لوله برود. (موقعی که لوله‌ها و تغییر مسیرها به این طریق مختلط می‌شوند، به خاطر بیاورید: لوله اول قبل از هر تغییر مسیری انجام می‌شود. بنابراین خروجی استاندارد find قبلاً برای اشاره کردن به لوله تنظیم گردیدهاست، پیش از اینکه ما تغییر مسیر ‎ 2>&1‎ را پردازش کنیم.)

اگر ما می‌خواستیم فقط خطای استاندارد را در لوله بخوانیم، و خروجی استاندارد را رها کنیم، می‌توانستیم چنین کنیم:

# Bourne
find ... 2>&1 >/dev/null | grep "some error"

تغییر مسیرها در این مثال بدین گونه پردازش می‌شوند:

  1. اول، لوله ایجاد می‌شود. خروجی find به آن ارسال می‌شود.

  2. بعد، ‎2>&1‎ باعث می‌شود خطای استاندارد find نیز به لوله برود.

  3. عاقبت، ‎>/dev/null‎ موجب می‌گردد خروجی استاندارد findدور انداخته شود، فقط خطای استاندارد اجازه رفتن به لوله دارد.

یک پرسش مرتبط پرسش و پاسخ شماره 47 است، که چگونگی ارسال stderr به خط لوله را بحث می‌کند.

برای یک توضیح بیشتر گرافیکی قابل درک نمودن عملگر کپی توصیف‌گر فایل را ببینید.

اگر بازهم سر در گم هستید...

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

بسیاری اشخاص تصور می‌کنند که ‎ 2>&1‎ یک‌جور «به هم پیوستن» یا «گره زدن با هم» یا « ازدواج کردن» دو توصیف‌گر فایل است، به طوریکه هر تغییر در یکی از آنها تغییری برای دیگری می‌شود. این حالت نیست. و برای بسیاری افراد، این همان جایی است که گیج شدن از آن حاصل می‌شود.

2>&1‎ فقط FD2 را تغییر می‌دهد تا به "آن جایی که FD1 در آن لحظه به آن اشاره می‌کند" اشاره نماید، در واقع باعث نمی‌شود که FD2 به خود FD1 اشاره کند. توجه نمایید که "2" و "1" به سبب روشی که به کار بروند، معانی مختلفی می‌گیرند: "2"، وقتی قبل از ‎ ">&"‎ واقع می‌شود FD2 واقعی معنی می‌دهد، اما "1" که بعد از‎ ">&"‎ واقع می‌شود، به جای خود FD1، به معنی «جایی که FD1 در حال حاضر به آن اشاره می‌کند» است. (اگر برعکس باشد، همچون ‎ "1>&2"‎، آنوقت 1به معنی خود FD1 می‌شود، و 2 به معنی «جایی که FD2 در حال حاضر به آن اشاره می‌کند» خواهد بود.)

شاید قیاس‌ها کمک کنند. یک قیاس در نظر گرفتن FDها به عنوان اشاره‌گرهای C است.

  int some_actual_integer;
  int *fd1, *fd2;

  fd1 = &some_actual_integer;     /* 1>file قابل مقایسه با*/
  fd2 = fd1;                       /*  2>&1 قابل مقایسه با*/
  fd1 = NULL;                      /*  1>&- قابل مقایسه با‎*/

   /* هنوز به مکان واقعی حافظه اشاره می‌کند fd2 در این نقطه‎
      هر دو باید به  یک نقطه اشاره کنند fd2 و fd1 این واقعیت که‎
     مناسب نیست. می‌توانیم یکی از آنها را ببندیم یا دوباره باز کنیم‎
      بدون تأثیر بر دیگری */

یک قیاس دیگر در نظر گرفتن آنها مانند hardlinkها در سیستم فایل است .

   touch some_real_file
   ln some_real_file fd1  # یک لینک به فایل ما ایجاد می‌کند fd1
   ln fd1 fd2             # لینک دیگری به فایل ما ایجاد می‌کند fd2
   rm fd1                 # تحت تأثیر قرار نمی‌گیرد fd2 حذف می‌شود اما لینک fd1 لینک‎

   # "some_real_file":در این نقطه ما بازهم یک فایل با دو لینک داریم‎
   #  "fd2"و

یا مانند پیوندهای نمادین -- اما با این قیاس باید محتاط باشیم.

   touch some_real_file
   ln -s some_real_file fd1    # را پیوند نمادین از فایل ما می‌سازد fd1‎
   ln -s "$(readlink fd1)" fd2 # را به همان فایلی پیوند نمادین  می‌سازد که fd2 ‎
                               # .به آن لینک نمادین است‎ fd1
   rm fd1                      # .دست نخورده می‌ماند fd2 را حذف می‌کند اما fd1‎

   # .را لازم دارد "readlink" برنامه غیر استاندارد‎
   # :نتیجه عبارت است از‎

   lrwxrwxrwx 1 wooledg wooledg 14 Mar 25 09:19 fd2 -> some_real_file
   -rw-r--r-- 1 wooledg wooledg  0 Mar 25 09:19 some_real_file

   # را استفاده کنیم، به طور ‎"ln -s fd1 fd2"‎ اگر ما سعی می‌کردیم در این مقایسه‎
   #    ها نیست، بلکه چگونگی تصورFD نامساعدی شکست می‌خوردیم. این چگونگی کارکرد‎
   #                    .برخی افراد در باره کار آنها می‌باشد. و این اشتباه است‎

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


CategoryShell

پرسش و پاسخ 55 (آخرین ویرایش ‎ 2011-08-23 17:34:06 ‎ توسط S010600032d00065e)


تعیین آنکه ورودی عدد باشد


چگونه می‌توانم بگویم که یک متغیر آیا محتوی یک عدد معتبر هست؟

اول، شما باید معین کنید مقصود شما از «عدد» چیست. در اکثر حالت‌های رایج که مردم این مورد را سؤال می‌کنند، به نظر می‌رسد منظور «یک عدد صحیح غیر منفی، بدون علامت + » است. یا به بیان دیگر رشته‌ای از تمام ارقام. سایر اوقات، افراد می‌خواهند یک ورودی ممیز شناورِ با علامت و نقطه اعشار اختیاری را تعیین اعتبار نمایند.

تفکیک دستی

اگر شما در حال اعتبار سنجی ساده «رشته‌ای از ارقام» می‌باشید، می‌توانید آن را با glob انجام بدهید:

# Bash
if [[ $foo != *[!0-9]* ]]; then
    echo "'$foo' is strictly numeric"
else
    echo "'$foo' has a non-digit somewhere in it"
fi

همان کار با کاربرد case می‌تواند در پوسته‌های Korn و POSIX به خوبی انجام شود:

# ksh, POSIX
case "$foo" in
    *[!0-9]*) echo "'$foo' has a non-digit somewhere in it" ;;
    *) echo "'$foo' is strictly numeric" ;;
esac

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

# Bash --های توسعه یافته باید فعال شوندglob ‎
# .کنترل می‌کند که آیا متغیر تماماً متشکل از ارقام است‎
shopt -s extglob
[[ $var == +([0-9]) ]]

یک حالت پیچیده‌تر:

# Bash
shopt -s extglob
[[ $foo = *[0-9]* && $foo = ?([+-])*([0-9])?(.*([0-9])) ]] &&
  echo "foo is a floating-point number"

تست مقدم بر ‎ $foo‎ برای اطمینان از وجود حداقل یک رقم می‌باشد. glob توسعه‌یافته، به تنهایی رشته تهی، یا + یا - تنها را می‌تواند مطابقت کند، که شاید رفتار مطلوب نباشد.

پوسته Korn به طور پیش‌فرض globهای توسعه‌یافته فعال دارد، اما فاقد ‎ [[‎ است، بنابراین برای انجام انطباق جانشین باید از case استفاده کنیم:

# Korn
case $foo in
  *[0-9]*)
    case $foo in
        ?([+-])*([0-9])?(.*([0-9]))) echo "foo is a number";;
    esac;;
esac

توجه نمایید که این کُد از همان glob توسعه‌یافته استفاده می‌کند که Bash در مثال قبل به کار برد، سومین پرانتز بسته، در انتهای آن در حقیقت بخشی از ترکیب دستوری case می‌باشد.

اگر تعریف شما از «عدد معتبر» حتی پیچیده‌تر است، یا اگر نیاز به راه حلی دارید که در پوسته‌های موروثی Bourne کار کند، شاید ترجیح بدهید از ترکیب دستوری عبارت منظمِ یک ابزار خارجی استفاده کنید. نگارش قابل حمل(که در اینجا به طور تفصیلی تشریح شده)، با استفاده از egrep:

# Bourne
if echo "$foo" | egrep '^[-+]?([0-9]+\.?|[0-9]*\.[0-9]+)$' >/dev/null
then
    echo "'$foo' is a number"
else
    echo "'$foo' is not a number"
fi

Bash نگارش 3 و بالاتر در فرمان‎ [[ ‎ دارای پشتیبانی از عبارت منظم می‌باشد. به سبب باگها و تغییرات در پیاده‌سازی ویژگی ‎ =~‎ در ‎bash 3.x‎، ما استفاده از آن را پیشنهاد نمی‌کنیم، اما در هر صورت افراد از آن استفاده می‌کنند، بنابراین ما باید در اینجا این مثال را حفظ کنیم (و این هشدار بازدارنده را نیز نگاه داریم، تا وقتی مردم آن را حذف کنند):

# Bash
# عبارت منظم را در متغیر قرارمی‌دهیم ‎<3.2‎ برای سازگاری معکوس با نگارشهای 
regexp='^[-+]?[0-9]*(\.[0-9]*)?$'
if [[ $foo = *[0-9]* && $foo =~ $regexp ]]; then
    echo "'$foo' looks rather like a number"
else
    echo "'$foo' doesn't look particularly numeric to me"
fi

استفاده از تفکیک انجام شده توسط‎ [‎ و printf(یا استفاده از eq)

# ناموفق است ksh با 
if [ "$foo" -eq "$foo" ] 2>/dev/null;then
 echo "$foo is an integer"
fi

[‎ به علت وجود ‎-eq‎ متغیر را تفکیک می‌کند و آن را به عنوان عدد صحیح تفسیر می‌نماید. اگر تفکیک موفق باشد به طور بدیهی بررسی صحیح است، اگر ناموفق باشد ‎ [‎ یک پیغام خطا چاپ می‌کند که ‎ 2>/dev/null‎ آنرا پنهان می‌کند و یک کد وضعیت غیر صفر را تنظیم می‌کند. به هر حال این شیوه در پوسته ksh ناموفق می‌شود، زیرا ksh متغیر را به عنوان یک عبارت حسابی ارزیابی می‌کند.

می‌توانید از ترفند مشابهی با printf استفاده کنید:

# POSIX
if printf "%f" "$foo" >/dev/null 2>&1; then
  echo "$foo is a float"
fi

می‌توانید از ‎ %d‎ برای تجزیه یک عدد صحیح استفاده کنید. مراقب باشید که تجزیه می‌تواند وابسته به منطقه باشد(آیا تصور می‌رود که باشد؟).

استفاده از ‎ ((...))‎ برای تعیین اینکه ورودی صحیح است

if (("$foo")) >/dev/null 2>&1; then
  echo "$foo is an integer"
fi

دستور ‎ (($foo))‎ سعی می‌کند foo را به عنوان یک عبارت حسابی صحیح تعیین کند، که اگر foo یک عدد صحیح نباشد یک کد خطای غیر صفر برمی‌گرداند.

پرسش و پاسخ 54 (آخرین ویرایش ‎ 2012-12-02 11:27:59 ‎ توسط geirha)