2015-07-28 9 views
12

Byłem zaskoczony z linią zaznaczono (!!) w poniższym przykładzie:Zaskakujące ekspansja tablica zachowanie

log1() { echo [email protected]; } 
log2() { echo "[email protected]"; } 

X=(a b) 
IFS='|' 

echo ${X[@]} # prints a b 
echo "${X[@]}" # prints a b 
echo ${X[*]} # prints a b 
echo "${X[*]}" # prints a|b 
echo "---" 
log1 ${X[@]} # prints a b 
log1 "${X[@]}" # prints a b 
log1 ${X[*]} # prints a b 
log1 "${X[*]}" # prints a b (!!) 
echo "---" 
log2 ${X[@]} # prints a b 
log2 "${X[@]}" # prints a b 
log2 ${X[*]} # prints a b 
log2 "${X[*]}" # prints a|b 

Oto moje rozumienie zachowań:

  • ${X[*]} i ${X[@]} zarówno poszerzyć do a b
  • "${X[*]}" rozwija się do "a|b"
  • "${X[@]}" rozszerza się "a" "b"
  • $* i [email protected] mają takie samo zachowanie jak ${X[*]} i ${X[@]}, z wyjątkiem ich treść, parametry programu lub funkcji

ten wydaje się być potwierdzone przez bash manual.

W linii log1 "${X[*]}", dlatego oczekuję, że wyrażone wyrażenie rozszerzy się do "a | b", a następnie zostanie przekazane do funkcji log1. Funkcja ma jeden parametr, który wyświetla. Dlaczego dzieje się coś innego?

Byłoby fajnie, gdyby odpowiedzi były poparte instrukcjami/standardowymi referencjami!

Odpowiedz

7

IFS służy nie tylko do łączenia elementów z ${X[*]}, ale także do podziału niecytowanych rozszerzeń [email protected]. Dla log1 "${X[*]}" dodaje się dzieje:

  1. "${X[*]}" rozszerza się a|b zgodnie z oczekiwaniami, więc $1 jest ustawiony na a|b wewnątrz log1.
  2. Po rozwinięciu [email protected] (bez cudzysłowu) wynikowy ciąg to a|b.
  3. nienotowanych rozszerzalności ulega słowo rozdzierające z | jako ogranicznik (z powodu ogólnej wartości IFS), tak że echo otrzymuje dwa argumenty a i b.
+0

Oooh. Dobrze. Czy istnieje sposób, aby wytworzyć zachowanie, które próbowałem mieć (tj. Formatowanie z ogranicznikiem, ale nie dzielenie go słowami)? Myślę, że 'printf' może być najprostszą opcją (na przykład: http://stackoverflow.com/questions/12985178/) – Norswap

+2

Zawsze używaj cytowanych rozszerzeń. To jest odpowiedź. Twoja funkcja 'log1' jest po prostu niepoprawna. 'log2' jest poprawną formą. –

+2

Dokładniej, zawsze cytuj '$ @'. (Są przypadki narożne, w których możesz celowo zostawić '$ *' lub inne parametry nie cytowane, ale '$ @' * istnieje * do cytowania, poza tym jest identyczne z '$ *'.) – chepner

5

To dlatego $IFS jest ustawiony na |:

(X='a|b' ; IFS='|' ; echo $X) 

wyjściowa:

a b 

man bash mówi:

IFS wewnętrznego pola separator, który jest używany do podziału na słowa po rozbudowie ...

3

W sekcji specyfikacji POSIX na temat [Parametry specjalne [(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02) znajdujemy.

@

rozszerza do pozycyjnych, począwszy od pierwszego. Kiedy rozwijanie odbywa się w podwójnych cudzysłowach i gdy wykonywane jest dzielenie pól (patrz Podział na pole), każdy parametr pozycyjny powinien rozszerzać się jako oddzielne pole, z zastrzeżeniem, że rozszerzenie pierwszego parametru będzie nadal połączone z początkową częścią oryginalne słowo (zakładając, że rozszerzony parametr został osadzony w słowie), a rozwinięcie ostatniego parametru będzie nadal połączone z ostatnią częścią oryginalnego słowa. Jeśli nie ma żadnych parametrów pozycyjnych, rozszerzenie "@" powinno generować zero pól, nawet gdy "@" jest podwójnie cytowane.

*

Rozwija się do parametrów pozycyjnych, zaczynając od jednego. Gdy rozszerzenie występuje w ciągu podwójnego cudzysłowu (patrz Podwójne cudzysłowy), powinno ono rozwinąć się do pojedynczego pola z wartością każdego parametru oddzielonego pierwszym znakiem zmiennej IFS lub przez if, jeśli IFS jest rozbrojony.Jeśli IFS jest ustawiony na łańcuch zerowy, nie jest to równoważne z rozbrojeniem; jego pierwszy znak nie istnieje, więc wartości parametrów są łączone.

zatem począwszy od podanych wariantów (są prostsze)

Widzimy zatem, że rozszerzanie * „rozwinąć [S] w jednym polu wartości każdego parametru, oddzielone przez pierwszy znak Zmienna IFS ". Dlatego otrzymujesz a|b od echo "${X[*]" i log2 "${X[*]}".

Widzimy również, że ekspansja @ rozwija się w taki sposób, że "każdy parametr pozycyjny rozwija się jako osobne pole". To dlatego otrzymujesz a b od echo "${X[@]}" i log2 "${X[@]}".

Czy widzisz tę notatkę dotyczącą podziału pola w tekście specyfikacji? "gdzie odbywa się dzielenie pól (patrz Podział na pola)"? To klucz do tajemnicy tutaj.

Poza ofertami zachowanie rozszerzeń jest takie samo. Różnica polega na tym, co dzieje się później. W szczególności podział na pola i słowa.

Najprostszym sposobem pokazania problemu jest uruchomienie kodu z włączoną opcją set -x.

Który dostaje to:

+ X=(a b) 
+ IFS='|' 
+ echo a b 
a b 
+ echo a b 
a b 
+ echo a b 
a b 
+ echo 'a|b' 
a|b 
+ echo --- 
--- 
+ log1 a b 
+ echo a b 
a b 
+ log1 a b 
+ echo a b 
a b 
+ log1 a b 
+ echo a b 
a b 
+ log1 'a|b' 
+ echo a b 
a b 
+ echo --- 
--- 
+ log2 a b 
+ echo a b 
a b 
+ log2 a b 
+ echo a b 
a b 
+ log2 a b 
+ echo a b 
a b 
+ log2 'a|b' 
+ echo 'a|b' 
a|b 

Rzeczą godną uwagi jest to, że przez czas log1 nazywany jest we wszystkich, ale ostateczna przypadku | jest już poszedł.

Powodem, dla którego już go nie ma, jest to, że bez cytowań wyniki z rozszerzenia zmiennej (w tym przypadku rozszerzenie *) są podzielone na pola/słowa. I od IFS jest używany zarówno, aby połączyć rozszerzane pola, a następnie podzielić je ponownie | zostanie połknięty przez podział pola.

I dokończyć wyjaśnienia (na razie faktycznie w pytaniu), powód ten nie powiedzie log1 nawet z cytowanego wersji ekspansji na rozmowy (tzn log1 "${X[*]}" który rozszerza się log1 "a|b" poprawnie) dlatego log1samego nie używa cytowanej ekspansji @, więc rozszerzenie @ w samej funkcji jest podzielone na słowa (co można zauważyć w przypadku echo a b w tej obudowie log1, a także wszystkich innych przypadkach log1).