Se você precisar armazenar a saída e desejar uma matriz, mapfileserá mais fácil.
Primeiro, considere se você precisa armazenar a saída do seu comando. Caso contrário, 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 o globbing, definir IFSa divisão em linhas, usar a substituição de comando dentro da ( )sintaxe de criação da matriz e redefinir IFSdepois, o que é mais complexo do que parece. A resposta de Terdon cobre parte dessa abordagem. Mas eu sugiro que você use mapfile, o shell Bash é built-in comando para leitura de texto como um conjunto de linhas, em vez disso. Aqui está um caso simples em que você está lendo de um arquivo:
mapfile < filename
Isso lê linhas em uma matriz chamada MAPFILE. Sem o redirecionamento de entrada , você estaria lendo a entrada padrão do shell (normalmente o seu terminal) em vez do arquivo nomeado por . Para ler em uma matriz que não seja o padrão , passe seu nome. Por exemplo, isso lê na matriz :< filenamefilenameMAPFILElines
mapfile lines < filename
Outro comportamento padrão que você pode optar por alterar é que os caracteres de 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 termine sem um caractere de nova linha; nesse caso, o último elemento não possui um). Para incluir essas novas linhas de forma que elas não apareçam na matriz, passe a -topção para mapfile. Por exemplo, isso é lido na matriz recordse não grava caracteres de nova linha à direita em seus elementos da matriz:
mapfile -t records < filename
Você também pode usar -tsem passar um nome de matriz; isto é, ele também funciona com um nome implícito de matriz MAPFILE.
O mapfileshell embutido suporta outras opções e, alternativamente, pode ser chamado como readarray. Execute help mapfile(e help readarray) para obter detalhes.
Mas você não deseja ler de um arquivo, mas da saída de um comando. Para conseguir isso, use a substituição do processo . Este comando lê as linhas do comando some-commandcom arguments...seus argumentos de linha de comando e as coloca na mapfilematriz padrão MAPFILE, com seus caracteres finais de nova linha removidos:
mapfile -t < <(some-command arguments...)
A substituição do processo substitui por um nome de arquivo real a partir do qual a saída da execução pode ser lida. O arquivo é um pipe nomeado em vez de um arquivo normal e, no Ubuntu, será nomeado como (às vezes com outro número ), mas você não precisa se preocupar com os detalhes, porque o shell cuida disso tudo nos bastidores.<(some-command arguments...)some-command arguments.../dev/fd/6363
Você pode pensar que poderia renunciar à substituição do processo usando , mas isso não funcionará porque, quando você tem um pipeline de vários comandos separados por , o Bash executa todos os comandos em subshells . Assim, tanto e executar em seus próprios ambientes , inicializadas a partir, mas separado do ambiente do shell em que você executar o pipeline. No subshell onde é executado, a matriz é preenchida, mas essa matriz é descartada quando o comando termina. nunca é criado ou modificado para o chamador.some-command arguments... | mapfile -t|some-command arguments...mapfile -tmapfile -tMAPFILEMAPFILE
Aqui está a aparência do exemplo na resposta de terdon , na íntegra, se você usar mapfile:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
É isso aí. Você não precisa verificar se IFSfoi definido , acompanhar se não foi definido e com qual valor, defini-lo como uma nova linha e redefini-lo ou reconfigurá-lo posteriormente. Você não precisa desativar o globbing (por exemplo, com set -f) - o que é realmente necessário para usar esse método com seriedade, pois os nomes de arquivos podem conter *e ?- e [depois reativá-lo ( set +f) posteriormente.
Você também pode substituir esse loop específico inteiramente por um único printfcomando - embora isso não seja realmente um benefício mapfile, pois você pode fazer isso usando mapfileou 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 menciona Terdon , operar linha por linha nem sempre é apropriado e não funcionará corretamente com nomes de arquivos que contêm novas linhas. Eu recomendo nomear arquivos dessa maneira, mas isso 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 mapfileum pouco aproxima esse objetivo, mas peço que você reconsidere seus requisitos.
A tarefa mostrada acima é melhor alcançada com apenas um único findcomando:
find . -type d -printf 'DIR: %p\n'
Você também pode usar um comando externo como sedadicionar DIR: ao início de cada linha. É indiscutivelmente um pouco feio e, ao contrário do comando find, ele adicionará "prefixos" extras nos nomes de arquivos que contêm novas linhas, mas funciona independentemente da entrada, portanto atende aos seus requisitos e ainda é preferível ler a saída em um arquivo. 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-commande passar o caminho do diretório como argumento, findfaça isso também:
find . -type d -print -exec some-command {} \;
Como outro exemplo, voltemos à tarefa geral de adicionar um prefixo a cada linha. Suponha que eu queira ver a saída de, help mapfilemas numere as linhas. Na verdade, eu não usaria mapfileisso nem qualquer outro método que o leia em uma variável de shell ou matriz de shell. Supondo help mapfile | cat -nque não dê a formatação que eu quero, eu posso usar awk:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
Ler toda a saída de um comando em uma única variável ou matriz às vezes é útil e apropriado, mas possui grandes desvantagens. Você não precisa apenas lidar com a complexidade adicional de usar seu shell quando um comando ou combinação de comandos existentes já pode fazer o que você precisa melhor ou melhor, mas toda a saída do comando deve 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 comumente tentada - e às vezes feita corretamente - é ler a entrada linha por linha read -rem um loop. Se você não precisar armazenar linhas anteriores enquanto estiver operando em linhas posteriores e precisar usar entradas longas, 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 .