Apa perbedaan antara $ * dan $ @?

73

Pertimbangkan kode berikut:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

Ini menghasilkan:

1 2 3 4

1 2 3 4

Saya menggunakan Ksh88, tetapi saya tertarik pada shell umum lainnya juga. Jika Anda mengetahui kekhasan untuk cangkang tertentu, mohon sebutkan.

Saya menemukan follwing di halaman manual Ksh di Solaris:

Arti $ * dan $ @ identik ketika tidak dikutip atau ketika digunakan sebagai nilai penetapan parameter atau sebagai nama file. Namun, ketika digunakan sebagai argumen perintah, $ * setara dengan `` $ 1d $ 2d ... '', di mana d adalah karakter pertama dari variabel IFS, sedangkan $ @ setara dengan $ 1 $ 2 ....

Saya mencoba memodifikasi IFSvariabel, tetapi tidak mengubah output. Mungkin saya melakukan sesuatu yang salah?

rahmu
sumber

Jawaban:

96

Ketika mereka tidak dikutip, $*dan $@sama. Anda tidak boleh menggunakan salah satu dari ini, karena mereka dapat pecah secara tiba-tiba begitu Anda memiliki argumen yang berisi spasi atau wildcard.


"$*"meluas ke satu kata "$1c$2c...". Biasanya cadalah spasi, tetapi sebenarnya adalah karakter pertama IFS, jadi itu bisa apa saja yang Anda pilih.

Satu-satunya penggunaan yang baik yang pernah saya temukan untuk itu adalah:

gabungkan argumen dengan koma (versi sederhana)

join1() {
    typeset IFS=,
    echo "$*"
}

join1 a b c   # => a,b,c

bergabung dengan argumen dengan pembatas yang ditentukan (versi yang lebih baik)

join2() {
    typeset IFS=$1   # typeset makes a local variable in ksh (see footnote)
    shift
    echo "$*"
}

join2 + a b c   # => a+b+c

"$@" memperluas kata-kata yang terpisah: "$1" "$2" ...

Ini hampir selalu yang Anda inginkan. Itu memperluas setiap parameter posisi ke kata yang terpisah, yang membuatnya sempurna untuk mengambil argumen baris perintah atau fungsi dan kemudian meneruskannya ke perintah atau fungsi lain. Dan karena diperluas menggunakan tanda kutip ganda, itu berarti hal-hal tidak rusak jika, katakanlah, "$1"berisi spasi atau tanda bintang ( *).


Mari kita menulis skrip yang disebut svimberjalan vimdengan sudo. Kami akan melakukan tiga versi untuk menggambarkan perbedaannya.

svim1

#!/bin/sh
sudo vim $*

svim2

#!/bin/sh
sudo vim "$*"

svim3

#!/bin/sh
sudo vim "$@"

Semuanya akan baik-baik saja untuk kasus-kasus sederhana, misalnya satu nama file yang tidak mengandung spasi:

svim1 foo.txt             # == sudo vim foo.txt
svim2 foo.txt             # == sudo vim "foo.txt"
svim2 foo.txt             # == sudo vim "foo.txt"

Tetapi hanya $*dan "$@"bekerja dengan baik jika Anda memiliki banyak argumen.

svim1 foo.txt bar.txt     # == sudo vim foo.txt bar.txt
svim2 foo.txt bar.txt     # == sudo vim "foo.txt bar.txt"   # one file name!
svim3 foo.txt bar.txt     # == sudo vim "foo.txt" "bar.txt"

Dan hanya "$*"dan "$@"berfungsi dengan baik jika Anda memiliki argumen yang berisi spasi.

svim1 "shopping list.txt" # == sudo vim shopping list.txt   # two file names!
svim2 "shopping list.txt" # == sudo vim "shopping list.txt"
svim3 "shopping list.txt" # == sudo vim "shopping list.txt"

Jadi hanya "$@"akan berfungsi dengan baik sepanjang waktu.


typesetadalah cara membuat variabel lokal di ksh( bashdan ashmenggunakan localsebagai gantinya). Itu berarti IFSakan dikembalikan ke nilai sebelumnya ketika fungsi kembali. Ini penting, karena perintah yang Anda jalankan sesudahnya mungkin tidak berfungsi dengan baik jika IFSdisetel ke sesuatu yang tidak standar.

Mikel
sumber
2
Penjelasan yang bagus, terima kasih banyak.
rahmu
Terima kasih atas contoh penggunaannya $*. Saya selalu menganggap itu sama sekali tidak berguna ... bergabung dengan pembatas adalah kasus penggunaan yang baik.
anishsane
35

