Sebarkan semua argumen dalam skrip bash shell

853

Saya menulis skrip yang sangat sederhana yang memanggil skrip lain, dan saya perlu menyebarkan parameter dari skrip saya saat ini ke skrip yang saya jalankan.

Misalnya, nama skrip saya adalah foo.shdan panggilanbar.sh

foo.sh:

bar $1 $2 $3 $4

Bagaimana saya bisa melakukan ini tanpa secara eksplisit menentukan setiap parameter?

Fragsworth
sumber

Jawaban:

1408

Gunakan "$@"alih-alih polos $@jika Anda benar-benar ingin agar parameter Anda lulus sama.

Mengamati:

$ cat foo.sh
#!/bin/bash
baz.sh $@

$ cat bar.sh
#!/bin/bash
baz.sh "$@"

$ cat baz.sh
#!/bin/bash
echo Received: $1
echo Received: $2
echo Received: $3
echo Received: $4

$ ./foo.sh first second
Received: first
Received: second
Received:
Received:

$ ./foo.sh "one quoted arg"
Received: one
Received: quoted
Received: arg
Received:

$ ./bar.sh first second
Received: first
Received: second
Received:
Received:

$ ./bar.sh "one quoted arg"
Received: one quoted arg
Received:
Received:
Received:
Sdaz MacSkibbons
sumber
7
Ini berfungsi untuk lulus murni dari string yang dikutip / lolos: Amati: cat rsync_foo.sh #! / Bin / bash echo "$ @" rsync "$ @" ./rsync_foo.sh -n "bar me" bar2 bar me bar2skipping directory bar me Apakah mungkin untuk memiliki skrip shell yang dapat melihat baris perintah asli yang sebenarnya lengkap dengan tanda kutip atau string yang lolos?
Mark Edington
3
Bagaimana dengan mengibarkan bendera? Misalnya, './bar.sh --dengan barang'
Nathan Long
4
Saya menyarankan siapa saja yang ingin memahami subjek Word Splitting dengan lebih baik, untuk membaca lebih lanjut di sini.
Rany Albeg Wein
4
Saya sarankan Anda menunjukkan apa yang terjadi ketika memasukkan 'arg with spaces'tiga contoh juga. Saya terkejut dengan hasilnya; semoga Anda bisa menjelaskannya.
Michael Scheper
2
@MichaelScheper, bagaimanapun, ./foo.sh "arg with spaces"dan ./foo.sh 'arg with spaces'100% identik, jadi saya tidak melihat bagaimana saran untuk menambahkannya ke contoh yang diberikan akan membantu.
Charles Duffy
475

Untuk bash dan cangkang mirip Bourne lainnya:

java com.myserver.Program "$@"
Chris Johnsen
sumber
13
@ Amir: Tidak, tidak untuk csh . Untuk kewarasan semua orang: jangan skrip csh . Tapi saya pikir mungkin $argv:qakan bekerja di beberapa varian csh.
Chris Johnsen
2
Terima kasih! Seperti yang diinginkan, ini melewati serangkaian argumen yang sama yang diterima oleh skrip - bukan satu argumen besar. Kutipan ganda diperlukan. Bekerja bahkan jika dengan argumen yang dikutip yang menyertakan spasi.
Andy Thomas
24
Terkait: jika skrip shell Anda hanya bertindak sebagai pembungkus untuk menjalankan java, pertimbangkan untuk membuat baris terakhir. exec java com.myserver.Program "$@"Hal ini menyebabkan bash untuk mengeksekusi ke java, daripada menunggu sampai selesai. Jadi, Anda menggunakan satu slot proses kurang. Juga, jika proses induk (yang menjalankan skrip Anda) menontonnya melalui pid, dan mengharapkannya menjadi proses 'java', beberapa hal yang tidak biasa dapat rusak jika Anda tidak melakukan exec; exec menyebabkan java untuk mewarisi pid yang sama.
greggo
7
@dragonxlwang: Anda bisa menggunakan variabel array, jika shell Anda mendukungnya (mis. bash , zsh , lainnya, tetapi bukan plain Bourne- atau POSIX-shell): simpan ke argumen dengan args=("$@")dan kembangkan setiap elemen sebagai "kata" shell yang terpisah ( mirip dengan "$@") "${args[@]}".
Chris Johnsen
1
Apakah ada "gotcha" yang perlu diingat saat menggunakan "$@", seperti apakah akan gagal jika Anda telah lolos spasi dalam argumen, atau karakter nol, atau karakter khusus lainnya?
IQAndreas
97

Gunakan "$@"(berfungsi untuk semua kompatibilitas POSIX ).

[...], bash menampilkan variabel "$ @", yang diperluas ke semua parameter baris perintah yang dipisahkan oleh spasi.

Dari Bash dengan contoh .

