[bash] كيفية التكرار عبر الوسيطات في البرنامج النصي باش



Answers

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

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

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

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

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

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

مثال:

    $ 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 "$@"
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"
    $

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

$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ 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" "$@"

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

Question

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

foo $1 args -o $1.ext

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

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




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

#this prints all arguments
while test $# -gt 0
do
    echo $1
    shift
done



aparse() {
while [[ $# > 0 ]] ; do
  case "$1" in
    --arg1)
      varg1=${2}
      shift
      ;;
    --arg2)
      varg2=true
      ;;
  esac
  shift
done
}

aparse "$@"



Related