$ * Ve $ @ arasındaki fark nedir?


73

Aşağıdaki kodu göz önünde bulundurun:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

Çıktıları:

1 2 3 4

1 2 3 4

Ksh88 kullanıyorum, ancak diğer yaygın mermilerle de ilgileniyorum. Belirli mermilerle ilgili herhangi bir özelliği biliyorsanız, lütfen bunlardan bahsedin.

Follwing'i Solaris'teki Ksh man sayfasında buldum:

$ * Ve $ @, kote edilmediğinde veya bir parametre atama değeri olarak veya bir dosya adı olarak kullanıldığında aynıdır. Bununla birlikte, bir komut argümanı olarak kullanıldığında, $ *, `` $ 1d $ 2d ... '' değerine eşittir; burada d, IFS değişkeninin ilk karakteridir; $ @, $ 1 $ 2 değerine eşittir ...

IFSDeğişkeni değiştirmeyi denedim , ancak çıktıyı değiştirmiyor. Belki yanlış bir şey yapıyorumdur?

Yanıtlar:


96

Alıntılanmadıklarında $*ve $@aynı olduklarında. Bunlardan hiçbirini kullanmamalısınız, çünkü boşluklar veya joker karakterler içeren argümanlarınız olur olmaz beklenmedik şekilde kırılabilirler.


"$*"tek bir kelimeye genişler "$1c$2c...". Genellikle cbir alandır, ancak aslında ilk karakteridir IFS, yani seçtiğiniz herhangi bir şey olabilir.

Bunun için şimdiye kadar bulduğum tek iyi kullanım:

bağımsız değişkenleri virgülle birleştir (basit sürüm)

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

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

belirtilen sınırlayıcı ile argümanları birleştir (daha iyi sürüm)

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

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

"$@" sözcükleri ayırmak için genişler: "$1" "$2" ...

Bu neredeyse her zaman istediğin şey. Her konum parametresini ayrı bir sözcüğe genişletir, bu komut satırında veya fonksiyon argümanlarını alıp bunları başka bir komut veya fonksiyona iletmek için mükemmel kılar. İkili tırnak işaretleri kullanılarak genişlediğinden "$1", bir boşluk veya yıldız işareti içeriyorsa , şeylerin bozulmadığı anlamına gelir *.


En adlandırılan bir senaryo yazalım svimçalıştırır vimile sudo. Farkı göstermek için üç sürüm yapacağız.

svim1

#!/bin/sh
sudo vim $*

svim2

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

svim3

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

Hepsi basit durumlar için iyi olacak, örneğin boşluk içermeyen tek bir dosya adı:

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

Ancak sadece $*ve "$@"birden fazla argüman varsa düzgün çalışır.

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"

Ve sadece "$*"ve "$@"boşluk içeren argümanlar varsa düzgün çalışın.

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"

Yani sadece "$@"her zaman düzgün çalışacaktır.


typesetYerel bir değişken nasıl olduğunu ksh( bashve ashkullanmak localyerine). IFSİşlev geri döndüğünde önceki değerine geri döndürüleceği anlamına gelir . Bu önemlidir, çünkü daha sonra çalıştırdığınız komutlar IFSstandart olmayan bir şeye ayarlanmışsa düzgün çalışmayabilir .


2
Harika bir açıklama, çok teşekkür ederim.
rahmu

Kullanma örneği için teşekkür ederiz $*. Her zaman tamamen işe yaramaz olduğunu düşünüyordum ... sınırlayıcıya katılmak iyi bir kullanım durumuydu.
anishsane

35

Kısa cevap: kullanın"$@" (çift tırnak işaretleyin). Diğer formlar çok nadiren kullanışlıdır.

