
Estou tentando procurar arquivos usando find
e colocar esses arquivos em um array Bash para que eu possa fazer outras operações neles (por exemplo, ls
ou grep
neles). Mas não consigo entender por que readarray
a saída não está sendo lida find
quando ela é canalizada para ela.
Digamos que eu tenha dois arquivos no diretório atual file1.txt
e file2.txt
. Então a find
saída é a seguinte:
$ find . -name "file*"
./file1.txt
./file2.txt
Então, quero canalizar isso para um array cujos dois elementos são as strings "./file1.txt"
e "./file2.txt"
(sem aspas, obviamente).
Eu tentei isso, entre algumas outras coisas:
$ declare -a FILES
$ find . -name "file*" | readarray FILES
$ echo "${FILES[@]}"; echo "${#FILES[@]}"
0
Como você pode ver na echo
saída, meu array está vazio.
Então, o que exatamente estou fazendo de errado aqui? Por que não está readarray
lendo find
a saída de como entrada padrão e colocando essas strings no array?
Responder1
Ao usar um pipeline, o bash executa os comandos em subshells¹. Portanto, o array é preenchido, mas em um subshell, portanto o shell pai não tem acesso a ele. Você provavelmente também deseja a -t
opção de não armazenar os delimitadores de linha nos membros da matriz, pois eles não fazem parte dos nomes dos arquivos.
Use substituição de processo:
readarray -t FILES < <(find .)
Observe que isso não funciona para arquivos com novas linhas em seus caminhos. A menos que você possa garantir que não será o caso, convém usar registros delimitados por NUL em vez de registros delimitados por nova linha:
readarray -td '' < <(find . -print0)
(a -d
opção foi adicionada no bash 4.4)
¹ exceto para o último componente de pipe ao usar a lastpipe
opção, mas isso é apenas para invocações não interativas de bash
.
Responder2
A solução correta é:
unset a; declare -a a
while IFS= read -r -u3 -d $'\0' file; do
a+=( "$file" ) # or however you want to process each file
done 3< <(find /tmp -type f -print0)
Isso é semelhante ao queBashFAQ 020 de Gregexplica detalhadamente eesta resposta cobre.
Não tem problemas com arquivos com nomes estranhos (que não contêm NUL no nome), com espaços ou novas linhas. E o resultado é definido em uma matriz, o que o torna útil para processamento posterior.
Responder3
readarray
também pode ler de stdin
readarray FILES <<< "$(find . -name "file*")"; echo "${#FILES[@]}"
Responder4
Com shopt -s lastpipe
a solução mostrada na pergunta original funciona, pois a parte após a última |
é executada no mesmo processo da maior parte do script:
shopt -s lastpipe
# This is just the code form the original question.
# Of course it could be done with -print0, etc.
declare -a FILES
find . -name "name*" | readarray -t FILES
echo find and readarray exit status: "${PIPESTATUS[@]}"
echo "${FILES[@]}"
echo "${#FILES[@]}"
No entanto, isso só é prático se lastpipe
puder ser transformado em um padrão para todo o projeto (talvez você já tenha uma configuração comum para set -u
, etc.). Saiba também que lastpipe
funciona apenas para shells não interativos.
A outra substituição típica de pipe < <(...)
(substituição de processo) tem o problema de ser muito difícil verificar se o processo filho foi bem-sucedido.