Lettura dell'output di un comando in un array in Bash


111

Ho bisogno di leggere l'output di un comando nel mio script in un array. Il comando è, ad esempio:

ps aux | grep | grep | x 

e fornisce l'output riga per riga in questo modo:

10
20
30

Devo leggere i valori dall'output del comando in un array, quindi eseguirò del lavoro se la dimensione dell'array è inferiore a tre.


5
Ehi @barp, RISPONDI ALLE TUE DOMANDE, per evitare che il tuo tipo sia uno scarico per l'intera comunità.
James

9
@ James, il problema non è il fatto che non sta rispondendo alla sua domanda ... questo è un sito di domande / risposte. Semplicemente non li ha contrassegnati come risposte. Dovrebbe contrassegnarli. Suggerimento. @ barp
DDPWNAGE

4
Per favore @barp, contrassegna la domanda come risposta.
smonff

Correlati: scorrere il contenuto di un file in Bash poiché la lettura dell'output di un comando tramite la sostituzione del processo è simile alla lettura da un file.
codeforester

Risposte:


161

Le altre risposte si rompa se output del comando contiene spazi (che è piuttosto frequente) o glob caratteri come *, ?, [...].

Per ottenere l'output di un comando in un array, con una riga per elemento, ci sono essenzialmente 3 modi:

  1. Con Bash≥4 mapfile, è il più efficiente:

    mapfile -t my_array < <( my_command )
  2. Altrimenti, un ciclo che legge l'output (più lento, ma sicuro):

    my_array=()
    while IFS= read -r line; do
        my_array+=( "$line" )
    done < <( my_command )
  3. Come suggerito da Charles Duffy nei commenti (grazie!), Quanto segue potrebbe funzionare meglio del metodo loop al numero 2:

    IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )

    Assicurati di utilizzare esattamente questo modulo, ovvero assicurati di avere quanto segue:

    • IFS=$'\n' sulla stessa riga readdell'istruzione: questo imposterà solo la variabile d'ambiente solo IFS per l' readistruzione. Quindi non influenzerà affatto il resto del tuo script. Lo scopo di questa variabile è indicare readdi interrompere il flusso in corrispondenza del carattere EOL \n.
    • -r: questo è importante. Indica read di non interpretare i backslash come sequenze di escape.
    • -d '': notare lo spazio tra l' -dopzione e il suo argomento ''. Se non lasci uno spazio qui, ''non verrà mai visualizzato, poiché scomparirà nella fase di rimozione delle virgolette quando Bash analizzerà l'istruzione. Questo dice readdi interrompere la lettura al byte nullo. Alcune persone lo scrivono come -d $'\0', ma non è realmente necessario. -d ''è meglio.
    • -a my_arraydice readdi popolare l'array my_arraydurante la lettura del flusso.
    • È necessario utilizzare l' printf '\0'istruzione dopo my_command , in modo che readritorni 0; in realtà non è un grosso problema se non lo fai (riceverai solo un codice di ritorno 1, che va bene se non lo usi set -e, cosa che non dovresti comunque), ma tienilo a mente. È più pulito e semanticamente corretto. Nota che questo è diverso da printf '', che non restituisce nulla. printf '\0'stampa un byte nullo, necessario readper smettere felicemente di leggere lì (ricordi l' -d ''opzione?).

Se puoi, cioè se sei sicuro che il tuo codice verrà eseguito su Bash≥4, usa il primo metodo. E puoi vedere che è anche più corto.

Se si desidera utilizzare read, il ciclo (metodo 2) potrebbe avere un vantaggio rispetto al metodo 3 se si desidera eseguire un'elaborazione mentre le righe vengono lette: si ha accesso diretto ad esso (tramite la $linevariabile nell'esempio che ho fornito) e hai anche accesso alle righe già lette (tramite l'array ${my_array[@]}nell'esempio che ho dato).

