it-swarm.asia

الأمر eval في باش واستخداماته النموذجية

بعد قراءة صفحات رجل باش وفيما يتعلق بهذا بعد .

ما زلت أواجه مشكلة في فهم ما يفعله الأمر eval والذي سيكون استخداماته المعتادة. على سبيل المثال إذا فعلنا:

bash$ set -- one two three  # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n}       ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n)       ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one

ما الذي يحدث بالضبط هنا وكيف يوقع الدولار والخط المائل العكسي في المشكلة؟

143
kstratis

eval تأخذ السلسلة كحجة لها ، وتقوم بتقييمها كما لو كنت قد كتبت هذه السلسلة في سطر الأوامر. (إذا قمت بتمرير عدة وسيطات ، فسيتم ربطها أولاً بمسافات بينها.)

${$n} هو خطأ في بناء الجملة في bash. داخل الأقواس ، لا يمكن أن يكون لديك سوى اسم متغير ، مع بعض البادئات واللاحقات المحتملة ، لكن لا يمكنك استخدام صيغة bash التعسفية ، ولا يمكنك استخدام التوسيع المتغير بشكل خاص. هناك طريقة لقول "قيمة المتغير الذي اسمه في هذا المتغير" ، على الرغم من:

echo ${!n}
one

$(…) تقوم بتشغيل الأمر المحدد داخل الأقواس في مجموعة فرعية (أي في عملية منفصلة ترث جميع الإعدادات مثل القيم المتغيرة من Shell الحالي) ، وتجمع ناتجها. لذلك echo $($n) تعمل $n كأمر Shell ، وتعرض إخراجها. نظرًا لأن $n يتم تقييمها على 1 ، فإن $($n) تحاول تشغيل الأمر 1 ، وهو غير موجود.

eval echo \${$n} يعمل على تشغيل المعلمات التي تم تمريرها إلى eval. بعد التوسع ، تكون المعلمتان echo و ${1}. إذن eval echo \${$n} تدير الأمر echo ${1}.

لاحظ أنه في معظم الأوقات ، يجب عليك استخدام علامات الاقتباس المزدوجة حول البدائل المتغيرة وبدائل الأوامر (أي في أي وقت هناك $): "$foo", "$(foo)". ضع دائمًا علامتي اقتباس حول المتغير وبدائل الأوامر ، إلا إذا كنت تعلم أنك بحاجة إلى تركها. بدون علامات الاقتباس المزدوجة ، تقوم شركة Shell بتقسيم المجال (بمعنى أنه يقسم قيمة المتغير أو الإخراج من الأمر إلى كلمات منفصلة) ثم يعامل كل كلمة كنموذج بدل. فمثلا:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *

eval لا يستخدم كثيرًا. في بعض الأصداف ، يكون الاستخدام الأكثر شيوعًا هو الحصول على قيمة المتغير الذي لم يعرف اسمه حتى وقت التشغيل. في bash ، هذا ليس ضروريًا بفضل بناء الجملة ${!VAR}. eval لا يزال مفيدًا عندما تحتاج إلى إنشاء عوامل تشغيل تحتوي على أوامر أطول ، والكلمات المحجوزة ، إلخ.

178
Gilles

ببساطة فكر في eval كـ "تقييم تعبيرك مرة واحدة إضافية قبل التنفيذ"

eval echo \${$n} تصبح echo $1 بعد الجولة الأولى من التقييم. ثلاثة تغييرات لاحظت:

  • أصبح \$$ (الخط المائل العكسي مطلوبًا ، وإلا فإنه يحاول تقييم ${$n} ، مما يعني أنه متغير اسمه {$n} ، وهو غير مسموح به)
  • $n تم تقييمه على 1
  • اختفى eval

في الجولة الثانية ، يتم بشكل أساسي echo $1 والتي يمكن تنفيذها مباشرة.

لذلك eval <some command> ستقوم أولاً بتقييم <some command> (يعني التقييم هنا أقصد المتغيرات البديلة ، واستبدل الأحرف المتجاوزة بالأحرف الصحيحة وغيرها) ، ثم قم بتشغيل التعبير الناتج مرة أخرى.

