Bagaimana untuk iterate atas argumen di Bash script

Saya memiliki kompleks perintah yang saya'd seperti untuk membuat shell/bash script. Saya bisa menulis ini dalam hal $1 dengan mudah:

foo $1 args -o $1.ext

Aku ingin bisa melewati beberapa masukan nama untuk script. Apa's cara yang tepat untuk melakukannya?

Dan, tentu saja, saya ingin menangani nama file dengan ruang-ruang di dalamnya.

Mengomentari pertanyaan (1)
Larutan

Gunakan"$@"` untuk mewakili semua argumen:

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

Ini akan iterate atas setiap argumen dan mencetaknya pada baris terpisah. $@ berperilaku seperti $* kecuali bahwa ketika mengutip argumen yang dipecah dengan baik jika ada ruang di dalamnya:

sh test.sh 1 2 '3 4'
1
2
3 4
Komentar (7)

Rewrite dari sekarang dihapus jawaban oleh VonC. Robert Berjudi's ringkas jawaban yang berhubungan langsung dengan pertanyaan. Yang satu ini menguatkan pada beberapa masalah dengan nama file yang mengandung spasi. Lihat juga: ${1:+"$@"} pada /bin/sh Dasar tesis: "$@" benar, $ * (tanda kutip) hampir selalu salah. Hal ini karena "$@" bekerja dengan baik ketika argumen yang mengandung spasi, dan bekerja sama sebagai $* ketika mereka don't. Dalam beberapa keadaan, "$*" OK juga, tapi "$@" biasanya (tapi tidak selalu) bekerja di tempat yang sama. Dikutp, $@ dan $* yang setara (dan hampir selalu salah). Jadi, apa perbedaan antara$*, $@, "$*", dan "$@"? Mereka semua yang terkait dengan 'semua argumen ke shell', tetapi mereka melakukan hal yang berbeda. Ketika dikutp, $* dan $@ melakukan hal yang sama. Mereka memperlakukan setiap 'kata' (urutan non-whitespace) sebagai argumen. Dikutip bentuk yang cukup berbeda, meskipun: "$*" memperlakukan daftar argumen sebagai satu ruang-string yang dipisahkan, sedangkan "$@" memperlakukan argumen yang hampir persis seperti mereka ketika ditentukan pada baris perintah. "$@" mengembang untuk apa-apa sama sekali ketika tidak ada argumen posisi; "$*" memperluas ke string kosong — dan ya, di sana's perbedaan, meskipun hal ini dapat sulit untuk mengartikannya. Lihat informasi lebih lanjut di bawah ini, setelah pengenalan (non-standar) perintah al. Sekunder tesis: jika anda perlu untuk memproses argumen dengan spasi dan kemudian lulus mereka ke perintah lain, maka anda kadang-kadang perlu non-standar alat-alat untuk membantu. (Atau anda harus menggunakan array, hati-hati: "${array[@]}" berperilaku analog ke "$@".) Contoh:

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

Mengapa doesn't yang bekerja? Itu doesn't bekerja karena proses shell kutipan sebelum mengembang variabel-variabel. Jadi, untuk mendapatkan shell untuk memperhatikan kutipan tertanam di $var, anda harus menggunakan 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
    $ 

Ini akan benar-benar sulit ketika anda memiliki nama file seperti "`Dia berkata, "Don't melakukan hal ini!"" (dengan tanda kutip dan tanda kutip ganda dan ruang).

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

Kerang (semua dari mereka) tidak membuatnya sangat mudah untuk menangani barang jadi (lucunya) banyak Unix program tidak melakukan pekerjaan yang baik penanganan mereka. Pada Unix, nama file (satu komponen) dapat berisi karakter apapun kecuali slash dan NUL'\0'. Namun, kerang sangat mendorong tidak ada spasi atau baris baru atau tab di mana saja dalam sebuah nama jalan. Hal ini juga mengapa standar Unix nama file tidak boleh mengandung spasi, dll. Ketika berhadapan dengan nama-nama file yang dapat berisi spasi dan lainnya merepotkan karakter, anda harus sangat berhati-hati, dan saya menemukan lama yang lalu bahwa saya membutuhkan sebuah program yang tidak standar di Unix. Saya menyebutnya melarikan diri (versi 1.1 tanggal 1989-08-23T16:01:45Z). Berikut adalah contoh dari melarikan diri di gunakan - dengan SKKL sistem kontrol. Ini adalah cover naskah yang tidak baik delta (berpikir check-in) dan mendapatkan (berpikir check-out). Berbagai argumen, terutama -y (alasan mengapa anda membuat perubahan) akan berisi kosong dan baris baru. Perhatikan bahwa script tanggal dari tahun 1992, sehingga menggunakan back-kutu bukan $(cmd ...) notasi dan tidak menggunakan #!/bin/sh pada baris pertama.

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

