कैसे एक बैश स्क्रिप्ट में तर्कों पर पुनरावृति करें


899

मेरे पास एक जटिल कमांड है जिसे मैं एक शेल / बैश स्क्रिप्ट बनाना चाहता हूं। मैं इसे $1आसानी से लिख सकता हूं :

foo $1 args -o $1.ext

मैं स्क्रिप्ट में कई इनपुट नामों को पारित करने में सक्षम होना चाहता हूं। इसे करने का सही तरीका क्या है?

और, ज़ाहिर है, मैं उनमें रिक्त स्थान के साथ फ़ाइल नाम को संभालना चाहता हूं।


67
FYI करें, यह एक अच्छा विचार है कि हमेशा कोटेशन के भीतर पैरामीटर रखें। आप कभी नहीं जानते कि कब एक पैराम में एक एम्बेडेड स्थान हो सकता है।
डेविड आर ट्रिब्बल

जवाबों:


1478

"$@"सभी तर्कों का प्रतिनिधित्व करने के लिए उपयोग करें :

for var in "$@"
do
    echo "$var"
done

यह प्रत्येक तर्क पर पुनरावृत्ति करेगा और इसे अलग लाइन पर प्रिंट करेगा। $ @ $ जैसा व्यवहार करता है * सिवाय इसके कि जब तर्क दिया जाए तो ठीक से टूट जाते हैं यदि उनमें रिक्त स्थान हैं:

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