"$@"oldukça garip bir sözdizimi. Ayrı alanlar olarak tüm konumsal parametrelerle değiştirilir. Konumsal parametre yoksa ( $#0'dır), o zaman "$@"hiçbir şeye genişlemez (boş bir dize değil, ancak 0 öğeli bir liste), eğer bir konumsal parametre varsa o "$@"zaman eşdeğerdir "$1", eğer iki konumsal parametre varsa "$@"eşdeğerdir "$1" "$2", vb.

"$@"Bir betiğin veya işlevin argümanlarını başka bir komuta aktarmanıza olanak tanır. Sarf malzemesinin çağrıldığı aynı argüman ve seçeneklerle bir komut çağırmadan önce ortam değişkenlerini ayarlama, veri dosyalarını hazırlama vb. İşlemleri yapan sarmalayıcılar için çok kullanışlıdır.

Örneğin, aşağıdaki işlev çıktısını filtreler cvs -nq update. Dışında çıkış filtreleme ve dönüş statüsünden (olduğu bu grepyerine daha cvs), çağırarak cvssmbazı argümanlar çağıran gibi davranır cvs -nq updatebu argümanlarla.

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

"$@"Konumsal parametreler listesine genişler. Dizileri destekleyen kabuklarda, dizinin öğelerinin listesine genişletmek için benzer bir sözdizimi vardır: "${array[@]}"(ayraçlar zsh dışında zorunludur). Yine, çift tırnaklar biraz yanıltıcıdır: alanların bölünmesine ve dizi elemanlarının desen oluşumuna karşı korurlar, ancak her bir dizi elemanı kendi alanında sona erer.

Bazı eski kabukları tartışmasız bir hataya sahipti: konumsal argümanlar olmadığında, "$@"boş bir dize içeren tek bir alana genişletildi, hiçbir alana değil. Bu, geçici çözüm${1+"$@"} yol açtı ( Perl belgeleriyle ünlü oldu ). Bourne kabuğunun ve OSF1 uygulamasının yalnızca eski sürümleri etkilenir, modern uyumlu değiştirmelerinin hiçbiri (kül, ksh, bash,…) etkilenmez. /bin/shBildiğim 21. yüzyılda piyasaya sürülen herhangi bir sistemden etkilenmiyor (Tru64 bakım sürümünü saymazsanız ve hatta /usr/xpg4/bin/shgüvenli bile olsa , yalnızca #!/bin/shkomut dosyası etkilenir, #!/usr/bin/env shPATH'niz POSIX uyumluluğu için ayarlanmış olduğu sürece komut dosyaları değil ) . Kısacası, bu endişelenmenize gerek olmayan tarihi bir anekdottur.


"$*"her zaman bir kelimeye genişler. Bu sözcük, aradaki boşlukla birleştirilen konumsal parametreleri içerir. (Daha genel olarak, ayırıcı IFSdeğişkenin değerinin ilk karakteridir . Değeri IFSboş dize ise, ayırıcı boş dizedir.) Konumsal parametreler "$*"yoksa boş dize, eğer iki varsa konumsal parametreler ve IFSvarsayılan değeri var sonra "$*"eşdeğer "$1 $2", vb.

$@ve $*dış tırnaklar eşdeğerdir. Konumsal parametreler listesine, ayrı alanlar gibi genişlerler "$@"; ancak sonuçta ortaya çıkan her alan, daha önce belirtilmeyen değişken genişlemelerinde olduğu gibi, dosya adı joker karakterleri olarak işlenen ayrı alanlara bölünür.

Örneğin, geçerli dizin üç dosya içeriyorsa bar, bazve foodaha sonra,:

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`

1
Tarihsel not: bazı eski Bourne mermilerinde, "$@"gerçekten boş ipliden
ninjalj

25

İşte $*ve arasındaki farkı gösteren basit bir betik $@:

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

Çıktı:

% 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<==

Dizi sözdiziminde, $*veya kullanırken fark yoktur $@. Bunları yalnızca çift tırnak işareti "$*"ve kullandığınızda anlamlıdır "$@".


Mükemmel örnek! Ancak kullanımını açıklayabilir misiniz IFS="^${IFS}"?
Russ

@Russ: Size değerin ilk karakterle ne kadar uyumlu olduğunu gösterir IFS.
cuonglm

Aynı şekilde IFS="^xxxxx"mi? İzleyen ${IFS}son ek, daha sonra bir şekilde otomatik olarak orijinal IFS'yi otomatik olarak geri kazanma gibi (örneğin: ilk karakter otomatik olarak yerinden çıkmış veya başka bir şey) yaptığınızı düşündürdü.
Russ

11

Sağladığınız kod aynı sonucu verecektir. Daha iyi anlamak için şunu deneyin:

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

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

Çıktı şimdi farklı olmalı. İşte ne alıyorum:

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

Bu benim için çalıştı bash. Bildiğim kadarıyla, ksh çok farklı olmamalı. Temel olarak, alıntı $*, her şeyi bir kelime olarak kabul eder ve alıntı $@, yukarıdaki örnekte görüldüğü gibi listeyi ayrı kelimeler olarak değerlendirir.

IFSDeğişkeni kullanmaya örnek olarak $*, bunu göz önünde bulundurun

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

Bunun bir sonucu olarak alıyorum:

$ fooifs 1 2 3 4
1c2c3c4

Ayrıca, sadece aynı şekilde çalıştığını doğruladım ksh. Hem bashve kshburada test OSX altındaydı ama o kadar önemli olur nasıl göremez.


"bir fonksiyon içinde değişti - genel etkilenmez". Bunu test ettin mi?
Mikel

Evet, kontrol ettim. İç huzuru unset IFSiçin, orjinaline sıfırlamak için sonuna ekleyeceğim , ancak benim için hiç sorun olmadı ve echo $IFSondan aldığım standart çıktıyla sonuçlandı. IFSKıvrımlı parantez takmanın ayarlanması yeni bir kapsam sunar, bu nedenle dışa aktarmazsanız dış kısmı etkilemez IFS.
Wojtek Rzepala

echo $IFShiçbir şey ispat etmiyor, çünkü kabuk onu görüyor ,, ama sonra sözcük bölmeyi kullanarak IFS! Dene echo "$IFS".
Mikel

iyi bir nokta. üzülme IFSbunu çözmelidir.
Wojtek Rzepala

IFSFonksiyonu çağırmadan önce farklı bir özel değere sahip olmadıkça . Fakat evet, çoğu zaman IFS'yi rahatsız etmek işe yarayacak.
Mikel

6

Konum parametrelerini doğru şekilde kullanması gereken scriptleri yazarken fark önemlidir.

Aşağıdaki çağrıyı hayal edin:

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

İşte sadece 4 parametre var:

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

Benim durumumda, aynı parametreleri kabul ettiği myuseraddiçin sadece bir sarmalayıcıdır useradd, ancak kullanıcı için bir kota ekler:

#!/bin/bash -e

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

Alıntı useradd "$@"ile yapılan çağrıya dikkat edin $@. Bu, parametrelere saygı gösterecek ve onları oldukları gibi gönderecektir useradd. Eğer bir alıntı yapmazsanız $@(veya $*ayrıca alıntı yapmazsanız), useradd boşluk içeren 3. parametre ikiye bölüneceğinden useradd, 5 parametre görecektir:

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

(Kullanmak olsaydı ve diğer taraftan, "$*", useradd sadece bir parametre görecekti: -m -c Carlos Campderrós ccampderros)

Yani, kısacası, çok kelimeli parametrelere saygılı parametrelerle çalışmak gerekirse, kullanın "$@".


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).

// erkek bash . ksh, haksızlık, benzer davranış.


2

Arasındaki farklar hakkında konuşmak zshve bash:

Etrafına tırnak $@ve $*, zshve bashaynı davranır ve ben sonuç tüm kabukları arasında oldukça standart olduğunu tahmin:

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

Tırnak olmadan, sonuç için aynıdır $*ve $@fakat farklı bashve içinde zsh. Bu durumda zshbazı garip davranışlar gösterir:

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, açıkça istenmedikçe, metinsel verileri IFS kullanarak bölmez, ancak burada boş argümanın beklenmedik bir şekilde listede bulunmadığına dikkat edin.)


1
Zsh'de $@bu anlamda özel değildir: en fazla bir kelimeye $xgenişler , fakat boş değişkenler hiçbir şeye genişlemez (boş bir kelime değil). Boş veya tanımsız ile deneyin . print -l a $foo bfoo
Gilles

0

Cevaplardan biri $*(ki "uyarıcı" olarak düşündüğüm), nadiren faydalı olduğunu söylüyor .

İle google'da arama yapıyorum G() { IFS='+' ; w3m "https://encrypted.google.com/search?q=$*" ; }

URL’ler genellikle a ile ayrıldığından +, klavyem ulaşılması daha kolay hale getirir   +, $*+ $IFShissetmeye değer.

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.