Nota che mapfilefornisce un modo per avere un callback eval'd su ogni riga letta, e infatti puoi anche dirgli di chiamare solo questo callback ogni N righe lette; dare un'occhiata alle help mapfileopzioni -Ce al contenuto -c. (La mia opinione su questo è che è un po 'goffo, ma a volte può essere usato se hai solo cose semplici da fare - non capisco davvero perché sia ​​stato implementato in primo luogo!).


Ora ti dirò perché il seguente metodo:

my_array=( $( my_command) )

è rotto quando ci sono spazi:

$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!

Quindi alcune persone consiglieranno di utilizzare IFS=$'\n'per risolverlo:

$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!

Ma ora usiamo un altro comando, con globs :

$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?

Questo perché ho un file chiamato tnella directory corrente ... e questo nome di file è abbinato al glob [three four] ... a questo punto alcune persone consiglierebbero di utilizzare set -fper disabilitare il globbing: ma guarda: devi cambiare IFSe usare set -fper poter aggiustare un tecnica rotta (e non la stai nemmeno aggiustando davvero)! quando lo facciamo stiamo davvero combattendo contro la shell, non lavorando con la shell .

$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'

qui stiamo lavorando con la shell!


4
È fantastico, non ne avevo mai sentito parlare mapfileprima, è esattamente quello che mi mancava da anni. Immagino che le versioni recenti di bashabbiano così tante nuove funzionalità interessanti, dovrei solo passare qualche giorno a leggere i documenti e scrivere un bel cheatsheet.
Gene Pavlovsky

6
A proposito, per usare questa sintassi < <(command)negli script della shell, la riga shebang dovrebbe essere #!/bin/bash- se eseguita come #!/bin/sh, bash uscirà con un errore di sintassi.
Gene Pavlovsky

1
Espandendo la nota utile di @ GenePavlovsky, lo script deve essere eseguito anche con il comando bash bash my_script.she non con il comando shsh my_script.sh
Vito

2
@Vito: in effetti, questa risposta è solo per Bash, ma questo non dovrebbe essere un problema, poiché le shell POSIX strettamente conformi non implementano nemmeno gli array ( she dashnon conoscono affatto gli array, tranne, ovviamente, per l' $@array dei parametri posizionali ).
gniourf_gniourf

3
Come un'altra alternativa che non richiede bash 4.0, considera IFS=$'\n' read -r -d '' -a my_array < <(my_command && printf '\0'): entrambi funzionano correttamente in bash 3.x e passa anche attraverso uno stato di uscita non riuscito da my_commanda read.
Charles Duffy

86

Puoi usare

my_array=( $(<command>) )

per memorizzare l'output del comando <command>nell'array my_array.

Puoi accedere alla lunghezza di quell'array usando

my_array_length=${#my_array[@]}

Ora la lunghezza è memorizzata in my_array_length.


19
Cosa succede se l'output di $ (comando) ha spazi e più righe con spazi? Ho aggiunto "$ (comando)" e inserisce tutto l'output di tutte le righe nel primo elemento [0] dell'array.
ikwyl6

3
@ ikwyl6 una soluzione alternativa consiste nell'assegnare l'output del comando a una variabile e quindi creare un array con esso o aggiungerlo a un array. VAR="$(<command>)"e poi my_array=("$VAR")omy_array+=("$VAR")
Vito

10

Immagina di mettere i file ei nomi delle directory (sotto la cartella corrente) in un array e contare i suoi elementi. Lo script sarebbe come;

my_array=( `ls` )
my_array_length=${#my_array[@]}
echo $my_array_length

Oppure puoi iterare su questo array aggiungendo il seguente script:

for element in "${my_array[@]}"
do
   echo "${element}"
done

Si noti che questo è il concetto di base e l'input è considerato prima disinfettato, ad esempio rimozione di caratteri extra, gestione di stringhe vuote e così via (che è fuori dall'argomento di questo thread).


3
Pessima idea per i motivi menzionati nella risposta sopra
Hubert Grzeskowiak
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.