miku
sumber
6
Jadi, apakah "$ @" tidak hanya mengutip sekitar $ @ tetapi pada dasarnya variabel bawaan yang berbeda?
ben
5
@ Ben Ini adalah variabel tunggal tetapi membutuhkan tanda kutip ganda di sekitarnya untuk memiliki nilai berguna yang berbeda dari (rusak) $*. Saya percaya ada perkembangan sejarah di sini; $*tidak berfungsi seperti yang dirancang, jadi $@diciptakan untuk menggantikannya; tetapi aturan kutip menjadi seperti apa adanya, tanda kutip ganda masih diperlukan (atau akan kembali ke $*semantik yang rusak ).
tripleee
7
"Jadi, apakah" $ @ "tidak hanya mengutip sekitar $ @ tetapi pada dasarnya variabel bawaan yang berbeda?" - Untuk semua maksud dan tujuan, ya: stackoverflow.com/a/28099707/162094
Stuart Berg
1
Jika Anda memanggil script yang berisi echo "$@"sebagai ./script.sh a "b c" dmaka Anda hanya mendapatkan a b c dbukan a "b c" d, yang sangat jauh berbeda.
isarandi
7
@isarandi Meskipun benar bahwa output tidak lagi berisi tanda kutip, itulah yang Anda harapkan ... tetapi yakinlah bahwa dalam skrip, echomenerima tiga argumen: "a" "b c" "d"(kemudian shell bergabung bersama-sama sebagai bagian dari ekspansi string-nya). Tetapi jika Anda menggunakan for i in "$@"; do echo $i; doneAnda akan mendapatkan a⏎b c⏎d.
FeRD
64

Saya menyadari ini telah dijawab dengan baik tetapi inilah perbandingan antara "$ @" $ @ "$ *" dan $ *

Isi skrip uji:

# cat ./test.sh
#!/usr/bin/env bash
echo "================================="

echo "Quoted DOLLAR-AT"
for ARG in "$@"; do
    echo $ARG
done

echo "================================="

echo "NOT Quoted DOLLAR-AT"
for ARG in $@; do
    echo $ARG
done

echo "================================="

echo "Quoted DOLLAR-STAR"
for ARG in "$*"; do
    echo $ARG
done

echo "================================="

echo "NOT Quoted DOLLAR-STAR"
for ARG in $*; do
    echo $ARG
done

echo "================================="

Sekarang, jalankan skrip pengujian dengan berbagai argumen:

# ./test.sh  "arg with space one" "arg2" arg3
=================================
Quoted DOLLAR-AT
arg with space one
arg2
arg3
=================================
NOT Quoted DOLLAR-AT
arg
with
space
one
arg2
arg3
=================================
Quoted DOLLAR-STAR
arg with space one arg2 arg3
=================================
NOT Quoted DOLLAR-STAR
arg
with
space
one
arg2
arg3
=================================
JDS
sumber
1
demonstrasi singkat yang baik dan kontribusi untuk jawaban ini.
Merlin
33
#!/usr/bin/env bash
while [ "$1" != "" ]; do
  echo "Received: ${1}" && shift;
done;

Hanya berpikir ini mungkin sedikit lebih berguna ketika mencoba menguji bagaimana args masuk ke skrip Anda

Gregory Patmore
sumber
6
ini jelas tidak membantu menjawab pertanyaannya tetapi ini memang bermanfaat. terbalik!
Dario Russo
2
Itu rusak ketika parameter kosong dilewatkan. Anda harus memeriksa$#
Sebi
penguji args yang bagus, setuju "", ''sebagai argumen, juga jika tidak ada args itu diam. Saya mencoba untuk memperbaikinya, tetapi membutuhkan for for loop dan counter $#. Saya baru saja menambahkan ini di akhir:echo "End of args or received quoted null"
Merlin
8

SUN Unix saya memiliki banyak batasan, bahkan "$ @" tidak diartikan seperti yang diinginkan. Solusi saya adalah $ {@}. Sebagai contoh,

#!/bin/ksh
find ./ -type f | xargs grep "${@}"

Omong-omong, saya harus memiliki skrip khusus ini karena Unix saya juga tidak mendukung grep -r

Hampden123
sumber
Ini adalah pertanyaan Bash; Anda menggunakanksh
tripleee
6

Jika Anda memasukkan $@string yang dikutip dengan karakter lain, tingkah lakunya sangat aneh ketika ada beberapa argumen, hanya argumen pertama yang disertakan di dalam tanda kutip.

Contoh:

#!/bin/bash
set -x
bash -c "true foo $@"

Hasil:

$ bash test.sh bar baz
+ bash -c 'true foo bar' baz

Tapi pertama-tama menugaskan ke variabel yang berbeda:

#!/bin/bash
set -x
args="$@"
bash -c "true foo $args"

Hasil:

