منبع اصلی نوشتار زیر در این لینک قرار دارد

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


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)