As respostas aceitas / com alta votação são ótimas, mas faltam alguns detalhes minuciosos. Esta postagem aborda os casos de como lidar melhor quando a expansão do nome do caminho do shell (glob) falha, quando os nomes de arquivos contêm linhas de linha / símbolos de traço incorporados e movendo a direção de saída do comando para fora do loop for ao gravar os resultados em um Arquivo.
Ao executar a expansão glob do shell usando *
, é possível que a expansão falhe se não houver arquivos presentes no diretório e uma cadeia glob não expandida for passada para o comando a ser executado, o que pode ter resultados indesejáveis. O bash
shell fornece uma opção de shell estendida para esse uso nullglob
. Portanto, o loop se torna basicamente o seguinte dentro do diretório que contém seus arquivos
shopt -s nullglob
for file in ./*; do
cmdToRun [option] -- "$file"
done
Isso permite que você saia com segurança do loop for quando a expressão ./*
não retornar nenhum arquivo (se o diretório estiver vazio)
ou de maneira compatível com POSIX ( nullglob
é bash
específico)
for file in ./*; do
[ -f "$file" ] || continue
cmdToRun [option] -- "$file"
done
Isso permite que você entre no loop quando a expressão falhar pela primeira vez e a condição [ -f "$file" ]
verificar se a cadeia não expandida ./*
é um nome de arquivo válido nesse diretório, o que não seria. Portanto, nessa falha de condição, usando continue
retomamos o for
loop que não será executado posteriormente.
Observe também o uso de --
pouco antes de passar o argumento do nome do arquivo. Isso é necessário porque, como observado anteriormente, os nomes de arquivos do shell podem conter traços em qualquer lugar do nome do arquivo. Alguns dos comandos do shell interpretam isso e os tratam como uma opção de comando quando o nome não é citado corretamente e executa o comando pensando se o sinalizador for fornecido.
Os --
sinais indicam as opções de final de linha de comando nesse caso, o que significa que o comando não deve analisar nenhuma sequência além deste ponto como sinalizadores de comando, mas apenas como nomes de arquivos.
A citação dupla dos nomes de arquivos resolve adequadamente os casos em que os nomes contêm caracteres glob ou espaços em branco. Mas os nomes de arquivos * nix também podem conter novas linhas. Portanto, limitamos os nomes de arquivos com o único caractere que não pode fazer parte de um nome de arquivo válido - o byte nulo ( \0
). Como bash
internamente usa C
cadeias de estilo nas quais os bytes nulos são usados para indicar o final da cadeia, é o candidato certo para isso.
Portanto, usando a printf
opção do shell para delimitar arquivos com esse byte NULL usando a -d
opção de read
comando, podemos fazer abaixo
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
O nullglob
e o printf
são agrupados, o (..)
que significa que eles são basicamente executados em um sub-shell (shell filho), porque, para evitar a nullglob
opção de refletir no shell pai, uma vez que o comando é encerrado. A -d ''
opção de read
comando não é compatível com POSIX, portanto, é necessário um bash
shell para que isso seja feito. Usando o find
comando, isso pode ser feito como
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
Para find
implementações que não suportam -print0
(além das implementações GNU e FreeBSD), isso pode ser emulado usandoprintf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Outra correção importante é mover a redirecionamento para fora do loop for para reduzir um alto número de E / S de arquivo. Quando usado dentro do loop, o shell deve executar chamadas do sistema duas vezes para cada iteração do loop for, uma vez para abrir e outra para fechar o descritor de arquivo associado ao arquivo. Isso se tornará um gargalo no desempenho para executar iterações grandes. A sugestão recomendada seria movê-lo para fora do loop.
Estendendo o código acima com essas correções, você pode fazer
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
que basicamente coloca o conteúdo do seu comando para cada iteração da entrada do arquivo em stdout e, quando o loop terminar, abra o arquivo de destino uma vez para gravar o conteúdo do stdout e salvá-lo. A find
versão equivalente do mesmo seria
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out