bash - هذه - لايستجيب نظرا لوجود برنامج نصي يعمل منذ مدة طويلة




كيفية التكرار عبر الوسيطات في البرنامج النصي باش (4)

لدي أمر معقد أود كتابة نص برمجي به. يمكنني كتابته من حيث $1 بسهولة:

foo $1 args -o $1.ext

أريد أن أتمكن من تمرير أسماء مدخلات متعددة إلى البرنامج النصي. ما هي الطريقة الصحيحة لفعل ذلك؟

وبالطبع ، أريد التعامل مع أسماء الملفات مع مسافات فيها.


استخدم "[email protected]" لتمثيل جميع الوسيطات:

for var in "[email protected]"
do
    echo "$var"
done

سيتكيف هذا مع كل وسيطة ويطبعه على سطر منفصل. يتصرف $ @ مثل $ * إلا أنه عند الاقتباس يتم فصل الحجج بشكل صحيح إذا كانت هناك مسافات فيها:

sh test.sh 1 2 '3 4'
1
2
3 4

لاحظ أن إجابة روبرت صحيحة ، وأنها تعمل في sh كذلك. يمكنك (بشكل مؤقت) تبسيطها بشكل أكبر:

for i in "[email protected]"

ما يعادل:

for i

أي أنك لست بحاجة إلى أي شيء!

اختبار ( $ هو موجه الأوامر):

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "[email protected]"; do echo "$i"; done
a
b
spaces here
d

قرأت لأول مرة عن هذا في بيئة برمجة Unix بواسطة Kernighan و Pike.

في bash ، help for المستندات:

for NAME [in WORDS ... ;] do COMMANDS; done

إذا كان 'in WORDS ...;' غير موجود ، ثم 'in "[email protected]"' يفترض.


يمكنك أيضًا الدخول إليها كعناصر صفيف ، على سبيل المثال إذا كنت لا تريد التكرار من خلال جميع العناصر

argc=$#
argv=([email protected])

for (( j=0; j<argc; j++ )); do
    echo ${argv[j]}
done

VonC answer محذوفة الآن بواسطة VonC .

تتعامل إجابة روبرت جامبل مع السؤال بشكل مباشر. هذا تضخيم على بعض القضايا مع أسماء الملفات التي تحتوي على مسافات.

انظر أيضًا: $ {1: + "$ @"} في / bin / sh

الأطروحة الأساسية: "[email protected]" صحيحة ، و $* (غير مدروس) هو دائما تقريبا خطأ. هذا لأن "[email protected]" يعمل بشكل جيد عندما تحتوي الوسيطات على مسافات ، ويعمل بنفس $* عندما لا يفعل. في بعض الحالات ، يكون "$*" ما يرام أيضًا ، ولكن "[email protected]" عادةً (ولكن ليس دائمًا) يعمل في نفس الأماكن. غير مسعرة و [email protected] و $* متساوية (وغالبًا ما تكون خاطئة).

إذن ، ما الفرق بين $* و [email protected] و "$*" و "[email protected]" ؟ ترتبط جميعها "بجميع الحجج إلى القشرة" ، لكنهم يفعلون أشياء مختلفة. عند التداول غير المقبول ، يقوم $* و [email protected] بنفس الشيء. يعاملون كل "كلمة" (تسلسل غير بيضاء) كحجة منفصلة. تختلف النماذج المقتبسة تمامًا ، على الرغم من أن: "$*" يعامل قائمة الوسيطة كسلسلة مفردة مفصولة بمسافة ، بينما تعالج "[email protected]" الوسيطات تمامًا كما كانت عند تحديدها في سطر الأوامر. يتوسّع "[email protected]" إلى لا شيء مطلقًا عندما لا توجد حجج موضعية ؛ "$*" توسيع "$*" إلى سلسلة فارغة - ونعم ، هناك فرق ، على الرغم من أنه قد يكون من الصعب إدراكه. انظر المزيد من المعلومات أدناه ، بعد إدخال الأمر (غير قياسي) al .

الأطروحة الثانوية: إذا كنت بحاجة إلى معالجة الحجج باستخدام مسافات ثم تمريرها إلى أوامر أخرى ، فأنت تحتاج أحيانًا إلى أدوات غير قياسية للمساعدة. (أو يجب عليك استخدام المصفوفات بعناية: "${array[@]}" تتصرف بشكل مشابه إلى "[email protected]" .)

مثال:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

لماذا لا يعمل هذا؟ لا يعمل لأن shell يقتبس قبل أن يقوم بتوسيع المتغيرات. لذلك ، لجعل القوقعة تولي اهتماما لعلامات الاقتباس المضمنة في $var ، يجب عليك استخدام eval :

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

