encontrar | O xargs shasum cria a soma de verificação do próprio arquivo de soma de verificação (prematuramente) e falha ao verificar

10

Meu problema (em um script com #!/bin/sh) é o seguinte: Tento somar todos os arquivos em um diretório para fins de arquivamento. O arquivo de soma de verificação (no meu caso sha1) com todos os nomes de arquivos deve residir no mesmo diretório. Vamos dizer que temos um diretório ~/testcom arquivos f1e f2:.

mkdir ~/test
cd ~/test
echo "hello" > f1
echo "world" > f2

Agora calculando as somas de verificação com

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum

faz exatamente o que eu quero, lista apenas todos os arquivos do diretório atual e calcula as somas sha1 (a profundidade máxima pode ser alterada posteriormente). A saída em STDOUT é:

f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2

Infelizmente, ao tentar salvar isso em um arquivo com

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum > sums.sha1

o arquivo resultante exibe a soma de verificação para si:

da39a3ee5e6b4b0d3255bfef95601890afd80709  sums.sha1
f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2  

e, portanto, falha posteriormente shasum --check, devido ao problema óbvio de modificação de arquivo adicional ao salvar a última soma.

Olhei em volta e, usando o -psinalizador para xargs, descobri que de alguma forma cria o arquivo de saída antes mesmo de executar o comando find, portanto, o arquivo adicional é encontrado e será soma de verificação ...

Sei que, como solução alternativa, eu poderia salvar a soma de verificação em outro local (diretório temporário via mktemp) ou excluí-la na localização específica, mas gostaria de entender por que ela se comporta da maneira que faz - o que, a meu ver, não é tão útil, por exemplo, se o primeiro comando verificar se o arquivo de saída já está no disco, ele nunca obterá a resposta correta ...

user121391
fonte
8
Não é xargs, é o próprio shell que cria esse arquivo, porque antes que qualquer comando seja executado, o shell redireciona todas as entradas, saídas e pipes, de modo que, quando findiniciado, o arquivo de saída já existe. Em -execvez disso, use :find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
jimmij
@jimmij, isso não garante que funcione, se várias shchamadas forem necessárias. Observe que você precisa de um argumento para $0antes {}.
Stéphane Chazelas
@jimmij Sua outra resposta sugerida teedesapareceu? Eu tentei e funciona bem, eu também suprimi STDOUT com adição de 1>/dev/null. Havia algo errado com a resposta ou foi um bug?
user121391
@ user121391 Stephane salientou que, às vezes, pode haver um problema de condição de corrida, o que parece verdade. Cancelei a remoção por um tempo para que você possa procurar, mas se você tiver muitos arquivos na lista, esse comando poderá dar errado.
jimmij
@ ahimmij, entendo. Pode ser útil se você o prefixar com um aviso sobre os problemas, porque acho que não é tão conhecido que isso pode acontecer. Caso contrário, eu teria aceitado sua resposta para os casos se as execuções recorrentes incluírem o arquivo antigo e o Anthon nos casos em que ele deve ser substituído.
user121391

Respostas:

12

Você pode impedir que o arquivo chegue xargsusando:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\n' |
  xargs -r shasum -- > sums.sha1

Para evitar problemas com o nome do arquivo com espaços em branco ou novas linhas ou aspas ou barras invertidas, eu usaria:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\0' |
  xargs -r0 shasum -- > sums.sha1

em vez de.

O --objetivo é evitar problemas com nomes de arquivos que começam com -. No entanto, não ajudará em um arquivo chamado -. Se você tivesse usado em -print0vez de -printf '%P\0', não precisaria --e não teria um problema com o -arquivo.

Anthon
fonte
Sua solução é o que acabei usando. Eu particularmente gosto que as execuções subsequentes não refazem novamente o arquivo de soma de verificação e inflem o diretório. Além disso, no meu script, eu costumava basenameobter o nome do arquivo sums.sha1 do caminho completo fornecido (isso não foi incluído na pergunta, mas pode ajudar outras pessoas).
user121391
7

Desde que você está usando -maxdepth 1, presumo que você não deseja recursão. Nesse caso, basta fazê-lo no shell:

for f in ~/test/*; do
    shasum -- "$f"
done > sums.sha1

Para pular diretórios, você pode:

for f in ~/test/*; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

Se você precisar de recursão e estiver usando bash, faça:

shopt -s globstar
for f in ~/test/**; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

Observe que todas essas abordagens têm o benefício de trabalhar em nomes de arquivos arbitrários, incluindo aqueles com espaços, novas linhas ou qualquer outra coisa.

terdon
fonte
Eu acho que você mencionaria que isso resolve todos os problemas que o OP teria com nomes de arquivos com novas linhas neles também. Por outro lado, se ele sums.sha1já estiver lá (de uma execução anterior), sua solução a incorporará.
Anthon
Desculpe, eu não esclareceu antes: o maxdepth só foi utilizado neste exemplo, eu uso uma função onde o usuário / script pode fornecer quaisquer valores, embora atualmente eu só preciso de profundidade 1.
user121391
@ user121391 veja resposta atualizada para uma abordagem recursiva.
terdon
Observe que ele também tentará somar outros tipos de arquivos não regulares, como pipes, dispositivos ... (e links simbólicos para eles).
Stéphane Chazelas
Obrigado, pessoalmente, estou usando sh, mas sua resposta pode ajudar outras pessoas.
user121391
4

com zsh:

shasum -- *(D.) > sums.sha1

O globo será expandido antes do redirecionamento, para que sums.sha1ele não seja incluído se não estiver lá.

Dé incluir arquivos de ponto (arquivos ocultos) como findfaria. .é selecionar apenas arquivos regulares (como o seu -type f).

Para excluir o sums.sha1mesmo assim, caso ele estivesse lá em primeiro lugar:

setopt extendedglob # best in ~/.zshrc
shasum -- ^sums.sha1(D.) > sums.sha1

Observe que eles executam um comando shasum; portanto, você pode acabar vendo um erro "Arg list too long" se a lista for enorme. Para contornar isso:

autoload zargs
zargs -e/ -- *(D.) / shasum > sums.sha1

Eu recomendaria usar em ./*vez de *evitar possíveis problemas com um arquivo chamado -.

Stéphane Chazelas
fonte
Eu editei a questão com o tipo de shell, mas sua resposta me faz lembrar que eu queria mudar para zsh há algum tempo ...;)
user121391
1

Como as outras respostas já declararam, o problema é que o shell abre e cria o sums.sha1arquivo antes de executar seu pipeline. Você pode usar o programa spongeque faz parte do moreutilspacote de muitas distribuições. Em contraste com o redirecionamento do shell sponge, aguarde até receber tudo, antes de abrir o arquivo. Geralmente é usado quando você deseja gravar um arquivo que lê no mesmo pipeline.

No seu caso, é usado assim:

$ find -maxdepth 1 -type f -printf '%P\n' |xargs shasum |sponge sums.sha1
$ cat sums.sha1
31836aeaab22dc49555a97edb4c753881432e01d  B
7d157d7c000ae27db146575c08ce30df893d3a64  A
TimWolla
fonte
0

Como alternativa ao find / xargs etc, você pode querer o sha1deep. Provavelmente está em um pacote diferente - na minha caixa, ele vem no pacote md5deep.

Como já foi dito, o sums.sha1 é criado pelo shell mesmo antes do início da localização. Um truque com ! -name sums.sha1a findvai funcionar, como a vontade

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum | grep -v ' sums\.sha1$' > sums.sha1
Torinthiel
fonte