يتم استخدام eval عندما تريد إنشاء متغيرات ديناميكية ، أو لقراءة المخرجات من البرامج المصممة خصيصًا ليتم قراءتها بهذه الطريقة. انظر http://mywiki.wooledge.org/BashFAQ/048 للحصول على أمثلة. يحتوي الرابط أيضًا على بعض الطرق النموذجية التي يتم بها استخدام eval والمخاطر المرتبطة به.

33
Hari Menon

في تجربتي ، يكون الاستخدام "النموذجي" لـ eval هو تشغيل الأوامر التي تنشئ أوامر Shell لتعيين متغيرات البيئة.

ربما لديك نظام يستخدم مجموعة من متغيرات البيئة ، ولديك برنامج نصي أو برنامج يحدد المتغيرات التي يجب تعيينها وقيمها. كلما قمت بتشغيل برنامج نصي أو برنامج ، يتم تشغيله في عملية متشعب ، لذلك يتم فقد أي شيء يقوم به مباشرة لمتغيرات البيئة عند الخروج. ولكن يمكن لهذا البرنامج النصي أو البرنامج إرسال أوامر التصدير إلى stdout.

بدون eval ، ستحتاج إلى إعادة توجيه stdout إلى ملف مؤقت ، وإصدار ملف temp ، ثم حذفه. مع eval ، يمكنك فقط:

eval "$(script-or-program)"

لاحظ أن الاقتباسات مهمة. خذ هذا المثال (مفتون):

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!
22
sootsnoot

يخبر بيان eval Shell بأن يأخذ حجج eval كأمر ويديرها عبر سطر الأوامر. إنه مفيد في موقف مثل:

في البرنامج النصي الخاص بك إذا كنت تقوم بتعريف أمر ما إلى متغير وبعد ذلك تريد استخدام هذا الأمر ، فعليك استخدام eval:

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >
7
Nikhil Gupta

في الأصل لم أكن أتعلم عمدا كيفية استخدام eval ، لأن معظم الناس سوف يوصون بالابتعاد عنه مثل الطاعون. ومع ذلك ، فقد اكتشفت مؤخرًا حالة استخدام جعلتني facepalm لعدم التعرف عليها عاجلاً.

إذا كانت لديك وظائف cron تريد تشغيلها بشكل تفاعلي للاختبار ، فيمكنك عرض محتويات الملف باستخدام cat ، ونسخ مهمة cron ولصقها لتشغيلها. لسوء الحظ ، هذا ينطوي على لمس الماوس ، وهو خطيئة في كتابي.

لنفترض أن لديك وظيفة cron في /etc/cron.d/repeatme مع المحتويات:

*/10 * * * * root program arg1 arg2

لا يمكنك تنفيذ هذا كنص برمجي مع كل الزبالة الموجودة أمامه ، ولكن يمكننا استخدام القص للتخلص من كل الزبالة ، ولفها في غلاف فرعي ، وتنفيذ السلسلة بإيفال

eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)

يقوم الأمر cut فقط بطباعة الحقل السادس من الملف ، محددًا بمسافات. Eval ثم ينفذ هذا الأمر.

لقد استخدمت وظيفة cron هنا كمثال ، ولكن المفهوم هو تنسيق النص من stdout ، ثم تقييم هذا النص.

استخدام eval في هذه الحالة ليس غير آمن ، لأننا نعرف بالضبط ما سوف نقوم بتقييمه قبل اليد.

1
Luke Pafford

لقد سألت عن الاستخدامات النموذجية.

إحدى الشكاوى الشائعة حول البرمجة النصية لـ Shell هي أنه (يُزعم) أنك لا تستطيع المرور بالرجوع إلى استرداد القيم من الوظائف.

ولكن في الواقع ، من خلال "eval" ، يمكنك يمكنك تمرير بالرجوع إليها. يمكن للمدرب أن يمرر قائمة بالواجبات المتغيرة ليتم تقييمها من قبل المتصل. يتم تمريره بالرجوع لأنه يمكن للمتصل تحديد اسم (أسماء) المتغير (المتغيرات) للنتيجة - انظر المثال أدناه. يمكن أن يتم تمرير نتائج الخطأ الأسماء القياسية مثل errno و errstr.