هذا يصبح صعب حقا عندما يكون لديك أسماء الملفات مثل " He said, "Don't do this!" " (مع اقتباسات وعلامات الاقتباس المزدوجة والمسافات).

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

لا تجعل القذائف (جميعها) من التعامل مع هذه الأشياء أمرًا سهلاً بشكل خاص ، لذلك (كثيرًا ما يكفي) لا تقوم برامج Unix بعمل جيد في التعامل معها. في نظام التشغيل Unix ، يمكن أن يحتوي اسم الملف (مكون مفرد) على أي أحرف باستثناء الشرطة المائلة و NUL '\0' . ومع ذلك ، فإن الأصداف تشجع بشدة عدم وجود مسافات أو خطوط جديدة أو علامات تبويب في أي مكان في أسماء المسارات. ولهذا السبب أيضاً لا تحتوي أسماء ملفات Unix القياسية على مسافات ، إلخ.

عند التعامل مع أسماء الملفات التي قد تحتوي على مسافات وشخصيات مزعجة أخرى ، يجب أن تكون حذراً للغاية ، وقد وجدت منذ فترة طويلة أنني بحاجة إلى برنامج غير قياسي على نظام Unix. أسميها escape (الإصدار 1.1 كان بتاريخ 1989-08-23T16: 01: 45Z).

هنا مثال على escape في الاستخدام - مع نظام التحكم SCCS. وهو عبارة عن نص برمجي يغطي delta (التفكير في الاختيار ) get (انظر مراجعة ). الحجج المختلفة ، خاصة -y (سبب جعل التغيير) تحتوي على فراغات وخطوط جديدة. لاحظ أن التواريخ البرنامج النصي من 1992 ، بحيث يستخدم علامات التجزئة بدلاً من تدوين $(cmd ...) ولا يستخدم #!/bin/sh على السطر الأول.

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "[email protected]"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(ربما لا أستخدم الهروب بشكل تام في هذه الأيام - لا حاجة له ​​مع الحجة -e ، على سبيل المثال - ولكن بشكل عام ، هذه واحدة من أبسط مخطوطاتي باستخدام escape .)

يقوم برنامج escape بإخراج حججه ببساطة ، مثل echo ، لكنه يضمن حماية الحجج للاستخدام مع eval (مستوى واحد من eval ؛ ولدي برنامج يقوم بتنفيذ shell عن بعد ، ويحتاج إلى الهروب من إخراج escape ).

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

لدي برنامج آخر يسمى al الذي يسرد حججها واحد في كل سطر (وهو أكثر القديمة: الإصدار 1.1 بتاريخ 1987-01-27T14: 35: 49). وهي مفيدة للغاية عند تصحيح أخطاء البرامج النصية ، حيث يمكن توصيلها بسطر الأوامر لمعرفة ما هي الحجج التي يتم تمريرها بالفعل إلى الأمر.

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[ تمت إضافتها: والآن لإظهار الفرق بين تنويهات "[email protected]" ، إليك مثال آخر:

$ cat xx.sh
set -x
al [email protected]
al $*
al "$*"
al "[email protected]"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

لاحظ أن لا شيء يحفظ الفراغات الأصلية بين * و */* في سطر الأوامر. لاحظ أيضًا أنه يمكنك تغيير "وسيطات سطر الأوامر" في shell باستخدام:

set -- -new -opt and "arg with space"

يحدد هذا الخيار 4 خيارات ، " -opt " ، and " -opt " ، and " and " ، و " arg with space ".
]

إنها إجابة طويلة جداً - ربما التفسير هو المصطلح الأفضل. شفرة المصدر escape متاحة عند الطلب (بريد إلكتروني إلى الاسم الأول dot lastname في gmail dot com). كود المصدر لـ al بسيط بشكل لا يصدق:

#include <stdio.h>
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

هذا كل شئ. وهو ما يعادل النصي test.sh الذي test.sh روبرت غامبل ، ويمكن كتابته كدالة صدفة (ولكن لم تكن موجودة في النسخة المحلية من قذيفة Bourne shell عندما كتبت أول كلمة al ).

لاحظ أيضًا أنه يمكنك كتابة al كبرنامج نصي بسيط في shell:

[ $# != 0 ] && printf "%s\n" "[email protected]"

مطلوب الشرط بحيث لا ينتج أي إخراج عند تمرير أي وسائط. سينتج الأمر printf سطر فارغ مع وسيطة تنسيق التنسيق فقط ولكن لا ينتج البرنامج C أي شيء.







command-line