trouver | xargs shasum crée la somme de contrôle du fichier de somme de contrôle lui-même (prématurément) et échoue lors de la vérification

10

Mon problème (dans un script avec #!/bin/sh) est le suivant: j'essaie de faire la somme de contrôle de tous les fichiers d'un répertoire à des fins d'archivage. Le fichier de somme de contrôle (dans mon cas sha1) avec tous les noms de fichiers doit résider dans le même répertoire. Disons que nous avons un répertoire ~/testavec des fichiers f1et f2:.

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

Calculons maintenant les sommes de contrôle avec

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

fait exactement ce que je veux, il répertorie tous les fichiers du répertoire actuel uniquement et calcule les sommes sha1 (maxdepth peut être modifié ultérieurement). La sortie sur STDOUT est:

f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2

Malheureusement, lorsque vous essayez de l'enregistrer dans un fichier avec

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

le fichier résultant affiche la somme de contrôle pour lui-même:

da39a3ee5e6b4b0d3255bfef95601890afd80709  sums.sha1
f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2  

et échoue donc à une date ultérieure shasum --check, en raison du problème évident de modification de fichier supplémentaire lors de l'enregistrement de la dernière somme.

J'ai regardé autour de moi et en utilisant -pflag for xargs, j'ai découvert qu'il créait en quelque sorte le fichier de sortie avant même d'exécuter la commande find, donc le fichier supplémentaire est trouvé et sera additionné ...

Je sais que pour contourner ce problème, je pourrais enregistrer la somme de contrôle dans un autre emplacement (répertoire temporaire via mktemp) ou l'exclure dans find spécifiquement, mais j'aimerais comprendre pourquoi il se comporte comme il le fait - ce qui, à mes yeux, n'est pas très utile, par exemple, si la première commande vérifiait si le fichier de sortie est déjà sur le disque, elle n'obtiendrait jamais la bonne réponse ...

user121391
la source
8
Ce n'est pas le cas xargs, c'est le shell lui-même qui crée ce fichier, car avant d'exécuter une commande, le shell redirige d'abord toutes les entrées, sorties et canaux, de sorte qu'au finddémarrage, le fichier de sortie existe déjà. Utilisez à la -execplace:find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
jimmij
@jimmij, ce n'est pas garanti de fonctionner non plus si plusieurs shinvocations sont nécessaires. Notez que vous avez besoin d'un argument pour $0avant {}.
Stéphane Chazelas
@jimmij Votre autre réponse suggérée teea disparu? Je l'ai essayé et cela fonctionne bien, j'ai également supprimé STDOUT avec l'ajout de 1>/dev/null. Y a-t-il eu un problème avec la réponse ou était-ce un bug?
user121391
@ user121391 Stephane a souligné que parfois il peut y avoir un problème de condition de concurrence, ce qui semble vrai. Je l'ai restitué pendant un certain temps pour que vous puissiez regarder, mais si vous avez beaucoup de fichiers sur la liste, cette commande pourrait mal tourner.
jimmij
@jimmij ah, je vois. Il pourrait être utile de le préfixer avec un avertissement sur les problèmes, car je pense que ce n'est pas si bien connu que cela peut se produire. Sinon, j'aurais accepté votre réponse pour les cas si les exécutions récurrentes incluent l'ancien fichier et Anthon pour les cas où il devrait être écrasé.
user121391

Réponses:

12

Vous pouvez empêcher le fichier d'atteindre en xargsutilisant:

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

Pour éviter les problèmes avec les noms de fichiers comportant des blancs, des retours à la ligne ou des guillemets ou des barres obliques inverses, j'utiliserais cependant:

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

au lieu.

Il --s'agit d'éviter les problèmes avec les noms de fichiers commençant par -. Cela n'aidera cependant pas pour un fichier appelé -. Si vous aviez utilisé à la -print0place de -printf '%P\0', vous n'auriez pas eu besoin de --et n'auriez pas eu de problème avec le -fichier.

Anthon
la source
Votre solution est ce que j'ai fini par utiliser. J'aime particulièrement que les exécutions suivantes ne ressassent pas le fichier de somme de contrôle et ne gonflent pas le répertoire. De plus, dans mon script, basenamej'obtenais le nom de fichier sums.sha1 à partir du chemin complet donné (cela n'était pas inclus dans la question, mais cela pourrait aider les autres).
user121391
7

Puisque vous utilisez -maxdepth 1, je suppose que vous ne voulez pas de récursivité. Si c'est le cas, faites-le simplement dans le shell à la place:

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

Pour ignorer les répertoires, vous pouvez faire:

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

Si vous avez besoin d'une récursivité et utilisez bash, faites:

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

Notez que toutes ces approches ont l'avantage de travailler sur des noms de fichiers arbitraires, y compris ceux avec des espaces, des sauts de ligne ou quoi que ce soit d'autre.

terdon
la source
Je pense que vous diriez que cela résout tous les problèmes que l'OP aurait avec les noms de fichiers avec des retours à la ligne. Par contre, si le sums.sha1est déjà là (lors d'une précédente exécution), votre solution l'intégrera.
Anthon
Désolé, je n'ai pas clarifié auparavant: la maxdepth n'a été utilisée que dans cet exemple, j'utilise une fonction où l'utilisateur / script peut fournir toutes les valeurs, bien que pour l'instant je n'ai besoin que de la profondeur 1.
user121391
@ user121391 voir la réponse mise à jour pour une approche récursive.
terdon
Notez qu'il essaiera également de faire la somme de contrôle d'autres types de fichiers non réguliers comme les tuyaux, les périphériques ... (et les liens symboliques vers eux).
Stéphane Chazelas
Merci, personnellement j'utilise sh, mais votre réponse pourrait aider les autres.
user121391
4

avec zsh:

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

Le glob sera étendu avant que la redirection ne soit effectuée, donc le sums.sha1ne sera pas inclus s'il n'était pas là en premier lieu.

Dest d'inclure des fichiers dot (fichiers cachés) comme le findferait. .est de sélectionner uniquement les fichiers normaux (comme le vôtre -type f).

Pour exclure de sums.sha1toute façon au cas où il était là en premier lieu:

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

Notez que ceux-ci exécutent une commande shasum, vous pouvez donc finir par voir une erreur "Arg list too long" si la liste est énorme. Pour contourner cela:

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

Je recommanderais d'utiliser ./*plutôt que d' *éviter des problèmes potentiels avec un fichier appelé -.

Stéphane Chazelas
la source
J'ai édité la question avec le type de shell, mais votre réponse me rappelle que je voulais passer à zsh il y a quelque temps ...;)
user121391
1

Comme les autres réponses l'ont déjà indiqué, le problème est que le shell s'ouvre et crée le sums.sha1fichier, avant d'exécuter votre pipeline. Vous pouvez utiliser le programme spongequi fait partie du moreutilspackage de nombreuses distributions. Contrairement au shell, la redirection spongeattendra d'avoir tout reçu avant d'ouvrir le fichier. Il est généralement utilisé lorsque vous souhaitez écrire un fichier que vous lisez dans le même pipeline.

Dans votre cas, il est utilisé comme ceci:

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

Comme alternative à find / xargs, etc., vous voudrez peut-être sha1deep. C'est probablement dans un paquet différent - sur ma boîte, il vient dans le paquet md5deep.

Comme d'autres l'ont dit, sums.sha1 est créé par le shell avant même que la recherche ne commence. Une astuce avec ! -name sums.sha1to findfonctionnera, tout comme

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