Se você precisar armazenar a saída e desejar uma matriz, mapfile
facilitará o processo.
Primeiro, considere se você precisa para armazenar a saída do seu comando. Se você não fizer isso, basta executar o comando.
Se você decidir ler a saída de um comando como uma matriz de linhas, é verdade que uma maneira de fazê-lo é desativar globbing, definir IFS
para dividir em linhas, usar substituição de comando dentro de (
)
sintaxe de criação da matriz e redefinir IFS
depois, que é mais complexo do que parece. a resposta de terdon cobre algumas dessas abordagens. Mas eu sugiro que você use mapfile
, o embutido do shell Bash para ler texto como uma matriz de linhas, em vez disso. Aqui está um caso simples em que você está lendo de um arquivo:
mapfile < filename
Isso lê as linhas em uma matriz chamada MAPFILE
. Sem o redirecionamento de entrada < filename
, você estaria lendo do shell < a href="https://en.wikipedia.org/wiki/Standard_streams#Standard_input_.28stdin.29"> entrada padrão (normalmente seu terminal) em vez do arquivo chamado filename
. Para ler em uma matriz diferente do padrão MAPFILE
, passe seu nome. Por exemplo, isso lê na matriz lines
:
mapfile lines < filename
Outro comportamento padrão que você pode optar por mudar é que os caracteres da nova linha no final das linhas são deixados no lugar; eles aparecem como o último caractere em cada elemento da matriz (a menos que a entrada tenha terminado sem um caractere de nova linha, caso em que o último elemento não possui um). Para chupar essas novas linhas para que elas não apareçam na matriz, passe a opção -t
para mapfile
. Por exemplo, isso lê a matriz records
e não grava caracteres de nova linha à direita em seus elementos de matriz:
mapfile -t records < filename
Você também pode usar -t
sem passar um nome de matriz; isto é, ele também funciona com um nome de array implícito de MAPFILE
.
O shell mapfile
incorporado suporta outras opções e, como alternativa, pode ser chamado como readarray
. Execute help mapfile
(e help readarray
) para detalhes.
Mas você não quer ler de um arquivo, você quer ler a saída de um comando. Para conseguir isso, use substituição de processos . Este comando lê as linhas do comando some-command
com arguments...
como seus argumentos de linha de comando e coloca nelas na matriz padrão mapfile
MAPFILE
, com seus caracteres de nova linha de terminação removidos:
mapfile -t < <(some-command arguments...)
A substituição do processo substitui <(some-command arguments...)
por um nome de arquivo real a partir do qual a saída de execução some-command arguments...
pode ser lida. O arquivo é um canal nomeado em vez de um arquivo regular e, no Ubuntu, será nomeado como /dev/fd/63
(às vezes com algum outro número que não seja 63
), mas você não precisa se preocupar com os detalhes, porque o shell cuida de tudo nos bastidores.
Você pode pensar que poderia renunciar à substituição do processo usando some-command arguments... | mapfile -t
, mas isso não funcionará porque, quando você tiver um pipeline de vários comandos separados por |
, Bash executado executa todos os comandos em subshells . Assim, tanto some-command arguments...
como mapfile -t
são executados em seus próprios ambientes , inicializado, mas separado do ambiente do shell no qual você executa o pipeline. No subshell onde mapfile -t
é executado, o MAPFILE
array é preenchido, mas, em seguida, esse array é descartado quando o comando é finalizado. MAPFILE
nunca é criado ou modificado para o chamador.
Veja como o exemplo na resposta de terdon se parece, na sua totalidade, se você usar mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
É isso. Você não precisa verifique se IFS
está definido , seu valor, defina-o como uma nova linha e redefina ou desmarque-o mais tarde. Você não precisa desativar globbing (por exemplo, com set -f
) - que é realmente necessário se você quiser usar esse método seriamente, pois os nomes de arquivos podem conter *
, ?
e [
- depois reative-o ( set +f
) depois.
Você também pode substituir esse loop inteiro inteiramente por um único comando printf
- embora isso não seja realmente um benefício de mapfile
, como você pode usar se usar mapfile
ou outro método para preencher a matriz . Aqui está uma versão mais curta:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
É importante ter em mente que, como terdon menciona , a operação linha a linha nem sempre é apropriada, e não funcionará corretamente com nomes de arquivos que contenham novas linhas. Eu recomendo contra nomear arquivos dessa maneira, mas pode acontecer, inclusive por acidente.
Não existe realmente uma solução única para todos.
Você pediu "um comando genérico para todos os cenários" e a abordagem de usar mapfile
aproxima-se desse objetivo, mas peço que reconsidere seus requisitos.
A tarefa mostrada acima é melhor alcançada com apenas um único comando find
:
find . -type d -printf 'DIR: %p\n'
Você também pode usar um comando externo como sed
para adicionar DIR:
ao início de cada linha. Isto é discutivelmente um pouco feio, e ao contrário do comando find ele adicionará "prefixos" extras dentro de nomes de arquivos que contêm novas linhas, mas funciona independentemente de sua entrada para que ele atenda seus requisitos e ainda seja preferível ler o saída em uma variável ou matriz :
find . -type d | sed 's/^/DIR: /'
Se você precisar listar e também executar uma ação em cada diretório encontrado, como executar some-command
e passar o caminho do diretório como um argumento, find
permite que você faça isso também:
find . -type d -print -exec some-command {} \;
Como outro exemplo, vamos retornar à tarefa geral de adicionar um prefixo a cada linha. Suponha que eu queira ver a saída de help mapfile
mas número de linhas hte. Eu realmente não usaria mapfile
para isso, nem qualquer outro método que o lê em uma variável de shell ou matriz de shell. Supondo que help mapfile | cat -n
não forneça a formatação que eu quero, posso usar awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, %pre% }'
Ler toda a saída de um comando em uma única variável ou matriz às vezes é útil e apropriado, mas tem grandes desvantagens. Você não precisa apenas lidar com a complexidade adicional de usar sua shell quando um comando existente ou combinação de comandos existentes já pode fazer o que você precisa tão bem ou melhor, mas toda a saída do comando tem que ser armazenada na memória. Às vezes você pode saber que isso não é um problema, mas às vezes você pode estar processando um arquivo grande.
Uma alternativa que é comumente tentada - e às vezes feita corretamente - é ler a entrada linha por linha com read -r
em um loop. Se você não precisa armazenar linhas anteriores enquanto opera em linhas posteriores, e você tem que usar uma entrada longa, então pode ser melhor que mapfile
. Mas também, deve ser evitado nos casos em que você pode simplesmente canalizá-lo para um comando que pode fazer o trabalho, que é a maioria dos casos .