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

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

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

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

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


ssh کرانه‌های کلمات مرا می‌خورد! نمی‌توانم ‎ ssh remotehost make CFLAGS="-g -O"‎ را انجام بدهم!

ssh رفتار فرمان راه دور پوسته یونیکس (rsh یا remsh) شامل این باگ، را شبیه‌سازی می‌کند. چند روش برای عبور موقت موجود است، و به طور دقیق وابسته آنست که شما چه چیزی لازم دارید.

نخست، اینجا توضیح کاملی از مشکل است:

~$ ~/bin/args make CFLAGS="-g -O"
2 args: 'make' 'CFLAGS=-g -O'
~$ ssh localhost ~/bin/args make CFLAGS="-g -O"
Password: 
3 args: 'make' 'CFLAGS=-g' '-O'

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

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

~$ ssh localhost '~/bin/args make CFLAGS="-g -O"'
Password: 
2 args: 'make' 'CFLAGS=-g -O'

پوستهِ رویِ میزبان راه دور شناسه را مجدداً تجزیه می‌کند، آن را به کلمات می‌شکند، و سپس اجرا می‌نماید.

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

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

# POSIX
# .برای استفاده توسط برنامه راه دور در دسترس نخواهد بود‎
ssh remotehost sh <<EOF
make CFLAGS="-g -O"
EOF

حال اجازه بدهید مشکل واقع‌گرایانه‌تری را در نظر بگیریم: می‌خواهیم یک اسکریپت wrapper بنویسیم که make را روی میزبان راه دور با شناسه‌های فراهم شده توسط کاربر که دست نخورده همراه با آن عبور داده می‌شوند، فراخوانی می‌کند. این بسیار دشوارتر از آنست که در نگاه اول به نظر می‌رسد، به دلیل آنکه ما نمی‌توانیم همه چیز را در یک کلمه به یکدیگر بچسبانیم -- فراخواننده اسکریپت می‌تواند شناسه‌های واقعاً پیچیده، نقل‌قول‌ها، و نام مسیرهای شامل فاصله‌ها، و فوق‌کاراکترهای پوسته را استفاده کند، که لازم است تمام اینها به دقت حفاظت بشوند. خوشبختانه، bash روشی جهت محافظت از چنین مواردی به طور سالم برای ما فراهم نموده است: ‎printf %q‎. با یک آرایه و یک حلقه همراه یکدیگر، می‌توانیم یک wrapper بنویسم:

# Bash < 3.1
# sh باشد نه BASH پوسته حساب کاربری شما روی میزبان راه دور باید
unset a i
for arg; do
  a[i++]=$(printf %q "$arg")
done
exec ssh remotehost make "${a[@]}"

# Bash 3.1 and up
# sh باشد نه BASH پوسته حساب کاربری شما روی میزبان راه دور باید
unset a
for arg; do
  printf -v temp %q "$arg"
  a+=("$temp")
done
exec ssh remotehost make "${a[@]}"

# Bash 4.1 and up
# sh باشد نه BASH پوسته حساب کاربری شما روی میزبان راه دور باید
unset a i
for arg; do
  printf -v 'a[i++]' %q "$arg"
done
exec ssh remotehost make "${a[@]}"

اگر نیاز داشته باشیم که در میزبان راه دور قبل از اجرای make دایرکتوری را نیز تغییر بدهیم، می‌توانیم آن را هم اضافه کنیم:

# Bash < 3.1
# sh باشد نه BASH پوسته حساب کاربری شما روی میزبان راه دور باید
unset a i
for arg; do
  a[i++]=$(printf %q "$arg")
done
exec ssh remotehost cd "$PWD" "&&" make "${a[@]}"

(اگر ‎$PWD‎ شامل کاراکتر فاصله باشد، آنوقت لازم است آن نیز با همان ترفند ‎printf %q‎ محافظت گردد، که به عنوان تمرین به خواننده واگذار می‌گردد.)


CategoryShell

پرسش و پاسخ 96 (آخرین ویرایش ‎2012-06-18 13:16:12‎ توسط geirha)


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


میخواهم لیست خیلی بلند شناسه‌ها را دریافت کنم. چطور می‌توانم یک لیست بزرگ قابل توجه را پردازش نمایم؟

