2015-07-28 16 views
12

Sono stato sorpreso con la linea tracciata (!!) nel seguente esempio:espansione sorprendente varietà comportamento

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 

Qui è la mia comprensione del comportamento:

  • ${X[*]} e ${X[@]} sia espandere a a b
  • "${X[*]}" si espande a "a|b"
  • "${X[@]}" espande per "a" "b"
  • $* e [email protected] hanno lo stesso comportamento ${X[*]} e ${X[@]}, tranne che per il loro contenuto essendo i parametri del programma o funzione

Questo sembra essere confermato dal bash manual.

Nella riga log1 "${X[*]}", pertanto mi aspetto che l'espressione quotata si espanda a "a | b", quindi venga passata alla funzione log1. La funzione ha un singolo parametro di stringa che viene visualizzato. Perché succede qualcos'altro?

Sarebbe bello se le vostre risposte fossero supportate da riferimenti manuali/standard!

risposta

7

IFS viene utilizzato non solo per unire gli elementi di ${X[*]}, ma anche per suddividere l'espansione non quotata [email protected]. Per log1 "${X[*]}", si verifica quanto segue:

  1. "${X[*]}" espande a a|b come previsto, così $1 è impostato a|b all'interno log1.
  2. Quando [email protected] (non quotato) viene espanso, la stringa risultante è a|b.
  3. L'espansione non menzionata subisce word-splitting con | come delimitatore (a causa del valore globale di IFS), in modo che echo riceve due argomenti, a e b.
+0

Oooh. Destra. C'è un modo per produrre il comportamento che ho provato ad avere (ad esempio la formattazione con un delimitatore, ma non la suddivisione di parole su di esso)? Penso che 'printf' potrebbe essere l'opzione più semplice (ad esempio: http://stackoverflow.com/questions/12985178/) – Norswap

+2

Usa sempre espansioni citate. Questa è la risposta. La tua funzione 'log1' è semplicemente errata. 'log2' è la forma corretta. –

+2

Più precisamente, cita sempre '$ @'. (Vi sono casi in cui è possibile lasciare intenzionalmente '$ *' o altri parametri non quotati, ma '$ @' * esiste * da quotare, altrimenti è identico a '$ *'.) – chepner

5

Questo perché $IFS è impostato su |:

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

uscita:

a b 

man bash dice:

IFS L'interno Separatore campo che viene usato per la suddivisione dopo l'espansione ...

3

Nella sezione specifica POSIX su [Parametri speciali [(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02) troviamo.

@

espande nei parametri posizionali, a partire da uno. Quando l'espansione avviene tra virgolette doppie e laddove viene eseguita la divisione del campo (vedere Divisione del campo), ciascun parametro posizionale si espande come un campo separato, con la previsione che l'espansione del primo parametro deve ancora essere unita alla parte iniziale di la parola originale (supponendo che il parametro espanso fosse incorporato in una parola) e l'espansione dell'ultimo parametro deve ancora essere unita all'ultima parte della parola originale. Se non ci sono parametri posizionali, l'espansione di '@' genererà campi zero, anche quando '@' è a doppia virgola.

*

espande nei parametri posizionali, a partire da uno. Quando l'espansione si verifica all'interno di una stringa con doppia quotatura (vedere Virgolette doppie), si espande in un singolo campo con il valore di ciascun parametro separato dal primo carattere della variabile IFS o da a se IFS non è impostato.Se IFS è impostato su una stringa nulla, questo non equivale a disattivarlo; il suo primo carattere non esiste, quindi i valori dei parametri sono concatenati.

Quindi, a partire con le varianti citate (sono più semplici):

Vediamo che l'espansione * "espandere [s] per un singolo campo con il valore di ogni parametro separato dal primo carattere della Variabile IFS ". Questo è il motivo per cui ottieni a|b da echo "${X[*]" e log2 "${X[*]}".

Vediamo anche che l'espansione @ si espande in modo tale che "ogni parametro posizionale si espanda come un campo separato". Questo è il motivo per cui ottieni a b da echo "${X[@]}" e log2 "${X[@]}".

Hai visto quella nota sulla divisione del campo nel testo della specifica? "dove viene eseguita la divisione del campo (vedi Field Splitting)"? Questa è la chiave del mistero qui.

Fuori dalle virgolette il comportamento delle espansioni è lo stesso. La differenza è ciò che succede dopo. In particolare, campo/divisione delle parole.

Il modo più semplice per mostrare il problema è eseguire il codice con set -x attivato.

Che si ottiene questo:

+ 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 

La cosa da notare qui è che per il momento in log1 è chiamato in tutti, ma il caso finale | è già andato.

Il motivo per cui è già andato è perché senza citazioni dagli risultati dell'espansione variabili (in questo caso l'espansione *) sono campo/parola scissione. E poiché IFS viene utilizzato sia per combinare i campi che si espandono e quindi per suddividerli nuovamente, lo | viene ingoiato dalla divisione del campo.

E per finire la spiegazione (per il caso in realtà in questione), la ragione per cui questo non riesce per log1 anche con la versione citata dell'espansione nella chiamata (cioè log1 "${X[*]}" che si espande a log1 "a|b" correttamente) è perché log1stesso non utilizza un'espansione quotata di @ quindi l'espansione di @ nella funzione è a sua volta suddivisione in parole (come può essere visto da echo a b in quel caso log1 e tutti gli altri casi log1).