Jawaban singkat: gunakan"$@" (perhatikan tanda kutip ganda). Bentuk-bentuk lain sangat jarang berguna.

"$@"adalah sintaks yang agak aneh. Itu digantikan oleh semua parameter posisi, sebagai bidang yang terpisah. Jika tidak ada parameter posisional ( $#0), maka "$@"ekspansi menjadi nol (bukan string kosong, tetapi daftar dengan 0 elemen), jika ada satu parameter posisional maka "$@"sama dengan "$1", jika ada dua parameter posisional maka "$@"setara dengan "$1" "$2", dll.

"$@"memungkinkan Anda untuk meneruskan argumen skrip atau fungsi ke perintah lain. Ini sangat berguna untuk pembungkus yang melakukan hal-hal seperti mengatur variabel lingkungan, menyiapkan file data, dll. Sebelum memanggil perintah dengan argumen dan opsi yang sama dengan pembungkus dipanggil.

Misalnya, fungsi berikut memfilter output dari cvs -nq update. Terlepas dari pemfilteran output dan status pengembalian (yang grepbukan dari itu cvs), memanggil cvssmbeberapa argumen berperilaku seperti memanggil cvs -nq updatedengan argumen ini.

cvssm () { cvs -nq update "$@" | egrep -v '^[?A]'; }

"$@"memperluas ke daftar parameter posisi. Dalam shell yang mendukung array, ada sintaksis yang sama untuk memperluas ke daftar elemen array: "${array[@]}"(kawat gigi wajib diisi kecuali di zsh). Sekali lagi, tanda kutip ganda agak menyesatkan: mereka melindungi terhadap pemisahan bidang dan pembuatan pola elemen array, tetapi setiap elemen array berakhir di bidangnya sendiri.

Beberapa cangkang kuno memiliki apa yang bisa dibilang bug: ketika tidak ada argumen posisional, "$@"diperluas ke bidang tunggal yang berisi string kosong, bukannya ke bidang tidak ada. Ini mengarah pada solusi${1+"$@"} (dibuat terkenal melalui dokumentasi Perl ). Hanya versi yang lebih lama dari shell Bourne aktual dan implementasi OSF1 yang terpengaruh, tidak ada penggantian yang kompatibel modern (abu, ksh, bash, ...). /bin/shtidak terpengaruh pada sistem apa pun yang dirilis pada abad ke-21 yang saya tahu (kecuali jika Anda menghitung rilis pemeliharaan Tru64, dan bahkan ada /usr/xpg4/bin/shyang aman sehingga hanya #!/bin/shskrip yang terpengaruh, bukan #!/usr/bin/env shskrip selama PATH Anda diatur untuk kepatuhan POSIX) . Singkatnya, ini adalah anekdot sejarah yang tidak perlu Anda khawatirkan.


"$*"selalu mengembang menjadi satu kata. Kata ini berisi parameter posisi, disatukan dengan spasi di antaranya. (Lebih umum, pemisah adalah karakter pertama dari nilai IFSvariabel. Jika nilai IFSadalah string kosong, pemisah adalah string kosong.) Jika tidak ada parameter posisi maka "$*"string kosong, jika ada dua parameter posisi dan IFSmemiliki nilai standarnya kemudian "$*"setara dengan "$1 $2", dll.

$@dan $*kutipan luar sama. Mereka memperluas ke daftar parameter posisi, sebagai bidang yang terpisah, seperti "$@"; tetapi setiap bidang yang dihasilkan kemudian dibagi menjadi bidang-bidang terpisah yang diperlakukan sebagai pola wildcard nama file, seperti biasa dengan ekspansi variabel yang tidak dikutip.

Misalnya, jika direktori saat ini berisi tiga file bar, bazdan foo, maka:

set --         # no positional parameters
for x in "$@"; do echo "$x"; done  # prints nothing
for x in "$*"; do echo "$x"; done  # prints 1 empty line
for x in $*; do echo "$x"; done    # prints nothing
set -- "b* c*" "qux"
echo "$@"      # prints `b* c* qux`
echo "$*"      # prints `b* c* qux`
echo $*        # prints `bar baz c* qux`
for x in "$@"; do echo "$x"; done  # prints 2 lines: `b* c*` and `qux`
for x in "$*"; do echo "$x"; done  # prints 1 lines: `b* c* qux`
for x in $*; do echo "$x"; done    # prints 4 lines: `bar`, `baz`, `c*` and `qux`
Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Catatan sejarah: pada beberapa cangkang Bourne kuno, "$@"memang berkembang ke daftar yang berisi string kosong: unix.stackexchange.com/questions/68484/…
ninjalj
25

Berikut ini adalah skrip sederhana untuk menunjukkan perbedaan antara $*dan $@:

#!/bin/bash

test_param() {
  echo "Receive $# parameters"
  echo Using '$*'

  echo
  for param in $*; do
    printf '==>%s<==\n' "$param"
  done;

  echo
  echo Using '"$*"'
  for param in "$*"; do
    printf '==>%s<==\n' "$param"
  done;

  echo
  echo Using '$@'
  for param in $@; do
    printf '==>%s<==\n' "$param"
  done;

  echo
  echo Using '"$@"';
  for param in "$@"; do
  printf '==>%s<==\n' "$param"
  done
}

IFS="^${IFS}"

test_param 1 2 3 "a b c"

Keluaran:

% cuonglm at ~
% bash test.sh
Receive 4 parameters

Using $*
==>1<==
==>2<==
==>3<==
==>a<==
==>b<==
==>c<==

Using "$*"
==>1^2^3^a b c<==

Using $@
==>1<==
==>2<==
==>3<==
==>a<==
==>b<==
==>c<==

Using "$@"
==>1<==
==>2<==
==>3<==
==>a b c<==

Dalam sintaks array, tidak ada perbedaan saat menggunakan $*atau $@. Ini hanya masuk akal ketika Anda menggunakannya dengan tanda kutip ganda "$*"dan "$@".

cuonglm
sumber
Contoh bagus! Bisakah Anda menjelaskan penggunaannya IFS="^${IFS}"?
Russ
@Russ: Ini menunjukkan kepada Anda bagaimana nilai sesuai dengan karakter pertama di IFS.
cuonglm
Dengan cara yang sama itu IFS="^xxxxx"akan dilakukan? ${IFS}Akhiran trailing membuat saya berpikir Anda melakukan sesuatu yang lebih rumit, seperti entah bagaimana secara otomatis memulihkan IFS asli di akhir (misalnya: char pertama secara otomatis digeser keluar atau sesuatu).
Russ
11

Kode yang Anda berikan akan memberikan hasil yang sama. Untuk memahaminya dengan lebih baik, coba ini:

foo () {
    for i in "$*"; do
        echo "$i"
    done
}

bar () {
    for i in "$@"; do
        echo "$i"
    done
}

Outputnya sekarang harus berbeda. Inilah yang saya dapatkan:

$ foo() 1 2 3 4
1 2 3 4
$ bar() 1 2 3 4
1
2
3
4

Ini berhasil bagi saya bash. Sejauh yang saya tahu, ksh seharusnya tidak jauh berbeda. Pada dasarnya, mengutip $*akan memperlakukan semuanya sebagai satu kata, dan mengutip $@akan memperlakukan daftar sebagai kata-kata yang terpisah, seperti yang dapat dilihat pada contoh di atas.

Sebagai contoh menggunakan IFSvariabel dengan $*, pertimbangkan ini

fooifs () {
    IFS="c"            
    for i in "$*"; do
        echo "$i"
    done
    unset IFS          # reset to the original value
}

Saya mendapatkan ini sebagai hasilnya:

$ fooifs 1 2 3 4
1c2c3c4

Juga, saya baru saja mengkonfirmasi itu berfungsi sama ksh. Keduanya bashdan kshdiuji di sini di bawah OSX tapi saya tidak bisa melihat bagaimana itu akan berarti banyak.

Wojtek Rzepala
sumber
"Diubah dalam suatu fungsi - tidak mempengaruhi global". Apakah Anda mengujinya?
Mikel
Ya, saya sudah memeriksanya. Demi ketenangan pikiran, saya akan menambahkan unset IFSdi akhir untuk meresetnya ke aslinya, tetapi itu berhasil bagi saya tidak ada masalah dan melakukan hasil echo $IFSdalam output standar yang saya dapatkan dari itu. Mengatur IFSwithing kurung keriting memperkenalkan ruang lingkup baru, jadi kecuali Anda mengekspornya, itu tidak akan mempengaruhi luar IFS.
Wojtek Rzepala
echo $IFStidak membuktikan apa-apa, karena shell melihat ,, tetapi kemudian kata splitting menggunakan IFS! Coba echo "$IFS".
Mikel
poin yang bagus. unsetting IFSharus menyelesaikannya.
Wojtek Rzepala
Kecuali jika IFSmemiliki nilai khusus yang berbeda sebelum memanggil fungsi. Tapi ya, sebagian besar waktu yang tidak disetel IFS akan berfungsi.
Mikel
6

Perbedaannya penting ketika menulis skrip yang harus menggunakan parameter posisi dengan cara yang benar ...

Bayangkan panggilan berikut:

$ myuseradd -m -c "Carlos Campderrós" ccampderros

Di sini hanya ada 4 parameter:

$1 => -m
$2 => -c
$3 => Carlos Campderrós
$4 => ccampderros

Dalam kasus saya, myuseraddhanya pembungkus untuk useraddyang menerima parameter yang sama, tetapi menambahkan kuota untuk pengguna:

#!/bin/bash -e

useradd "$@"
setquota -u "${!#}" 10000 11000 1000 1100

Perhatikan panggilan untuk useradd "$@", dengan $@kutipan. Ini akan menghormati parameter dan mengirimkannya apa adanya useradd. Jika Anda membatalkan tanda kutip $@(atau menggunakan $*juga tanda kutip ), useradd akan melihat 5 parameter, karena parameter ke-3 yang berisi spasi akan dibagi menjadi dua:

$1 => -m
$2 => -c
$3 => Carlos
$4 => Campderrós
$5 => ccampderros

(dan sebaliknya, jika Anda menggunakan "$*", useradd akan hanya melihat satu parameter: -m -c Carlos Campderrós ccampderros)

Jadi, singkatnya, jika Anda perlu bekerja dengan parameter yang menghormati parameter multi-kata, gunakan "$@".

Carlos Campderrós
sumber
4
   *      Expands  to  the positional parameters, starting from one.  When
          the expansion occurs within double quotes, it expands to a  sin
          gle word with the value of each parameter separated by the first
          character of the IFS special variable.  That is, "$*" is equiva
          lent to "$1c$2c...", where c is the first character of the value
          of the IFS variable.  If IFS is unset, the parameters are  sepa
          rated  by  spaces.   If  IFS  is null, the parameters are joined
          without intervening separators.
   @      Expands to the positional parameters, starting from  one.   When
          the  expansion  occurs  within  double  quotes,  each  parameter
          expands to a separate word.  That is, "$@" is equivalent to "$1"
          "$2"  ...   If the double-quoted expansion occurs within a word,
          the expansion of the first parameter is joined with  the  begin
          ning  part  of  the original word, and the expansion of the last
          parameter is joined with the last part  of  the  original  word.
          When  there  are no positional parameters, "$@" and $@ expand to
          nothing (i.e., they are removed).

// bash bash . adalah ksh, affair, perilaku serupa.

buru-buru
sumber
2

Berbicara tentang perbedaan antara zshdan bash:

Dengan kutipan di sekitar $@dan $*, zshdan bashberperilaku sama, dan saya kira hasilnya cukup standar di antara semua shell:

 $ f () { for i in "$@"; do echo +"$i"+; done; }; f 'a a' 'b' ''
 +a a+
 +b+
 ++
 $ f () { for i in "$*"; do echo +"$i"+; done; }; f 'a a' 'b' ''
 +a a b +

Tanpa kutipan, hasilnya sama untuk $*dan $@, tetapi berbeda di dalam bashdan di zsh. Dalam hal ini zshmenunjukkan beberapa perilaku aneh:

bash$ f () { for i in $*; do echo +"$i"+; done; }; f 'a a' 'b' ''
+a+
+a+
+b+
zsh% f () { for i in $*; do echo +"$i"+; done; }; f 'a a' 'b' ''  
+a a+
+b+

(Zsh biasanya tidak membagi data tekstual menggunakan IFS, kecuali diminta secara eksplisit, tetapi perhatikan bahwa di sini argumen kosong secara tak terduga hilang dalam daftar.)

Stéphane Gimenez
sumber
1
Dalam zsh, $@tidak khusus dalam hal ini: $xmeluas hingga paling banyak satu kata, tetapi variabel kosong tidak berkembang menjadi apa-apa (bukan kata kosong). Coba print -l a $foo bdengan fookosong atau tidak terdefinisi.
Gilles 'SO- berhenti menjadi jahat'
0

Salah satu jawaban mengatakan $*(yang saya anggap sebagai "percikan") jarang berguna.

Saya mencari di Google dengan G() { IFS='+' ; w3m "https://encrypted.google.com/search?q=$*" ; }

Karena URL sering terpecah dengan +, tetapi keyboard saya membuatnya   lebih mudah dijangkau daripada +, $*+ $IFSmerasa berharga.

isomorfisma
sumber