ابتدا، بیایید برخی موضوعات زمینه‌ای را بازبینی کنیم. موقعی که یک پردازش می‌خواهد پردازش دیگری را اجرا کند، یک فرزند ‎fork()‎(منشعب) می‌کند، و پردازش فرزند یکی از فراخوان‌های سیستمی خانواده ‎exec*‎ (مانند‎execve()‎)، را با دادن نام یا مسیر فایل برنامه پردازش جدید، نام پردازش جدید، لیست شناسه‌ها برای پردازش جدید، و در بعضی حالت‌ها، مجموعه‌ای از متغیرهای محیط، فراخوانی می‌کند. از این قرار:

  •  /* C */
     execlp("ls", "ls", "-l", "dir1", "dir2", (char *) NULL);

محدودیتی(به طور معمول) برای تعداد شناسه‌هایی که به این طریق می‌تواند عبور داده شود، وجود ندارد، اما در اکثر سیستم‌ها، حدی برای اندازه کل لیست، وجوددارد. برای جزئیات بیشتر، ‎http://www.in-ulm.de/~mascheck/various/argmax/‎ را ملاحظه کنید.

اگر شما در یک فراخوانی منفرد برنامه، سعی کنید (مثلاً) نام‌فایلهای بسیار زیادی را عبور بدهید، با موردی مشابه این مواجه می‌شوید:

  •  $ grep foo /usr/include/sys/*.h
     bash: /usr/bin/grep: Arg list too long

ترفندهای گوناگونی وجود دارد که می‌توانستید برای غلبه براین مورد به صورت تک موردی(فاقد عمومیت) به کار ببرید(اول تعویض دایرکتوری به ‎/usr/include/sys، و سپس استفاده از ‎grep foo *.h‎ برای کوتاه نمودن طول هر نام فایل...)، اما اگر راه‌کار کاملا نیرومندی لازم داشته باشید چطور؟

بعضی اشخاص در اینجا دوست دارند xargs را به کار ببرند، اما این مورد مسائل خطیری دارد. این فرمان با کاراکترهای فضای سفید و نقل‌قول در ورودی‌اش به عنوان جداکننده کلمات رفتار می‌کند، که آن را برای مدیریت صحیح نام‌فایلها، نالایق می‌سازد. (برای تشریح مطلب در این خصوص، صفحه کاربرد find را ببینید.)

اگر عمل بازگشتی مورد قبول است، می‌توانید به طور مستقیم find را استفاده کنید:

  •  find /usr/include/sys -name '*.h' -exec grep foo /dev/null {} +

اگر بازگشت قابل قبول نیست اما شما find گنو را دارید، می‌توانید این جایگزین غیرقابل‌حمل را به کار ببرید:

  • GNUfind /usr/include/sys -name '*.h' -maxdepth 1 -exec grep foo /dev/null {} +

(به یاد بیاورید که اگر grep بیش از یک نام فایل برای پردازش دریافت کند، تنها نام‌فایلها را چاپ خواهد کرد. پس، ما برای تضمین آنکه همواره حتی اگر ‎-exec‎ فقط یک نام به آن عبور دهد، حداقل دو نام‌فایل دارد، ‎/dev/null‎ را به عنوان نام فایل به آن عبور می‌دهیم.)

عمومی‌ترین پیشنهاد استفاده از آرایه Bash و حلقه‌ای برای پردازش در قطعات بزرگ است:

  •  # Bash
     files=(/usr/include/*.h /usr/include/sys/*.h)
     for ((i=0; i<${#files[*]}; i+=100)); do
       grep foo "${files[@]:i:100}" /dev/null
     done

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


CategoryShell

پرسش و پاسخ 95 (آخرین ویرایش ‎2010-01-06 14:23:30‎ توسط GreyCat)


اخطار در مورد کمبود فضای دیسک


می‌خواهم وقتی دیسک پُر می‌شود یک هشدار دریافت کنم(با تجزیه خروجی df).

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

نخست، بزرگترین مشکل با df آن است که در تمام سیستم‌عامل‌ها به صورت یکسان کار نمی‌کند. یونیکس به طور عمده به دو خانواده تقسیم می‌شود-- ‎System V‎ و BSD. در سیستم‌های خانواده BSD(در این وضعیت، شامل لینوکس)، df یک گزارش قابل خواندن انسانی ارائه می‌کند:

  •  ~$ df
     Filesystem           1K-blocks      Used Available Use% Mounted on
     /dev/sda2              8230432   3894324   3918020  50% /
     tmpfs                   253952         8    253944   1% /lib/init/rw
     udev                     10240        44     10196   1% /dev
     tmpfs                   253952         0    253952   0% /dev/shm

در حالیکه، در سیستم‌های هم‌خانواده با ‎System-V‎، خروجی به طور کامل متفاوت است:

  •  $ df
     /net/appl/clin   (svr1:/dsk/2/clin/pa1.1-hpux10HP-UXB.10.20):  1301728 blocks            -1 i-nodes
     /net/appl/tool-share (svr2:/dsk/4/dsk3/tool/share): 51100992 blocks       4340921 i-nodes
     /net/appl/netscape (svr2:/dsk/4/dsk3/netscape/pa1.1-hpux10HP-UXB.10.20): 51100992 blocks       4340921 i-nodes
     /net/appl/gcc-3.3 (svr2:/dsk/4/dsk3/gcc-3.3/pa1.1-hpux10HP-UXB.10.20): 51100992 blocks       4340921 i-nodes
     /net/appl/gcc-3.2 (svr2:/dsk/4/dsk3/gcc-3.2/pa1.1-hpux10HP-UXB.10.20): 51100992 blocks       4340921 i-nodes
     /net/appl/tool   (svr2:/dsk/4/dsk3/tool/pa1.1-hpux10HP-UXB.10.20): 51100992 blocks       4340921 i-nodes
     /net/home/wooledg    (/home/wooledg       ):   658340 blocks     87407 i-nodes
     /net/home            (auto.home           ):        0 blocks         0 i-nodes
     /net/hosts           (-hosts              ):        0 blocks         0 i-nodes
     /net/appl            (auto.appl           ):        0 blocks         0 i-nodes
     /net/vol             (auto.vol            ):        0 blocks         0 i-nodes
     /nfs                 (-hosts              ):        0 blocks         0 i-nodes
     /home                (/dev/vg00/lvol5     ):   658340 blocks     87407 i-nodes
     /opt                 (/dev/vg00/lvol6     ):   623196 blocks     83075 i-nodes
     /tmp                 (/dev/vg00/lvol4     ):    86636 blocks     11404 i-nodes
     /usr/local           (/dev/vg00/lvol9     ):   328290 blocks     41392 i-nodes
     /usr                 (/dev/vg00/lvol7     ):   601750 blocks     80228 i-nodes
     /var                 (/dev/vg00/lvol8     ):   110696 blocks     14447 i-nodes
     /stand               (/dev/vg00/lvol1     ):   110554 blocks     13420 i-nodes
     /                    (/dev/vg00/lvol3     ):   190990 blocks     25456 i-nodes

بنابراین، اولین سد راه شما، تشخیص آن خواهد بود که ممکن است نسبت به آنکه با کدام سیستم عامل کار می‌کنید، دستور متفاوتی لازم داشته باشید(به عنوان مثال bdf در HP-UX)، و شاید سیستم‌هایی باشند که واقعاً در آنها انجام این کار با اسکریپت پوسته به هیچ وجه امکان‌پذیر نباشد.

برای بقیه این مبحث، ما فرض خواهیم نمود که شما سیستمی با فرمان df هم‌خانواده BSD در اختیار دارید.

مشکل بعدی آن است که قالب خروجی df در تمام سکوها(پلاتفرم‌ها) یکسان نمی‌باشد.بعضی سکوها از خروجی شش ستونی و برخی از هفت ستونی استفاده می‌کنند. برخی سکوها(مانند لینوکس)، موقع گزارش فضای واقعی استفاده شده یا در دسترس، به طور پیش‌فرض بلوک‌های یک کیلوبایتی به کار می‌برند، دیگران، مانند OpenBSD یا IRIX، به طور پیش‌فرض بلوک‌های 512 بایت را به کار می‌برند و برای استفاده از کیلوبایت‌ها گزینه ‎-k‎ را لازم دارند.

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

  •  Filesystem           1K-blocks      Used Available Use% Mounted on
     ...
     svr2:/dsk/4/dsk3/tool/i686Linux2.4.27-4-686
                           35194552   7856256  25550496  24% /net/appl/tool

اگر نام دستگاه به اندازه کافی طولانی باشد(با فایل‌سیستم‌های متصل‌شده شبکه خیلی رایج است)، df ممکن است در کوشش برای حفظ خوانایی ستونها برای انسان، خروجی را به دو سطر تقسیم کند. یا شاید نکند...برای مثال، ‎OpenBSD 4.3‎ را ببینید:

  •  ~$ df
     Filesystem  512-blocks      Used     Avail Capacity  Mounted on
     /dev/wd0a       253278    166702     73914    69%    /
     /dev/wd0d      8121774   6904178    811508    89%    /usr
     /dev/wd0e      8121774   6077068   1638618    79%    /var
     /dev/wd0f       507230        12    481858     0%    /tmp
     /dev/wd0g      8121774   5653600   2062086    73%    /home
     /dev/wd0h    125253320 116469168   2521486    98%    /export
    
     ~$ sudo mount 192.168.2.5:/var/cache/apt/archives /mnt
     ~$ df
     Filesystem                          512-blocks      Used     Avail Capacity  Mounted on
     /dev/wd0a                               253278    166702     73914    69%    /
     /dev/wd0d                              8121774   6904178    811508    89%    /usr
     /dev/wd0e                              8121774   6077806   1637880    79%    /var
     /dev/wd0f                               507230        12    481858     0%    /tmp
     /dev/wd0g                              8121774   5653600   2062086    73%    /home
     /dev/wd0h                            125253320 116469168   2521486    98%    /export
     192.168.2.5:/var/cache/apt/archives    1960616   1638464    222560    88%    /mnt

اکثر نگارش‌های df گزینه ‎-P‎ را در اختیار شما قرار می‌دهند که به منظور میزان کردن خروجی است... نسبتاً. نگارش‌های قدیمی‌تر OpenBSD حتی موقعی که گزینه ‎-P‎ فراهم می‌شود، بازهم سطرهای خروجی را تقسیم می‌کنند، اما لینوکس به طور کلی خروجی برای هر فایل‌سیستم در یک سطر را تحمیل می‌کند.

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

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

  •  ~$ df -P
     Filesystem         1024-blocks      Used Available Capacity Mounted on
     /dev/hda1               180639     93143     77859      55% /
     tmpfs                   318572         4    318568       1% /dev/shm
     /dev/hda5                90297      4131     81349       5% /tmp
     /dev/hda2              5763648    699476   4771388      13% /usr
     /dev/hda3              1829190    334184   1397412      20% /var
     /dev/sdc1            2147341696 349228656 1798113040      17% /data3
     /dev/sde1            2147341696 2147312400     29296     100% /data4
     /dev/sdf1            1264642176 1264614164     28012     100% /data5
     /dev/sdd1            1267823104 1009684668 258138436      80% /hfo
     /dev/sda1            2147341696 2147311888     29808     100% /data1
     /dev/sdg1            1953520032 624438272 1329081760      32% /mnt
     /dev/sdb1            1267823104 657866300 609956804      52% /data2
     imadev:/home/wooledg   3686400   3336736    329184      92% /net/home/wooledg
     svr2:/dsk/4/dsk3/tool/i686Linux2.4.27-4-686  35194552   7856256  25550496      24% /net/appl/tool
     svr2:/dsk/4/dsk3/tool/share  35194552   7856256  25550496      24% /net/appl/tool-share

بنابراین، به طور واقعی چه کار می‌توانید بکنید؟

  • گزینه ‎-P‎ را به کار ببرید. حتی اگر همه چیز را ‎100%‎ سازگار نمی‌کند، به طور کلی آزاری ندارد. مطابق کُد منبع ‎df.c‎ در coreutils لینوکس، گزینه ‎ -P‎ تضمین می‌کند که خروجی در یک سطر منفرد خواهد بود(اما این فقط برای لینوکس است).

  • منطقه خود را به ‎C‎ تنظیم کنید. شما نیازی به سرآیند ستونهای غیر انگلیسی درهم پیچیده ندارید.

  • اگر معتبر است، استفاده از ‎"stat --file-system --format="‎ را در نظر بگیرید. اگر در موقعیت شما قابلیت حمل مسئله‌ای نیست، صفحه man فرمان stat را بررسی کنید. در سیستم‌های بسیاری قادر خواهید بود اندازه بلوک، تعداد کل بلوکهای دیسک، وتعداد بلوکهای آزاد، همگی در قالب تعیین شده کاربر، را چاپ کنید.

  • به طور صریح یک فایل‌سیستم را انتخاب نمایید. اگر نتایج مربوط به یک فایل‌سیستم را می‌خواهید از چنین دستوری ‎df -P | grep /dev/hda2‎ استفاده نکنید. نام یک شاخه یا یک دستگاه را به عنوان یک شناسه به فرمان df بدهید به این طریق فقط خروجی آن فایل‌سیستم را در مکان اول می‌بینید.

    •   ~$  df -P /
        Filesystem         1024-blocks      Used Available Capacity Mounted on
        /dev/sda2              8230432   3894360   3917984      50% /
  • شمارش کلمات خروجی بدون رعایت سطرهای‌جدید. این یک راه غلبه بر مشکل سطرهایی است که به طور غیرقابل پیش‌بینی تقسیم می‌شوند. برای مثال، استفاده از آرایه Bash با نام df_arr:

    •   ~$ read -d '' -ra df_arr < <(LC_ALL=C df -P /); echo "${df_arr[11]}"
        50%

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

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


CategoryShell

پرسش و پاسخ 94 (آخرین ویرایش ‎2013-08-10 20:27:02‎ توسط nrbg-4dbfc8a9)


تنظیم عنوان ترمینال


چطور می‌توانم محتویات نوار عنوان ترمینال خود را تنظیم نمایم؟

اگر ترمینالی دارید که رشته‌های escape سازگار با xterm را می‌شناسد، و شما می‌خواهید فقط یکبار عنوان آن را تنظیم کنید، می‌توانید از چنین تابعی استفاده کنید:

settitle() { printf '\e]2;%s\a' "$*"; }زیرنویس 1

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

trap 'printf "\e]2;%s\a" "$(HISTTIMEFORMAT='' history 1)" > /dev/tty' DEBUG

اگر چه، شماره فرمان تاریخچه را نیز در آنجا می‌نویسد، و در پوسته‌های فرعی صریح مانند ‎(cd foo && make)‎ راه‌اندازی نمی‌شود.

یا این مورد برای استفاده از فقط نام و شناسه‌های فرمان ساده جاری:

trap 'printf "\e]2;%s\a" "$BASH_COMMAND" > /dev/tty' DEBUG

برای پوسته‌های موافق Posix که ‎'\e'‎ را به عنوان رشته کاراکتری که به عنوان Escape تفسیر بشود، تشخیص نمی‌دهند شاید ‎'\x1b'‎ به جای آن جایگزین بشود.


CategoryShell

پرسش و پاسخ 93 (آخرین ویرایش ‎2011-04-03 15:10:11‎ توسط adsl-75-61-109-235)


  1. مترجم: سپس عنوان مورد نظر را به صورت شناسه، در فراخوانی تابع به کار ببرید. (1)


نوشتن اسکریپت CGI !!


چگونه یک اسکریپت CGI بنویسم که پارامترها را بپذیرد؟

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

یک برنامه CGI می‌تواند با پارامترهایی که توسط مرورگر شبکه ارسال گردیده، فراخوانی بشود . دو روش(حداقل) برای فراخوانی برنامه CGI وجود دارد: شیوه "GET" و شیوه "POST" . در شیوه "GET" ، پارامترها در یک متغیر محیطی به نام ‎QUERY_STRING‎ برای برنامه CGI فراهم می‌شوند. پارامترها قالب تعاریف ‎KEY=VALUE‎ را می‌گیرند(یعنی ‎user=george‎)، با برخی کاراکترهایی که به هگزادسیمال کدگذاری شده‌اند، فاصله‌ها به عنوان علامت به‌اضافه کُد شده‌اند، و همه آنها با کاراکترهای & به یکدیگر متصل شده‌اند. در شیوه "POST" ، در عوض پارامترها در ورودی استاندارد فراهم می‌شوند.

البته اینک ما می‌دانیم که شما هرگز نمی‌خواهید یک اسکریپت CGI در Bash بنویسید. بنابراین برای اهداف مورد نظر این مدخل، ما فرض خواهیم نمود که تروریستها همسر و فرزندان شما را دزدیده‌اند و اگر شما تقاضای آنان را درنوشتن چنین اسکریپتی اجابت نکنید، آنها را شکنجه می‌کنند، ضرب و جرح می‌سازند، و می‌کشند، «یا وخیم‌تر».

(موقعیت «یا وخیم‌تر» شاید به وضوح چیزی مشابه مجبور کردن شما به استفاده از نرم‌افزارهای مایکروسافت باشد.)

بنابراین، برای یک متغیر معین ‎QUERY_STRING‎، شاید بخواهیم کلیدها(متغیرها) و مقادیر آنها را استخراج کنیم، به طوری که بتوانیم آنها را در اسکریپت به کار ببریم.

روش سریع، آسان، و خطرناک برای پردازش QUERY_STRING، تبدیل &ها به ;ها و سپس استفاده از فرمان eval برای انجام تخصیص آنها می‌باشد. به هر حال استفاده از eval به شدت دلسرد کننده است. این است که ما همیشه می‌گوییم، اگر راه دیگری برای انجام آن وجود دارد، از eval پرهیز نمایید.

روش خطرناک

#  cgi در خواندن رشته ورودی در
if [ "$QUERY_STRING" ]; then
  foo=$QUERY_STRING
else
  read foo
fi

# (‏می‌ماند برای  تمرین  خواننده)‎ ‎"&"‎ تبدیل مقداری رشته کُد شده و مواردی مانند‎

# روی رشته eval اجرای‎
eval $foo

# ‏را در یک فیلد فرم وب قرار ‎"/bin/rm -rf /"‎ کنار بنشینید و تماشاکنید، کاربر‎
# .نباشد می‌تواند به بخشی از سیستم‌فایل صدمه وارد کند root داده است، که حتی اگر‎
# .یک رشته خطرناک دیگر می‌ تواند بمب خوشه‌ای باشد

روش ایمن

به جای گفتن آنکه پوسته هر کُدِ فراهم شده توسط کاربر در پارامترها را اجرا کند، رویکرد بهتر استخراج هر زوج متغیر/مقدار، و تخصیص آنها در متغیرهای پوسته، به طور یک به یک، بدون اجرای آنها می‌باشد. این کار یک تخصیص متغیر غیر مستقیم نیاز دارد، که به معنی استفاده از بعضی ترفندکاری‌های مختص پوسته می‌باشد. ما این ترفند را با ترکیب دستوری Bash می‌نویسیم، تبدیل آن به پوسته ksh یا Bourne به عنوان تمرین واگذار می‌شود.

# Bash

# cgi  خواندن رشته ورودی در
if [ "$QUERY_STRING" ]; then
  foo=$QUERY_STRING
else
  read -r foo
fi

#  می‌باشد name=Fred+Flintstone&city=Bedrock شامل موردی مشابه foo‎
#  متصل شده‌اند رفتار می‌کند ‎&‎ که با key=value این مانند یک لیست از عبارتهای‎
#  تکرار روی عناصر لیست و انجام عمل تخصیص  هریک 

IFS='&'; set -f
for i in $foo; do
    declare "$i"
done
unset IFS

# یک متغیر پوسته با همان نام  خواهد شد CGI اکنون هر پارامتر‎
# .بهتر است شما بدانید نامها کدام هستند، چون آنها را پیگردی نمی‌کنیم‎
#  است. فاصله به عنوان + کُد شده است "urlencoded" هر متغیر  بازهم ‎
# هگزادسیمال است ‎xx‎ کُد شده‌اند که ‎%xx‎‎‎ اقلام مختلف به عنوان‎

# را به کار ببریم ‎"name"‎ فرض کنید می‌خواهیم پارامتری به نام‎

# .اول رمزگشایی فاصله‌ها‎

name=${name//+/ }

# ما ترفند دیگری برای انجام آن به کار می‌بریم ‎%xx‎  اکنون رمز گشایی کاراکترهای‎
# ‎تعویض می‌کنیم \x‎ را با  ‎%‎ اول تمام علائم ‎
# ‎ها می‌شود\xxx‎ را به کار می‌بریم برای آنکه موجب ارزیابی تمام ‎echo -e‎ دوم، دستور

name=${name//\%/\\x}
name=$(echo -e "$name")

# این کار را قبل از تکرار و تخصیص حلقه انجام ندادیم چون اگر این کار را می‌کردیم‎
# کُد شده (یا هر کاراکتر بدخواهانه)‏ است ‎&‎ آنوقت یک پارامتر که شامل یک کاراکتر‎
# .موجب اندوه بسیار خواهد شد. ما باید این کار را در اینجا انجام بدهیم ‎

# انجام بدهید ‎"name"‎ حالا هر کاری مایل هستید با

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

در این نگارش هنوز کاستی‌هایی وجود دارد. برای مثال، ما برای تضمین معتبر یا ایمن بودن نام متغیر پوسته، هیچگونه اعتبارسنجی روی طرف چپ(نام متغیر) در هر زوج ‎key=value‎ انجام نمی‌دهیم. اگر کاربر یک ‎PATH=‎ را در یک پارامتر استعلام وارد کند چه؟

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

حتی یک رویکرد بهتر، می‌تواند قرار دادن زوج‌های کلید\متغیر داخل یک آرایه انجمنی باشد. آرایه‌های انجمنی در ksh93 و ‎bash 4.0‎ معتبر هستند، اما در POSIX یا پوسته‌های Bourne خیر. آنهابرای نگهداری زوج‌های ‎key/value‎ طراحی شده‌اند که در آن کلیدها می‌توانند رشته‌های اختیاری باشند، بنابراین به نظر می‌رسد برای این کار مناسب هستند.

# Bash 4+

#  cgi خواندن رشته ورودی‎
if [ "$QUERY_STRING" ]; then
  foo=$QUERY_STRING
else
  read -r foo
fi

# .برقراری آرایه انجمنی برای نگهداری پارامترهای پرس وجو‎
declare -A q

# ‎key=value+%41%42%43‎ تکرار روی عناصر‎
# .جدا سازی کلید و کمیت، و انجام رمزگشایی کمیت‎
IFS='&'; set -f
for i in $foo; do
    IFS='=' read key value <<< "$i"

    # ‎هاbackslash‎ حذف  ‎--‎ اول پاک سازی :‎ مراحل رمزگشایی ‎
    # .دوم، علامت‌های بعلاوه تبدیل به فاصله می‌شوند‎
    # .می‌شوند ‎\x‎ سوم، علائم درصدتبدیل به ‎
    # .را موجب گردد printf چیزی باقی نمی‌گذارد که بتواند به طور غیر منتظره یک بسط ‎‎
    # .ها مال ما هستند و هیچ علامت درصد باقی نماندهbackslashe تمام ‎

    value=${value//\\/}
    value=${value//+/ }
    value=${value//\%/\\x}
    printf -v final -- "$value"
    q["$key"]="$final"
done
unset IFS

# .به کار ببریم q اکنون می‌توانیم پارامترها را از آرایه انجمنی با نام ‎
#  ‎${!q[*]}‎ اگر لیستی از کلیدها را لازم داشته باشیم، این است‎

در اینجا مرحله پاکسازی بینهایت مهم است. بدون آن اقدام احتیاطی، فرمان printf می‌تواند به واسطه یک رشته قالب‌بندی مهاجم، آسیب پذیر باشد. گزینه ‎printf -v varname‎ در هر نگارش از bash که از آرایه‌های انجمنی پشتبانی کند، معتبر است، بنابراین می‌توانیم از آن در اینجا استفاده کنیم. خیلی بیشتر از فراخوانی پوسته فرعی مؤثر است. همچنین از مشکلات بالقوه‌ ‎echo -e‎ در صورتی که اتفاقاً value موردی مانند ‎-n‎ باشد، اجتناب نموده‌ایم.

به طور تکنیکی، خصوصیات CGI چندین نمونه از یک کلید را در یک استعلام منفرد اجازه می‌دهد. برای مثال، ‎group=managers&member=Alice&member=Charlie‎ یک رشته کاملاً مشروع پرس وجو می‌باشد. هیچ یک از رویکردها در این صفحه این حالت را مدیریت نمی‌کند(حداقل نه به طریقی که ما احتمالاً آنرا روش صحیح در نظر بگیریم). خوشبختانه، غالباً اینطور نیست که شما بخواهید یک اسکریپت CGI مانند این بنویسید، و در هر حال، شما مجبور نیستید برای انجام این وظیفه از bash استفاده کنید.


CategoryShell

پرسش و پاسخ 92 (آخرین ویرایش ‎2010-04-16 23:58:26‎ توسط GreyCat)