Bash betiğindeki bağımsız değişkenler üzerinde yineleme nasıl yapılır


900

Ben bir kabuk / bash betiği yapmak istiyorum karmaşık bir komut var. $1Kolayca yazabilirim :

foo $1 args -o $1.ext

Komut dosyasına birden fazla giriş adı iletebilmek istiyorum. Bunu yapmanın doğru yolu nedir?

Ve tabii ki, içinde boşluk bulunan dosya isimlerini ele almak istiyorum.


67
Bilginize, parametreleri her zaman tırnak içine almak iyi bir fikirdir. Bir parmın ne zaman gömülü bir alan içerebileceğini asla bilemezsiniz.
David R Tribble

Yanıtlar:


1482

"$@"Tüm argümanları temsil etmek için kullanın :

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

Bu, her bir bağımsız değişkeni yineleyip ayrı bir satıra yazdırır. $ @, $ * gibi davranır, ancak tırnak içine alındığında, içinde boşluklar varsa bağımsız değişkenler düzgün bir şekilde parçalanır:

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

38
Bu arada, diğer yararlarından biri "$@", konumsal parametreler olmadığında hiçbir şeye "$*"genişlemezken boş dizeye genişler - ve evet, hiçbir argüman ile bir boş argüman arasında bir fark vardır. Bkz. ${1:+"$@"} in / Bin / sh` .
Jonathan Leffler

9
Bu gösterimin, işleve ilişkin tüm bağımsız değişkenlere erişmek için kabuk işlevlerinde de kullanılması gerektiğini unutmayın.
Jonathan Leffler

Çift tırnak işaretlerini bırakarak: $varArgümanları yürütebildim.
eonist

ikinci argümandan nasıl başlarım? Yani, buna ihtiyacım var ama her zaman ikinci argümandan başlıyorum, bu 1 $ 'ı atlayın.
1919

4
@ m4l490n: shiftatar $1ve sonraki tüm öğeleri kaydırır.
MSalters

239

VonC tarafından silinen bir cevabın yeniden yazılması .

Robert Gamble'ın özlü yanıtı doğrudan soru ile ilgilidir. Bu, boşluk içeren dosya adları ile ilgili bazı konularda güçlendirir.

Ayrıca bkz: / bin / sh içinde $ {1: + "$ @"}

Temel tez: "$@" doğrudur ve $*(alıntılanmamış) neredeyse her zaman yanlıştır. Bunun nedeni "$@", bağımsız değişkenler boşluk içerdiğinde iyi çalışır ve içermedikleri gibi çalışır $*. Bazı durumlarda "$*"da sorun yoktur, ancak "$@"genellikle (ancak her zaman değil) aynı yerlerde çalışır. Tırnaksız $@ve $*eşdeğerdir (ve neredeyse her zaman yanlıştır).

Yani, arasındaki fark nedir $*, $@, "$*", ve "$@"? Hepsi 'kabuğun tüm argümanlarıyla' ilişkilidir, ancak farklı şeyler yaparlar. Alıntı yapıldığında $*ve $@aynı şeyi yapın. Her bir 'sözcüğü' (boşluk olmayan diziyi) ayrı bir argüman olarak ele alırlar. Alıntılanan formlar oldukça farklıdır: "$*"argüman listesini boşlukla ayrılmış tek bir dize olarak "$@"ele alırken argümanları komut satırında belirtildiği gibi hemen hemen aynı şekilde ele alır. "$@"konumsal argüman olmadığında hiç bir şeye genişlemez; "$*"boş bir dizeye genişler - ve evet, bir fark vardır, ancak onu algılamak zor olabilir. (Standart dışı) komutun kullanılmasından sonra aşağıdaki daha fazla bilgi edinin al.

İkincil tez: boşluklarla argümanları işlemeniz ve daha sonra diğer komutlara iletmeniz gerekiyorsa, bazen yardımcı olmak için standart olmayan araçlara ihtiyacınız vardır. (Veya dizileri dikkatli bir şekilde kullanmalısınız: "${array[@]}"benzer şekilde davranır "$@".)

Misal:

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

Neden çalışmıyor? Kabuk, değişkenleri genişletmeden önce tırnak işlemlerini gerçekleştirdiği için çalışmaz. Bu nedenle, kabuğun gömülü tırnaklara dikkat etmesini sağlamak için $varşunları kullanmalısınız 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!"" ( Dosya ve çift tırnak ve boşluklarla) gibi dosya adlarına sahip olduğunuzda bu gerçekten zor olur .

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

Kabuklar (hepsi) bu tür şeyleri işlemeyi özellikle kolaylaştırmaz, bu nedenle (yeterince komik) birçok Unix programı bunları işlemek için iyi bir iş yapmaz. Unix'te, bir dosya adı (tek bileşenli), eğik çizgi ve NUL dışında herhangi bir karakter içerebilir '\0'. Ancak, mermiler yol adlarının hiçbir yerinde boşluk, satır, çizgi veya sekme olmamasını şiddetle teşvik eder. Ayrıca standart Unix dosya adlarının boşluk vb. İçermemesinin nedeni de budur.

Boşluklar ve diğer zahmetli karakterler içerebilecek dosya adları ile uğraşırken, son derece dikkatli olmalısınız ve uzun zaman önce Unix'te standart olmayan bir programa ihtiyacım olduğunu buldum. Ona diyorumescape (sürüm 1.1 tarihli 1989-08-23T16: 01: 45Z).

escapeSCCS kontrol sistemi ile kullanımda bir örnek . Bu a hem yapan bir kapak script delta(düşünmek giriş ) ve get(düşünmek çıkış ). Çeşitli argümanlar, özellikle -y(değişikliği yapma nedeniniz) boşluklar ve yeni satırlar içerecektir. Komut dosyasının 1992'den kalma olduğunu unutmayın, bu nedenle $(cmd ...)gösterim yerine geri keneler kullanır #!/bin/shve ilk satırda kullanılmaz.

:   "@(#)$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"

(Muhtemelen bu günlerde kaçmayı çok fazla kullanmazdım - -eörneğin argümanla gerekli değildir - ancak genel olarak, bu benim kullandığım basit komut dosyalarından biridir escape.)

escapeProgram sadece oldukça gibi, bağımsız değişkenlerini verir echo , ama bu argümanlar ile kullanım için korunmasını sağlamaktadır eval(Düz bir eval, Uzak kabuk yürütmesini bir program var ve çıkışını kaçmak için gerekli escape).

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

alArgümanlarını her satırda bir tane olarak listeleyen başka bir programım var (ve daha da eski: 1987-01-27T14: 35: 49 tarihli 1.1 sürümü). Komut dosyalarında hata ayıklama sırasında en kullanışlıdır, çünkü aslında hangi argümanların komuta iletildiğini görmek için bir komut satırına eklenebilir.

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

[ Eklendi: Şimdi çeşitli "$@"gösterimler arasındaki farkı göstermek için , bir örnek daha:

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

Komut satırındaki *ve arasındaki orijinal boşlukları hiçbir şeyin saklamadığına dikkat edin */*. Ayrıca, kabuk kullanarak 'komut satırı bağımsız değişkenlerini' değiştirebileceğinize dikkat edin:

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

Bu, ' -new', ' -opt', ' and' ve ' arg with space' olmak üzere 4 seçenek ayarlar .
]

Hmm, bu oldukça uzun bir cevap - belki de tefsir daha iyi bir terimdir. escapeİstek üzerine temin edilebilen kaynak kodu (gmail dot com adresinden ad soyadının soyadına e-posta). İçin kaynak kodu alinanılmaz derecede basit:

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

Bu kadar. test.shRobert Gamble'ın gösterdiği senaryoya eşdeğerdir ve bir kabuk işlevi olarak yazılabilir (ancak ilk yazdığımda Bourne kabuğunun yerel sürümünde kabuk işlevleri yoktu al).

Ayrıca albasit bir kabuk komut dosyası olarak yazabileceğinizi unutmayın :

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

Koşullu, argüman iletilmediğinde çıktı üretmemesi için gereklidir. printfKomut sadece biçim dizesi bağımsız değişkenle boş satır üretecek, ancak C programı şey üretir.


132

Robert'ın cevabının doğru olduğunu ve bunun da işe yaradığını unutmayın sh. Daha da basitleştirebilirsiniz:

for i in "$@"

şuna eşittir:

for i

Yani, hiçbir şeye ihtiyacın yok!

Test ( $komut istemidir):

$ 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

Bunu ilk olarak Kernighan ve Pike'ın Unix Programlama Ortamında okudum .

In bash, bunu help forbelgeler:

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

Eğer 'in WORDS ...;'mevcut değilse, o 'in "$@"'zaman varsayılır.


4
Katılmıyorum. Kişinin şifreli "$@"ne for ianlama geldiğini bilmek gerekir ve ne anlama geldiğini öğrendikten sonra , daha az okunabilir değildir for i in "$@".
Alok Singhal

10
Bunun for idaha iyi olduğunu iddia etmedim çünkü tuş vuruşlarını koruyor. Ben okunabilirliği karşılaştırıldı for ive for i in "$@".
Alok Singhal

Ben aradığım şey buydu - açıkça referans vermek zorunda olmadığınız döngülerdeki bu $ @ varsayımına ne diyorlar? $ @ Başvurusunu kullanmamanın bir dezavantajı var mı?
qodeninja

58

Basit durumlar için de kullanabilirsiniz shift. Bağımsız değişken listesine bir sıra gibi davranır. Her biri shiftilk argümanı atar ve geri kalan argümanların her birinin dizini azaltılır.

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

Bence bu cevap daha iyi çünkü shiftbir for döngüsünde yaparsanız , bu öğe hala dizinin bir parçası iken, bu shiftbeklendiği gibi çalışır.
DavidG

2
echo "$1"Bağımsız değişken dizesinin değerindeki aralığı korumak için kullanmanız gerektiğini unutmayın . Ayrıca, (küçük bir sorun) bu yıkıcı bir durumdur: eğer hepsini değiştirdiyseniz argümanları tekrar kullanamazsınız. Çoğu zaman, bu bir sorun değildir - argüman listesini zaten bir kez işlersiniz.
Jonathan Leffler

1
Bu geçişin daha iyi olduğunu kabul edin, çünkü o zaman "-o outputfile" gibi bayrak argümanlarını işlemek için bir anahtar durumunda iki argüman almanın kolay bir yoluna sahipsiniz.
Baxissimo

Öte yandan, bayrak argümanlarını ayrıştırmaya başlıyorsanız,
bash'dan

4
Bazı çift parametre değerine ihtiyacınız varsa bu çözüm daha iyidir, örneğin --file myfile.txt , 1 $ parametredir, $ 2 değerdir ve başka bir argümanı atlamanız gerektiğinde iki kez shift çağırırsınız
Daniele Licitra

16

Bunlara bir dizi öğesi olarak da erişebilirsiniz; örneğin, bunların tümünü yinelemek istemiyorsanız

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

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

5
Bence argv = ($ @) argv = ("$ @") boşluklu diğer bilge argümanlar doğru ele
alınmıyor

Doğru sözdiziminin argv = ("$ @") olduğunu onaylıyorum. tırnak içine alınmış bir parametreniz varsa son değerleri alamazsınız. Örneğin, şöyle bir şey ./my-script.sh param1 "param2 is a quoted param"kullanırsanız: - argv = ($ @) kullanarak [param1, param2], - argv = ("$ @") kullanarak [param1, param2 alıntılanmış bir param olur]
jseguillon

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

aparse "$@"

1
Aşağıda belirtildiği gibi [ $# != 0 ]daha iyidir. Yukarıda, #$olması gerektiği $#gibiwhile [[ $# > 0 ]] ...
hute37

1

Baz'ın cevabını güçlendirerek, argüman listesini bir indeksle (belirli bir kelimeyi aramak gibi) numaralandırmanız gerekiyorsa, listeyi kopyalamadan veya mutasyona uğratmadan bunu yapabilirsiniz.

Bir bağımsız değişken listesini çift tire ("-") olarak bölmek ve tire işaretlerinden önce bağımsız değişkenleri bir komuta, tire işaretlerinden sonraki bağımsız değişkenleri diğerine geçirmek istediğinizi varsayalım:

 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:$#}"
 }

Sonuçlar şöyle görünmelidir:

 $ 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 Komut satırı seçeneklerinizi veya parametrelerini biçimlendirmek için komut dosyalarınızdaki komutu kullanın.

#!/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

Zor olanı, bunu iyice araştırırdım. Örnek: dosya: ///usr/share/doc/util-linux/examples/getopt-parse.bash, Manuel: man.jimmylandstudios.xyz/?man=getopt
JimmyLandStudios
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.