(Saya mungkin akan tidak menggunakan melarikan diri begitu benar-benar hari ini - itu tidak diperlukan dengan -e argumen, misalnya - tapi secara keseluruhan, ini adalah salah satu sederhana menggunakan script melarikan diri.) Yang melarikan diri program hanya output argumen, bukan seperti echo tidak, tetapi memastikan bahwa argumen yang dilindungi untuk digunakan dengan eval (satu tingkat dari eval; saya punya sebuah program yang melakukan remote shell eksekusi, dan yang diperlukan untuk melarikan diri output dari melarikan diri).

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

Saya memiliki program yang disebut al yang berisi daftar argumen satu per baris (dan itu bahkan lebih kuno: versi 1.1 tanggal 1987-01-27T14:35:49). Hal ini paling berguna ketika debugging script, karena dapat dihubungkan ke command line untuk melihat argumen apa yang sebenarnya dilewatkan ke perintah.

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

[Ditambahkan: Dan sekarang untuk menunjukkan perbedaan antara berbagai "$@" notasi, berikut adalah salah satu contoh lagi:

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

Melihat bahwa tidak ada yang mempertahankan asli kosong antara * dan */* pada command line. Juga, perhatikan bahwa anda dapat mengubah 'argumen baris perintah' in the shell dengan menggunakan:

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

Ini set 4 pilihan, '-yang baru', '-memilih', 'dan', dan 'arg dengan ruang'.
] Hmm, yang's cukup lama jawaban mungkin tafsir adalah istilah yang lebih baik. Kode sumber untuk melarikan diri tersedia berdasarkan permintaan (email untuk firstname dot lastname at gmail dot com). Kode sumber untuk al adalah sangat sederhana:

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

Yang's semua. Hal ini sama dengan test.sh script bahwa Robert Berjudi menunjukkan, dan dapat ditulis sebagai fungsi shell (tapi fungsi shell didn't ada di versi lokal dari Bourne shell ketika saya pertama kali menulis al). Perhatikan juga bahwa anda dapat menulis al sebagai sebuah skrip shell sederhana:

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

Bersyarat diperlukan sehingga tidak menghasilkan output ketika lulus tidak ada argumen. The printf perintah ini akan menghasilkan sebuah baris kosong dengan hanya format string argumen, tapi program C menghasilkan apa-apa.

Komentar (1)

Perhatikan bahwa Robert's jawaban adalah benar, dan ia bekerja di sh juga. Anda dapat (portable) menyederhanakan bahkan lebih jauh:

for i in "$@"

setara dengan:

for i

I. e., anda don't perlu apa-apa!

Pengujian ($ adalah command prompt):

$ 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

Saya pertama kali membaca tentang hal ini di Unix Lingkungan Pemrograman oleh Kernighan dan Tombak.

Di bash, membantu dokumen ini:

untuk NAMA [dalam kata-KATA ... ;] lakukan PERINTAH; dilakukan

Jika 'dalam kata-KATA ...;' tidak ada, maka 'di "$@",' diasumsikan.

Komentar (10)

Untuk kasus sederhana anda juga dapat menggunakan shift. Memperlakukan daftar argumen seperti antrian. Masing-masing shift melempar argumen pertama keluar dan indeks dari masing-masing argumen yang tersisa adalah decremented.

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

Anda juga dapat mengakses mereka sebagai sebuah elemen array, misalnya jika anda don't ingin untuk iterate melalui semua dari mereka


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

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

aparse "$@"
Komentar (1)