38
संयोग से, अन्य गुणों में से "$@"एक यह है कि यह कुछ भी नहीं फैलता है जब कोई स्थितिगत पैरामीटर नहीं होते हैं, जबकि "$*"खाली स्ट्रिंग में फैलता है - और हां, कोई तर्क नहीं है और एक खाली तर्क के बीच अंतर है। देखें ${1:+"$@"} in / बिन / श`
जोनाथन लेफ़लर

9
ध्यान दें कि फ़ंक्शन के सभी तर्कों को एक्सेस करने के लिए इस नोटेशन का उपयोग शेल फ़ंक्शन में भी किया जाना चाहिए।
जोनाथन लेफ्लर

दोहरे उद्धरण छोड़ कर: $varमैं तर्कों को निष्पादित करने में सक्षम था।
20

मैं दूसरे तर्क से कैसे शुरू करूं? मेरा मतलब है, मुझे इसकी आवश्यकता है लेकिन हमेशा दूसरे तर्क से शुरू करें, यह है, $ 1 को छोड़ दें।
m4l490n

4
@ m4l490n: shiftदूर फेंकता है $1, और बाद के सभी तत्वों को नीचे ले जाता है।
'11

239

VonC द्वारा अब हटाए गए उत्तर को फिर से लिखना ।

रॉबर्ट गैंबल का संक्षिप्त जवाब सीधे सवाल से संबंधित है। यह एक रिक्त स्थान वाले फ़ाइल नाम के साथ कुछ मुद्दों पर प्रवर्धित करता है।

यह भी देखें: / बिन / श में $ {1: + "$ @"}

मूल थीसिस: "$@" सही है, और $*(अयोग्य) लगभग हमेशा गलत है। इसका कारण यह है कि "$@"जब तर्क स्थान होते हैं तो ठीक काम करते हैं, और $*जब वे नहीं करते हैं तो उसी तरह काम करते हैं। कुछ परिस्थितियों में, "$*"ठीक भी है, लेकिन "$@"आमतौर पर (लेकिन हमेशा नहीं) एक ही स्थान पर काम करता है। निर्विवाद, $@और $*समतुल्य हैं (और लगभग हमेशा गलत)।

तो, के बीच अंतर क्या है $*, $@, "$*", और "$@"? वे सभी 'शेल के सभी तर्कों' से संबंधित हैं, लेकिन वे अलग-अलग चीजें करते हैं। जब अयोग्य, $*और $@एक ही काम करते हैं। वे प्रत्येक 'शब्द' (गैर-व्हाट्सएप का क्रम) को एक अलग तर्क मानते हैं। उद्धृत रूप काफी भिन्न हैं, यद्यपि: "$*"तर्क सूची को एक एकल स्थान-पृथक स्ट्रिंग के "$@"रूप में मानते हैं , जबकि तर्कों को लगभग वैसा ही मानते हैं जैसा वे कमांड लाइन पर निर्दिष्ट करते समय थे। "$@"जब कोई स्थितिगत तर्क नहीं होते हैं, तो कुछ भी नहीं फैलता है; "$*"एक खाली स्ट्रिंग के लिए फैलता है - और हाँ, वहाँ एक अंतर है, हालांकि इसे समझना मुश्किल हो सकता है। (गैर-मानक) कमांड की शुरुआत के बाद, नीचे अधिक जानकारी देखें 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
    $

वह काम क्यों नहीं करता है? यह काम नहीं करता क्योंकि शेल प्रक्रियाएं चर को फैलाने से पहले बोली लगाती हैं। तो, खोल में एम्बेडेड उद्धरण पर ध्यान देने के लिए शेल प्राप्त करने के लिए $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
    $ 

गोले (वे सभी) इस तरह के सामान को संभालने के लिए विशेष रूप से आसान नहीं बनाते हैं, इसलिए (मज़ेदार रूप से) कई यूनिक्स कार्यक्रम उन्हें संभालने का एक अच्छा काम नहीं करते हैं। यूनिक्स पर, एक फ़ाइल नाम (एकल घटक) में स्लैश और NUL को छोड़कर कोई भी वर्ण हो सकता है '\0'। हालाँकि, गोले एक पथ नाम में कहीं भी रिक्त स्थान या newlines या टैब को दृढ़ता से प्रोत्साहित नहीं करते हैं। यह भी है कि मानक यूनिक्स फ़ाइल नामों में रिक्त स्थान क्यों नहीं हैं, आदि।

जब उन फ़ाइल नामों के साथ काम करना जिनमें रिक्त स्थान और अन्य परेशानी वाले वर्ण हो सकते हैं, तो आपको बेहद सावधान रहना होगा, और मैंने बहुत पहले पाया कि मुझे एक ऐसे कार्यक्रम की आवश्यकता थी जो यूनिक्स पर मानक नहीं है। मैं इसे कहता हूं escape(संस्करण 1.1 दिनांक 1989-08-23T16: 01: 45Z)।

यहां escapeSCCS नियंत्रण प्रणाली के साथ - उपयोग में एक उदाहरण है। यह एक कवर स्क्रिप्ट है जो ए 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है, मैं एक प्रोग्राम है जो दूरदराज के खोल निष्पादन किया है, और है कि के उत्पादन से बचने के लिए की जरूरत है 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
$

ध्यान दें कि कमांड लाइन पर *और उसके बीच के मूल रिक्त को कुछ भी संरक्षित नहीं करता है */*। यह भी ध्यान दें कि आप शेल में 'कमांड लाइन आर्ग्युमेंट्स' को बदलकर उपयोग कर सकते हैं:

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

यह 4 विकल्प सेट करता है, ' -new', ' -opt', ' and', और ' arg with space'।
]

हम्म, यह काफी लंबा जवाब है - शायद एक्जेसिस बेहतर शब्द है। escapeअनुरोध पर उपलब्ध स्रोत कोड (जीमेल डॉट कॉम पर फर्स्टनाम डॉटनाम के लिए ईमेल)। स्रोत कोड alअविश्वसनीय रूप से सरल है:

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

बस इतना ही। यह उस test.shस्क्रिप्ट के बराबर है जिसे रॉबर्ट गैम्बल ने दिखाया था, और इसे शेल फ़ंक्शन के रूप में लिखा जा सकता था (लेकिन शेल फ़ंक्शन बॉर्न शेल के स्थानीय संस्करण में मौजूद नहीं था जब मैंने पहली बार लिखा था al)।

यह भी ध्यान दें कि आप alएक सरल शेल स्क्रिप्ट के रूप में लिख सकते हैं :

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

सशर्त की जरूरत है ताकि कोई तर्क न दिए जाने पर कोई आउटपुट न आए। printfआदेश केवल प्रारूप स्ट्रिंग तर्क के साथ एक रिक्त पंक्ति का उत्पादन करेगा, लेकिन सी कार्यक्रम कुछ भी नहीं पैदा करता है।


132

ध्यान दें कि रॉबर्ट का उत्तर सही है, और यह काम भी करता है sh। आप इसे आगे भी सरल बना सकते हैं:

for i in "$@"

के बराबर है:

for i

यानी, आपको किसी चीज़ की ज़रूरत नहीं है!

परीक्षण ( $कमांड प्रॉम्प्ट है):

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d

मैंने पहली बार इस बारे में Kernighan और पाईक द्वारा यूनिक्स प्रोग्रामिंग पर्यावरण में पढ़ा ।

में bash, help forदस्तावेज इस:

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

अगर 'in WORDS ...;'मौजूद नहीं है, तो 'in "$@"'मान लिया जाता है।


4
मैं असहमत हूं। किसी को यह जानना है कि क्रिप्टिक "$@"का क्या मतलब है, और एक बार जब आप जानते हैं कि for iइसका क्या मतलब है, तो यह किसी से कम पढ़ने योग्य नहीं है for i in "$@"
आलोक सिंघल

10
मैंने दावा नहीं किया कि for iयह बेहतर है क्योंकि यह कीस्ट्रोक्स बचाता है। मैंने for iऔर की पठनीयता की तुलना की for i in "$@"
आलोक सिंघल

यह वही है जो मैं देख रहा था - वे $ लूप में $ @ की इस धारणा को क्या कहते हैं, जहां आपको इसे स्पष्ट रूप से संदर्भित करने की आवश्यकता नहीं है? $ @ संदर्भ का उपयोग नहीं करने के लिए एक नकारात्मक पहलू है?
क्षोदिनेजा

58

सरल मामलों के लिए आप भी उपयोग कर सकते हैं shift। यह तर्क सूची को एक कतार की तरह मानता है। प्रत्येक shiftपहले तर्क को बाहर फेंक देता है और शेष तर्कों में से प्रत्येक के सूचकांक को घटा दिया जाता है।

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

मुझे लगता है कि यह उत्तर बेहतर है क्योंकि यदि आप shiftलूप के लिए करते हैं , तो वह तत्व अभी भी ऐरे का हिस्सा है, जबकि इसके साथ यह shiftअपेक्षित है।
डेविड

2
ध्यान दें कि आपको echo "$1"तर्क स्ट्रिंग के मूल्य में रिक्ति को संरक्षित करने के लिए उपयोग करना चाहिए । इसके अलावा, (एक छोटी सी बात) यह विनाशकारी है: यदि आप उन सभी को स्थानांतरित कर चुके हैं तो आप तर्कों का पुन: उपयोग नहीं कर सकते। अक्सर, यह कोई समस्या नहीं है - आप केवल तर्क सूची को किसी भी समय संसाधित करते हैं।
जोनाथन लेफलर

1
सहमति दें कि शिफ्ट बेहतर है, क्योंकि तब आपके पास "-o outputfile" जैसे ध्वज तर्कों को संसाधित करने के लिए स्विच मामले में दो तर्कों को हथियाने का एक आसान तरीका है।
बेक्सीसिमो

दूसरी ओर, यदि आप ध्वज तर्कों को पार्स करना शुरू कर रहे हैं, तो आप बैश से अधिक शक्तिशाली स्क्रिप्ट भाषा पर विचार करना चाह सकते हैं :)
nuoritoveri

4
यह समाधान बेहतर है अगर आपको कुछ युगल पैरामीटर-मूल्य की आवश्यकता है, उदाहरण के लिए --file myfile.txt , इसलिए, $ 1 पैरामीटर है, $ 2 मूल्य है और जब आप किसी अन्य तर्क को छोड़ने की आवश्यकता होती है तो आप दो बार शिफ्ट कहते हैं
डेनियल लेक्ट्रा

16

आप उन्हें एरे तत्वों के रूप में भी एक्सेस कर सकते हैं, उदाहरण के लिए यदि आप उन सभी के माध्यम से पुनरावृति नहीं करना चाहते हैं

argc=$#
argv=("$@")

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

5
मेरा मानना ​​है कि लाइन argv = ($ @) होना चाहिए argv = ("$ @") रिक्त स्थान के साथ अन्य बुद्धिमान args को सही ढंग से नहीं संभाला जाता है
kdubs

मैं सही वाक्यविन्यास argv = ("$ @") की पुष्टि करता हूं। जब तक कि आपके पास एक उद्धृत पैरामीटर नहीं है, तब तक आपको अंतिम मान नहीं मिलेगा। उदाहरण के लिए यदि आप कुछ का उपयोग करते हैं ./my-script.sh param1 "param2 is a quoted param": - argv = ($ @) का उपयोग करने पर आपको [param1, param2] मिलेगा, - argv = ("$ @") का उपयोग करके, आपको [param1, param2 एक उद्धृत परम]
मिलेगा

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

aparse "$@"

1
जैसा कि नीचे बताया गया [ $# != 0 ]है बेहतर है। उपरोक्त में, जैसा #$होना चाहिए$#while [[ $# > 0 ]] ...
hute37

1

बाज के जवाब पर अमल करते हुए, यदि आपको एक अनुक्रमणिका के साथ तर्क सूची की गणना करने की आवश्यकता है (जैसे कि किसी विशिष्ट शब्द की खोज करना), तो आप इसे सूची की नकल किए बिना या इसे म्यूट कर सकते हैं।

मान लें कि आप एक तर्क सूची को दो-डैश ("-") पर विभाजित करना चाहते हैं और एक कमांड के लिए डैश से पहले तर्क और दूसरे के लिए डैश के बाद तर्क पास करना चाहते हैं:

 toolwrapper() {
   for i in $(seq 1 $#); do
     [[ "${!i}" == "--" ]] && break
   done || return $? # returns error status if we don't "break"

   echo "dashes at $i"
   echo "Before dashes: ${@:1:i-1}"
   echo "After dashes: ${@:i+1:$#}"
 }

परिणाम इस तरह दिखना चाहिए:

 $ toolwrapper args for first tool -- and these are for the second
 dashes at 5
 Before dashes: args for first tool
 After dashes: and these are for the second

1

getopt अपनी स्क्रिप्ट में उपयोग कमांड किसी भी कमांड लाइन विकल्प या मापदंडों फ़ॉर्मेट करने के लिए।

#!/bin/bash
# Extract command line options & values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift ;;
-c) echo "Found the -c option" ;;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift

मुश्किल एक, मैं इस पर पूरी तरह से शोध करूंगा। उदाहरण: फ़ाइल: ///rr/share/doc/util-linux/examples/getopt-parse.bash, मैनुअल: man.jimmylandstudios.xyz/?man=getopt
JimMLandStudios
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.