Come raccogliere correttamente una matrice di linee in zsh


42

Ho pensato che il seguente avrebbe raggruppato l'output di my_commandin una matrice di linee:

IFS='\n' array_of_lines=$(my_command);

in modo che $array_of_lines[1]si riferisca alla prima riga nell'output di my_command, $array_of_lines[2]alla seconda e così via.

Tuttavia, il comando sopra non sembra funzionare bene. Sembra anche dividere l'output di my_commandattorno al personaggio n, come ho verificato print -l $array_of_lines, che credo stampa elementi di un array riga per riga. Ho anche controllato questo con:

echo $array_of_lines[1]
echo $array_of_lines[2]
...

In un secondo tentativo, ho pensato che l'aggiunta evalpotesse aiutare:

IFS='\n' array_of_lines=$(eval my_command);

ma ho ottenuto lo stesso esatto risultato senza di esso.

Infine, seguendo la risposta sugli elementi della lista con spazi in zsh , ho anche provato a usare i flag di espansione dei parametri invece di IFSdire a zsh come dividere l'input e raccogliere gli elementi in un array, cioè:

array_of_lines=("${(@f)$(my_command)}");

Ma ho ancora ottenuto lo stesso risultato (si sta verificando una divisione n)

Con questo, ho le seguenti domande:

Q1. Quali sono i modi "corretti" per raccogliere l'output di un comando in una matrice di righe?

Q2. Come posso specificare IFSdi dividere solo su newline?

Q3. Se uso i flag di espansione dei parametri come nel mio terzo tentativo sopra (ovvero usando @f) per specificare la divisione, zsh ignora il valore di IFS? Perché non ha funzionato sopra?

Risposte:


71

TL, DR:

array_of_lines=("${(@f)$(my_command)}")

Primo errore (→ Q2): IFS='\n'imposta IFSi due caratteri \e n. Per impostare IFSuna nuova riga, utilizzare IFS=$'\n'.

Secondo errore: per impostare una variabile per un valore di array, è necessario parentesi intorno agli elementi: array_of_lines=(foo bar).

Funzionerebbe, tranne per il fatto che elimina le righe vuote, poiché gli spazi bianchi consecutivi contano come un singolo separatore:

IFS=$'\n' array_of_lines=($(my_command))

Puoi conservare le righe vuote tranne alla fine raddoppiando il carattere degli spazi bianchi in IFS:

IFS=$'\n\n' array_of_lines=($(my_command))

Per continuare a trascinare anche le righe vuote, dovresti aggiungere qualcosa all'output del comando, perché ciò accade nella sostituzione del comando stesso, non dall'analisi.

IFS=$'\n\n' array_of_lines=($(my_command; echo .)); unset 'array_of_lines[-1]'

(supponendo che l'output di my_commandnon finisca in una riga non delimitata; si noti inoltre che si perde lo stato di uscita di my_command)

Tieni presente che tutti i frammenti sopra lasciati IFScon il suo valore non predefinito, quindi potrebbero incasinare il codice successivo. Per mantenere l'impostazione di IFSlocal, metti il ​​tutto in una funzione in cui dichiari IFSlocal (facendo attenzione anche a preservare lo stato di uscita del comando):

collect_lines() {
  local IFS=$'\n\n' ret
  array_of_lines=($("$@"; ret=$?; echo .; exit $ret))
  ret=$?
  unset 'array_of_lines[-1]'
  return $ret
}
collect_lines my_command

Ma consiglio di non scherzare IFS; usa invece il fflag di espansione per dividere su newline (→ Q1):

array_of_lines=("${(@f)$(my_command)}")

O per conservare le righe vuote finali:

array_of_lines=("${(@f)$(my_command; echo .)}")
unset 'array_of_lines[-1]'

Il valore di IFSnon importa lì. Ho il sospetto che tu abbia usato un comando che si divide IFSper stampare $array_of_linesnei tuoi test (→ Q3).


7
Questo è così complicato! "${(@f)...}"è lo stesso di ${(f)"..."}, ma in modo diverso. (@)tra virgolette doppie significa "cedere una parola per elemento di array" e (f)significa "diviso in un array per newline". PS: Link ai documenti
volo di pecore il

3
@flyingsheep, no ${(f)"..."}salta le righe vuote, le "${(@f)...}"conserva. Questa è la stessa distinzione tra $argve "$argv[@]". Quella "$@"cosa per preservare tutti gli elementi di un array proviene dalla shell Bourne alla fine degli anni '70.
Stéphane Chazelas,

4

Due problemi: in primo luogo, le virgolette apparentemente doppie non interpretano anche le escape di backslash (mi dispiace per quello :). Usa le $'...'virgolette. E secondo man zshparam, per raccogliere parole in un array è necessario racchiuderle tra parentesi. Quindi funziona:

% touch 'a b' c d 'e f'
% IFS=$'\n' arr=($(ls)); print -l $arr
a b
c
d
e f
% print $arr[1]
a b

Non posso rispondere al tuo Q3. Spero che non dovrò mai conoscere cose così esoteriche :).


-2

Puoi anche usare tr per sostituire newline con spazio:

lines=($(mycommand | tr '\n' ' '))
select line in ("${lines[@]}"); do
  echo "$line"
  break
done

3
cosa succede se le linee contengono spazi?
don_crissti,

2
Non ha senso. Sia SPC che NL sono nel valore predefinito di $IFS. Tradurre l'una nell'altra non fa differenza.
Stéphane Chazelas,

Le mie modifiche erano ragionevoli? Non riuscivo a farlo funzionare com'era,
John P

(scaduto) Ammetto di averlo modificato senza capire veramente quale fosse l'intento, ma penso che la traduzione sia un buon inizio per la separazione basata sulla manipolazione delle stringhe. Non vorrei tradurre in spazi, e dividerli su di essi, a meno che il comportamento previsto non fosse più simile echo, in cui gli input sono tutti più o meno un mucchio di parole separate da chi se ne frega.
John P,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.