Se è necessario archiviare l'output e si desidera un array, mapfile
è semplice.
Innanzitutto, considera se è necessario memorizzare l'output del comando. In caso contrario, basta eseguire il comando.
Se decidi di voler leggere l'output di un comando come un array di righe, è vero che un modo per farlo è disabilitare il globbing, impostare la IFS
divisione su righe, utilizzare la sostituzione dei comandi all'interno della (
)
sintassi di creazione dell'array e reimpostare in IFS
seguito, che è più complesso di quanto sembri. La risposta di Terdon copre parte di tale approccio. Ma ti suggerisco di usare mapfile
, invece, il comando integrato della shell Bash per leggere il testo come una matrice di righe. Ecco un semplice caso in cui stai leggendo da un file:
mapfile < filename
Questo legge le righe in un array chiamato MAPFILE
. Senza il reindirizzamento dell'input , dovresti leggere dall'input standard della shell (in genere il tuo terminale) invece del file indicato da . Per leggere in un array diverso da quello predefinito , passane il nome. Ad esempio, questo si legge nell'array :< filename
filename
MAPFILE
lines
mapfile lines < filename
Un altro comportamento predefinito che potresti scegliere di modificare è che i caratteri di nuova riga alla fine delle righe siano lasciati al loro posto; appaiono come l'ultimo carattere in ciascun elemento dell'array (a meno che l'input non sia terminato senza un carattere di nuova riga, nel qual caso l'ultimo elemento non ne ha uno). Per tagliare queste nuove righe in modo che non appaiano nella matrice, passare l' -t
opzione a mapfile
. Ad esempio, questo legge l'array records
e non scrive i caratteri di nuova riga finali nei suoi elementi dell'array:
mapfile -t records < filename
È inoltre possibile utilizzare -t
senza passare un nome di array; vale a dire, funziona anche con un nome di matrice implicito di MAPFILE
.
La mapfile
shell incorporata supporta altre opzioni e può essere invocata come readarray
. Esegui help mapfile
(e help readarray
) per i dettagli.
Ma non vuoi leggere da un file, vuoi leggere dall'output di un comando. A tale scopo, utilizzare la sostituzione del processo . Questo comando legge le linee dal comando some-command
con arguments...
come i suoi argomenti della riga di comando e luoghi a loro a mapfile
's array predefinito MAPFILE
, con i loro personaggi di fine riga rimossi:
mapfile -t < <(some-command arguments...)
La sostituzione del processo sostituisce con un nome file effettivo da cui è possibile leggere l'output dell'esecuzione . Il file è una pipe denominata piuttosto che un normale file e, su Ubuntu, sarà chiamato come (a volte con un numero diverso da quello ), ma non è necessario preoccuparsi dei dettagli, perché la shell si prende cura di esso tutto dietro le quinte.<(some-command arguments...)
some-command arguments...
/dev/fd/63
63
Potresti pensare di poter rinunciare alla sostituzione del processo usando invece, ma ciò non funzionerà perché, quando hai una pipeline di più comandi separati da , Bash Run esegue tutti i comandi in subshells . Così sia e correre nei loro ambienti , inizializzati da separare, ma dall'ambiente del guscio in cui si esegue la pipeline. Nel subshell in cui corre, la serie fa vengono popolate, ma poi tale matrice viene scartato quando finisce il comando. non viene mai creato o modificato per il chiamante.some-command arguments... | mapfile -t
|
some-command arguments...
mapfile -t
mapfile -t
MAPFILE
MAPFILE
Ecco come appare l'esempio nella risposta di terdon , nel suo complesso, se si utilizza mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
Questo è tutto. Non è necessario verificare se è IFS
stato impostato , tenere traccia di se non è stato impostato e con quale valore, impostarlo su una nuova riga, quindi reimpostare o reinserirlo in un secondo momento. Non è necessario disabilitare globbing (per esempio, con set -f
) - che è realmente necessario se si è di utilizzare quel metodo serio, dal momento che i nomi dei file possono contenere *
, ?
e [
--quindi riattivarla ( set +f
) in seguito.
Puoi anche sostituire quel particolare ciclo interamente con un singolo printf
comando, anche se questo non è in realtà un vantaggio mapfile
, in quanto puoi farlo se usi mapfile
o un altro metodo per popolare l'array. Ecco una versione più breve:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
È importante tenere presente che, come indicato da Terdon , operare riga per riga non è sempre appropriato e non funzionerà correttamente con nomi di file che contengono nuove righe. Consiglio di non nominare i file in questo modo, ma può succedere, anche per caso.
Non esiste davvero una soluzione unica per tutti.
Hai chiesto "un comando generico per tutti gli scenari" e l'approccio all'utilizzo in mapfile
qualche modo di tali obiettivi, ma ti esorto a riconsiderare le tue esigenze.
Il compito mostrato sopra è meglio realizzato con un solo find
comando:
find . -type d -printf 'DIR: %p\n'
È inoltre possibile utilizzare un comando esterno come sed
aggiungere DIR:
all'inizio di ogni riga. Questo è probabilmente un po 'brutto e, a differenza di quel comando find, aggiungerà "prefissi" extra all'interno di nomi di file che contengono nuove righe, ma funziona indipendentemente dal suo input, quindi soddisfa i tuoi requisiti ed è comunque preferibile leggere l'output in un variabile o matrice :
find . -type d | sed 's/^/DIR: /'
Se è necessario elencare ed eseguire anche un'azione su ciascuna directory trovata, come eseguire some-command
e passare il percorso della directory come argomento, è find
possibile farlo anche:
find . -type d -print -exec some-command {} \;
Come altro esempio, torniamo all'attività generale di aggiungere un prefisso a ciascuna riga. Supponiamo che io voglia vedere l'output di help mapfile
ma numerare le righe. In realtà non userei mapfile
per questo, né qualsiasi altro metodo che lo legga in una variabile shell o array di shell. Supponendo help mapfile | cat -n
che non dia la formattazione che desidero, posso usare awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
Leggere tutto l'output di un comando in una singola variabile o matrice è talvolta utile e appropriato, ma presenta importanti svantaggi. Non solo devi affrontare l'ulteriore complessità dell'uso della shell quando un comando esistente o una combinazione di comandi esistenti può già fare ciò che ti serve o meglio, ma l'intero output del comando deve essere archiviato in memoria. A volte potresti sapere che non è un problema, ma a volte potresti elaborare un file di grandi dimensioni.
Un'alternativa che viene comunemente tentata - e talvolta eseguita correttamente - è leggere l'input riga per riga con read -r
in un ciclo. Se non è necessario memorizzare le righe precedenti mentre si opera su righe successive e è necessario utilizzare input lunghi, potrebbe essere meglio di mapfile
. Ma anche questo dovrebbe essere evitato nei casi in cui puoi semplicemente inviarlo a un comando che può fare il lavoro, che è nella maggior parte dei casi .