As outras respostas vai quebrar se a saída de comando contém espaços (que é bastante frequente) ou glob personagens como *
, ?
, [...]
.
Para obter a saída de um comando em uma matriz, com uma linha por elemento, existem essencialmente três maneiras:
Com Bash≥4 uso mapfile
- é o mais eficiente:
mapfile -t my_array < <( my_command )
Caso contrário, um loop lendo a saída (mais lento, mas seguro):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
Conforme sugerido por Charles Duffy nos comentários (obrigado!), O seguinte pode funcionar melhor do que o método de loop no número 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
Certifique-se de usar exatamente este formulário, ou seja, certifique-se de ter o seguinte:
IFS=$'\n'
na mesma linha da read
instrução: isso só definirá a variável de ambiente apenas IFS
para a read
instrução. Portanto, isso não afetará o resto do seu script. O objetivo desta variável é informar read
para interromper o fluxo no caractere EOL \n
.
-r
: Isso é importante. Diz read
para não interpretar as barras invertidas como sequências de escape.
-d ''
: observe o espaço entre a -d
opção e seu argumento ''
. Se você não deixar um espaço aqui, o ''
nunca será visto, pois desaparecerá na etapa de remoção da citação quando o Bash analisar a instrução. Isso indica read
para parar de ler no byte nulo. Algumas pessoas escrevem como -d $'\0'
, mas não é realmente necessário. -d ''
é melhor.
-a my_array
diz read
para preencher a matriz my_array
enquanto lê o fluxo.
- Você deve usar a
printf '\0'
instrução depois my_command
, para que read
retorne 0
; na verdade, não é grande coisa se você não fizer isso (você apenas obterá um código de retorno 1
, o que está certo se você não usar set -e
- o que você não deveria usar de qualquer maneira), mas apenas tenha isso em mente. É mais limpo e semanticamente correto. Observe que isso é diferente de printf ''
, que não produz nada. printf '\0'
imprime um byte nulo, necessário read
para parar de ler ali (lembra da -d ''
opção?).
Se você puder, ou seja, se você tiver certeza de que seu código será executado em Bash≥4, use o primeiro método. E você pode ver que é mais curto também.
Se você quiser usar read
, o loop (método 2) pode ter uma vantagem sobre o método 3 se você quiser fazer algum processamento à medida que as linhas são lidas: você tem acesso direto a ele (por meio da $line
variável no exemplo que dei), e você também tem acesso às linhas já lidas (por meio da matriz ${my_array[@]}
no exemplo que dei).
Observe que mapfile
fornece uma maneira de ter um retorno de chamada avaliado em cada linha lida e, na verdade, você pode até mesmo dizer para ele apenas chamar esse retorno de chamada a cada N linhas lidas; dê uma olhada nas help mapfile
opções -C
e -c
nele contidas. (Minha opinião sobre isso é que é um pouco desajeitado, mas pode ser usado às vezes se você tiver apenas coisas simples para fazer - eu realmente não entendo por que isso foi implementado em primeiro lugar!).
Agora vou dizer por que o seguinte método:
my_array=( $( my_command) )
está quebrado quando há espaços:
$ # 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!
Então, algumas pessoas irão recomendar o uso IFS=$'\n'
para consertá-lo:
$ 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!
Mas agora vamos usar outro comando, com 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?
Isso é porque eu tenho um arquivo chamado t
no diretório atual ... e esse nome de arquivo é correspondido pelo glob [three four]
... neste ponto, algumas pessoas recomendariam usar set -f
para desabilitar o globbing: mas olhe só: você tem que alterar IFS
e usar set -f
para corrigir um técnica quebrada (e você nem mesmo está consertando)! ao fazer isso, estamos realmente lutando contra o shell, não trabalhando com o 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]")'
aqui estamos trabalhando com o shell!