$ bash test.sh bar baz
+ args='bar baz'
+ bash -c 'true foo bar baz'
Kolin
sumber
3
Saya tidak akan menyangkal itu membingungkan, tetapi sebenarnya masuk akal dalam semantik "$@"di bash. Ini juga membantu menggambarkan perbedaan utama antara $@dan $*, dan mengapa keduanya berguna. Dari bash(1)halaman manual bagian Parameter Khusus: " *- Ketika ekspansi terjadi dalam tanda kutip ganda, itu berkembang menjadi satu kata dengan nilai setiap parameter [...] Artinya, "$*"sama dengan "$1c$2c...", di mana c[ $IFS]." Dan memang, menggunakan $*bukannya $@dalam contoh pertama Anda akan sudah terjaring keluaran identik dengan versi kedua.
FeRD
2
Sekarang, bandingkan dengan "$@". Lagi dari halaman manual: " @- Ketika ekspansi terjadi dalam tanda kutip ganda, setiap parameter meluas ke kata yang terpisah. Artinya, "$@"setara dengan "$1" "$2"... Jika ekspansi kutip ganda terjadi dalam kata, ekspansi parameter pertama digabungkan dengan bagian awal dari kata aslinya, dan perluasan dari parameter terakhir digabungkan dengan bagian terakhir dari kata aslinya. " ... Dan memang, jika kode Anda telah bash -c "true foo $@ bar baz", maka jalankan itu test.sh one twoakan bersih bash -c 'true foo one' 'two bar baz'.
FeRD
1
Terima kasih atas kutipan dan info dokumen $*, saya sepertinya lupa bahwa itu ada ..
ColinM
Heh. Saya sebaliknya, $@baru mendapatkan traksi ketika saya pertama kali mulai scripting shell, saya masih harus mengingatkan diri saya ada di sana. Itu umum untuk "$*"digunakan dalam skrip ... maka penulis akan menyadari bahwa itu menghancurkan semua argumen mereka bersama-sama, jadi mereka akan mencoba segala macam omong kosong yang kompleks dengan pemisahan kata "$*", atau [kembali] menyusun daftar arg dengan mengulang lebih shiftmenarik mereka satu per satu ... hanya menggunakan $@memecahkan itu. (Membantu bash menggunakan mnemonic yang sama untuk mengakses anggota array, juga: ${var[*]}untuk mereka semua sebagai kata, ${var[@]}untuk daftar kata.)
FeRD
Masalah sebenarnya di sini adalah bahwa Anda menggunakan bash -ccara yang sama sekali tidak masuk akal.
tripleee
4

Kadang-kadang Anda ingin memberikan semua argumen Anda, tetapi didahului dengan bendera (misalnya --flag)

$ bar --flag "$1" --flag "$2" --flag "$3"

Anda dapat melakukan ini dengan cara berikut:

$ bar $(printf -- ' --flag "%s"' "$@")

Catatan: untuk menghindari pemisahan bidang ekstra, Anda harus mengutip %sdan $@, dan untuk menghindari memiliki string tunggal, Anda tidak dapat mengutip subshell dari printf.

Kvantour
sumber
3

Berfungsi dengan baik, kecuali jika Anda memiliki spasi atau karakter yang lolos. Saya tidak menemukan cara untuk menangkap argumen dalam kasus ini dan mengirim ke ssh di dalam skrip.

Ini bisa bermanfaat tetapi sangat jelek

_command_opts=$( echo "$@" | awk -F\- 'BEGIN { OFS=" -" } { for (i=2;i<=NF;i++) { gsub(/^[a-z] /,"&@",$i) ; gsub(/ $/,"",$i );gsub (/$/,"@",$i) }; print $0 }' | tr '@' \' )
pengguna6650302
sumber
3

Banyak jawaban di sini merekomendasikan $@atau $*dengan dan tanpa tanda kutip, namun sepertinya tidak ada yang menjelaskan apa yang sebenarnya dilakukan dan mengapa Anda harus seperti itu. Jadi izinkan saya mencuri ringkasan yang sangat bagus ini dari jawaban ini :

masukkan deskripsi gambar di sini

Perhatikan bahwa kutipan membuat semua perbedaan dan tanpa keduanya keduanya memiliki perilaku yang identik.

Untuk tujuan saya, saya perlu meneruskan parameter dari satu skrip ke skrip lain apa adanya dan untuk itu pilihan terbaik adalah:

# file: parent.sh
# we have some params passed to parent.sh 
# which we will like to pass on to child.sh as-is

./child.sh $*

Perhatikan tidak ada kutipan dan $@harus berfungsi juga dalam situasi di atas.

Shital Shah
sumber
2

bar "$@" akan setara dengan bar "$1" "$2" "$3" "$4"

Perhatikan bahwa tanda kutip itu penting!

"$@", $@, "$*"Atau $*masing-masing akan berperilaku sedikit berbeda mengenai melarikan diri dan Rangkaian seperti yang dijelaskan dalam jawaban stackoverflow .

Satu use case yang terkait erat adalah memberikan semua argumen di dalam argumen seperti ini:

bash -c "bar \"$1\" \"$2\" \"$3\" \"$4\"".

Saya menggunakan variasi jawaban @ kvantour untuk mencapai ini:

bash -c "bar $(printf -- '"%s" ' "$@")"

Davidiusdadi
sumber