find
considera não encontrar nada como um caso especial de sucesso (nenhum erro ocorreu). Uma maneira geral de testar se os arquivos correspondem a alguns find
critérios é testar se a saída de find
está vazia. Para obter melhor eficiência quando houver arquivos correspondentes, use -quit
no GNU find para que ele saia na primeira correspondência ou head
( head -c 1
se disponível, caso contrário, head -n 1
, que é padrão) em outros sistemas para torná-lo morra de um cano quebrado em vez de produzir uma saída longa.
while IFS= read -r name; do
[ -n "$(find . -name "$name" -print | head -n 1)" ] || printf '%s\n' "$name"
done <file_list
No bash ≥4 ou zsh, você não precisa do comando find
externo para uma correspondência de nome simples: você pode usar **/$name
. Versão de bash:
shopt -s nullglob
while IFS= read -r name; do
set -- **/"$name"
[ $# -ge 1 ] || printf '%s\n' "$name"
done <file_list
Versão do Zsh com um princípio semelhante:
while IFS= read -r name; do
set -- **/"$name"(N)
[ $# -ge 1 ] || print -- "$name"
done <file_list
Ou aqui está uma maneira mais curta, porém mais enigmática, de testar a existência de um arquivo que corresponde a um padrão. O qualificador de glob N
faz a saída vazia se não houver correspondência, [1]
retém apenas a primeira correspondência e e:REPLY=true:
altera cada correspondência para expandir para 1
em vez do nome de arquivo correspondente. Portanto, **/"$name"(Ne:REPLY=true:[1]) false
expande para true false
se houver uma correspondência ou apenas false
se não houver correspondência.
while IFS= read -r name; do
**/"$name"(Ne:REPLY=true:[1]) false || print -- "$name"
done <file_list
Seria mais eficiente combinar todos os seus nomes em uma única pesquisa. Se o número de padrões não for muito grande para o limite de comprimento do sistema em uma linha de comando, você poderá unir todos os nomes com -o
, fazer uma única chamada find
e pós-processar a saída. Se nenhum dos nomes contiver metacaracteres de shell (para que os nomes também sejam find
patterns), aqui está uma maneira de pós-processar com awk (não testado):
set -o noglob; IFS='
'
set -- $(<file_list sed -e '2,$s/^/-o\
/')
set +o noglob; unset IFS
find . \( "$@" \) -print | awk -F/ '
BEGIN {while (getline <"file_list") {found[$0]=0}}
wanted[$0]==0 {found[$0]=1}
END {for (f in found) {if (found[f]==0) {print f}}}
'
Outra abordagem seria usar Perl e File::Find
, o que facilita a execução do código Perl para todos os arquivos em um diretório.
perl -MFile::Find -l -e '
%missing = map {chomp; $_, 1} <STDIN>;
find(sub {delete $missing{$_}}, ".");
print foreach sort keys %missing'
Uma abordagem alternativa é gerar uma lista de nomes de arquivos em ambos os lados e trabalhar em uma comparação de texto. Versão do Zsh:
comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)