فيما يلي مثال للمرور بالإشارة في bash:

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

الإخراج يشبه هذا:

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

يوجد تقريبا غير محدود عرض النطاق في إخراج النص! وهناك المزيد من الاحتمالات إذا تم استخدام خطوط الإخراج المتعددة: على سبيل المثال ، يمكن استخدام السطر الأول للواجبات المتغيرة ، والثاني "دفق الفكر" المستمر ، ولكن هذا خارج نطاق هذا المنشور.

0
Craig Hicks

لقد اضطررت مؤخرًا إلى استخدام eval لفرض توسيع التوسعات متعددة الأقواس في الترتيب حسب الاحتياج. باش يفعل توسعات متعددة للقوس من اليسار إلى اليمين ، هكذا

xargs -I_ cat _/{11..15}/{8..5}.jpg

يتوسع إلى

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

لكنني كنت في حاجة إلى التوسع هدفين الثاني القيام به أولا ، العائد

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

أفضل ما يمكن أن أتوصل إليه للقيام بذلك كان

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

يعمل هذا لأن علامات الاقتباس المفردة تحمي المجموعة الأولى من الأقواس من التمدد أثناء تحليل سطر الأوامر eval ، تاركةً ليتم توسيعها عن طريق الشريحة الفرعية التي تم استدعاؤها بواسطة eval.

قد يكون هناك بعض المخططات المذهلة التي تتضمن توسعات دعامة متداخلة تسمح بحدوث ذلك في خطوة واحدة ، ولكن إذا كان لديّ شيخوخة غبية ولا أستطيع رؤيتها.

0
flabdablet

أحب إجابة "تقييم تعبيرك مرة واحدة قبل التنفيذ" ، وأود توضيح ذلك بمثال آخر.

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

النتائج الغريبة في الخيار 2 هي أننا سنتجاوز معلمتين كما يلي:

  • المعلمة الأولى: "value
  • المعلمة الثانية: content"

كيف يتم ذلك لمواجهة بديهية؟ سوف eval إضافية إصلاح ذلك.

مقتبس من https://stackoverflow.com/a/40646371/744133

0
YoYo

في السؤال:

who | grep $(tty | sed s:/dev/::)

إخراج أخطاء تدعي أن الملفات و tty غير موجودة. لقد فهمت أن هذا يعني أن tty لا يتم تفسيره قبل تنفيذ grep ، ولكن بدلاً من ذلك ، مر bash tty كمعلمة إلى grep ، والتي فسرتها كاسم ملف.

هناك أيضًا حالة من إعادة التوجيه المتداخلة ، والتي يجب معالجتها بواسطة أقواس متطابقة والتي يجب أن تحدد عملية فرعية ، ولكن bash هي في المقام الأول فاصل Word ، مما ينشئ معلمات لإرسالها إلى برنامج ، وبالتالي لا تتم مطابقة الأقواس أولاً ، ولكن يتم تفسيرها على أنها رأيت.

حصلت على تحديد مع grep ، وحددت الملف كمعلمة بدلاً من استخدام توجيه الإخراج. لقد قمت أيضًا بتبسيط الأمر الأساسي ، بتمرير الإخراج من أمر كملف ، بحيث لا يتم دمج أنابيب الإدخال/الإخراج:

grep $(tty | sed s:/dev/::) <(who)

يعمل جيدا.

who | grep $(echo pts/3)

غير مرغوب فيه حقًا ، ولكنه يلغي الأنبوب المتداخل ويعمل أيضًا بشكل جيد.

في الختام ، باش لا يبدو مثل pipping متداخلة. من المهم أن نفهم أن باش ليس برنامج موجة جديدة مكتوب بطريقة متكررة. بدلاً من ذلك ، يعد bash برنامجًا قديمًا يبلغ 1،2،3 ، تم إلحاقه بالميزات. لأغراض ضمان التوافق مع الإصدارات السابقة ، لم يتم تعديل الطريقة الأولية للتفسير. إذا تم إعادة كتابة bash لتتطابق أولاً مع الأقواس ، فكم عدد الأخطاء التي سيتم إدخالها في عدد برامج bash؟ كثير من المبرمجين يحبون أن يكونوا خفيين.

0
Carmin