Como encontro quais arquivos estão faltando em uma lista?

9

Eu tenho uma lista de arquivos que eu quero verificar se eles existem no meu sistema de arquivos. Eu pensei em fazer isso usando find como em:

for f in $(cat file_list); do
find . -name $f > /dev/null || print $f
done

(usando zsh ), mas isso não funciona, pois find parece sair de 0 , quer encontre ou não o arquivo. Eu acho que eu poderia passar por algum outro teste que testa para ver se find produz qualquer saída (bruto, mas eficaz seria substituir o > /dev/null com |grep '' ), mas isso é como usar um troll para pegar uma cabra ( outras nacionalidades podem dizer algo sobre marrecos e nozes).

Existe uma maneira de coagir find a me dar um valor de saída útil? Ou pelo menos para obter uma lista dos arquivos que não foram encontrados? (Eu posso imaginar o último sendo talvez mais fácil por alguma escolha inteligente de conectivos lógicos, mas eu pareço sempre ficar amarrado em nós quando eu tento descobrir isso.)

Experiência / Motivação: Eu tenho um backup "mestre" e quero verificar se alguns arquivos em minha máquina local existem no meu backup mestre antes de excluí-los (para criar um pouco de espaço). Então, fiz uma lista dos arquivos, ssh ed para a máquina mestre e fiquei sem saber como encontrar os arquivos que faltam.

    
por Loop Space 25.04.2011 / 20:54

6 respostas

5

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)
    
por 25.04.2011 / 22:28
9

Você pode usar stat para determinar se existe um arquivo no sistema de arquivos.

Você deve usar as funções de shell incorporadas para testar se existem arquivos.

while read f; do
   test -f "$f" || echo $f
done < file_list

O "teste" é opcional e o script funcionará sem ele, mas eu deixei lá para legibilidade.

Editar: Se você realmente não tem outra opção a não ser trabalhar para uma lista de nomes de arquivos sem caminhos, sugiro que você crie uma lista de arquivos uma vez com find e então iterar com o grep para descobrir quais arquivos estão lá.

find -type f /dst > $TMPFILE
while read f; do
    grep -q "/$f$" $TIMPFILE || echo $f
done < file_list

Observe que:

  • a lista de arquivos inclui apenas arquivos que não são diretórios,
  • a barra no padrão de correspondência do grep é, portanto, comparamos nomes de arquivos completos e não parciais,
  • e o último '$' no padrão de pesquisa é corresponder ao final da linha para que você não obtenha correspondências de diretório, somente patches de nome de arquivo completo.
por 25.04.2011 / 20:58
1

Uma primeira abordagem simplista poderia ser:

a) classifique sua lista de arquivos:

sort file.lst > sorted.lst 
for f in $(< sortd.lst) ; do find -name $f -printf "%f\n"; done > found.lst
diff sorted.lst found.lst

para encontrar erros ou

comm sorted.lst found.lst

para encontrar correspondências

  • Armadilhas:
    • Novas linhas em nomes de arquivos são muito difíceis de manipular
    • espaços em branco e coisas semelhantes em nomes de arquivos também não são bons. Mas como você tem controle sobre os arquivos na lista de arquivos, talvez essa solução já seja suficiente, no entanto ...
  • Desvantagens:

    • Quando o find encontra um arquivo, ele continua correndo para encontrar outro e outro. Seria bom pular mais pesquisas.
    • find poderia pesquisar vários arquivos de uma vez, com alguma preparação:

      find -name a.file -ou -name -b.file -ou -name c.file ...

Poderia ser uma opção? Novamente, uma lista de arquivos presumida:

 for f in $(< sorted.tmp) ; do locate --regexp "/"$f"$" > /dev/null || echo missing $f ; done

Uma pesquisa por foo.bar não corresponderá a um arquivo foo.ba ou oo.bar com o --regexp-construct (não deve ser confessado pelo regex sem p).

Você pode especificar um banco de dados específico para localização, e você precisa atualizá-lo antes de pesquisar, se precisar dos resultados mais recentes.

    
por 25.04.2011 / 22:57
1

Acho que isso também pode ser útil.

Esta é uma solução de uma linha, no caso de você optar pela sua "lista", arquivos reais que você deseja sincronizar com outra pasta:

function FUNCsync() { local fileCheck="$synchronizeTo/$1"; if [[ ! -f "$fileCheck" ]];then echo "$fileCheck";fi; };export -f FUNCsync;find "$synchronizeFrom/" -maxdepth 1 -type f -not -iname "*~" -exec bash -c 'FUNCsync "{}"' \; |sort

para ajudar na leitura:

function FUNCsync() {
  local fileCheck="$synchronizeTo/$1";
  if [[ ! -f "$fileCheck" ]];then 
    echo "$fileCheck";
  fi; 
};export -f FUNCsync;
find "$synchronizeFrom/" -maxdepth 1 -type f -not -iname "*~" -exec bash -c 'FUNCsync "{}"' \; |sort

este exemplo exclui arquivos de backup "* ~" e limites para o tipo de arquivo regular "-type f"

    
por 26.06.2013 / 22:15
0
FIND_EXP=". -type f \( "
while read f; do
   FIND_EXP="${FIND_EXP} -iname $f -or"
done < file_list
FIND_EXP="${var%-or}"
FIND_EXP="${FIND_EXP} \)"
find ${FIND_EXP}

Talvez?

    
por 25.04.2011 / 22:15
0

Por que não simplesmente comparar o tamanho da lista de consultas com o tamanho da lista de resultados?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
    
por 15.12.2016 / 08:45

Tags