Comment trouver les fichiers manquants dans une liste?

9

J'ai une liste de fichiers que je veux vérifier s'ils existent sur mon système de fichiers. J'ai pensé à faire cela en utilisant findcomme dans:

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

(en utilisant zsh) mais cela ne fonctionne pas, car il findsemble se terminer, 0qu'il trouve ou non le fichier. Je suppose que je pourrais le passer à travers un autre test qui teste pour voir si findproduit une sortie (brute mais efficace serait de remplacer la > /dev/nullavec |grep '') mais cela ressemble à l'utilisation d'un troll pour attraper une chèvre (d'autres nationalités pourraient dire quelque chose à propos des marteaux et des noix ).

Existe-t-il un moyen de contraindre findà me donner une valeur de sortie utile? Ou au moins pour obtenir une liste de ces fichiers qui n'ont pas été trouvés? (Je peux imaginer que ce dernier soit peut-être plus facile grâce à un choix astucieux de connecteurs logiques, mais il semble que je sois toujours lié par des nœuds lorsque j'essaie de le comprendre.)

Contexte / Motivation: J'ai une sauvegarde "maître" et je veux vérifier que certains fichiers sur ma machine locale existent sur ma sauvegarde maître avant de les supprimer (pour créer un peu d'espace). J'ai donc fait une liste des fichiers, sshles ai édités sur la machine principale, et j'étais alors à court de trouver le meilleur moyen de trouver les fichiers manquants.

Andrew Stacey
la source
J'ai mis à jour ma solution pour l'utiliser beaucoup plus rapidement locate.
utilisateur inconnu
@userunknown n'affiche locatepas l'état actuel du système de fichiers, il peut s'agir d'un jour, voire d'une semaine. Cela convient comme base pour tester les sauvegardes.
Volker Siegel

Réponses:

5

findconsidère que rien ne constitue un cas particulier de réussite (aucune erreur n'est survenue). Une façon générale de tester si les fichiers correspondent à certains findcritères est de tester si la sortie de findest vide. Pour une meilleure efficacité lorsqu'il y a des fichiers correspondants, utilisez -quitsur GNU find pour le faire quitter à la première correspondance, ou head( head -c 1si disponible, sinon head -n 1ce qui est standard) sur d'autres systèmes pour le faire mourir d'un tuyau cassé plutôt que de produire une sortie longue.

while IFS= read -r name; do
  [ -n "$(find . -name "$name" -print | head -n 1)" ] || printf '%s\n' "$name"
done <file_list

En bash ≥4 ou zsh, vous n'avez pas besoin de la findcommande externe pour une simple correspondance de nom: vous pouvez utiliser **/$name. Version basique:

shopt -s nullglob
while IFS= read -r name; do
  set -- **/"$name"
  [ $# -ge 1 ] || printf '%s\n' "$name"
done <file_list

Version Zsh sur un principe similaire:

while IFS= read -r name; do
  set -- **/"$name"(N)
  [ $# -ge 1 ] || print -- "$name"
done <file_list

Ou voici un moyen plus court mais plus cryptique de tester l'existence d'un fichier correspondant à un modèle. Le qualificatif glob Nrend la sortie vide s'il n'y a pas de correspondance, [1]ne conserve que la première correspondance et e:REPLY=true:modifie chaque correspondance pour qu'elle se développe au 1lieu du nom de fichier correspondant. **/"$name"(Ne:REPLY=true:[1]) falseS'étend donc en true falsecas de correspondance, ou simplement falses'il n'y a pas de correspondance.

while IFS= read -r name; do
  **/"$name"(Ne:REPLY=true:[1]) false || print -- "$name"
done <file_list

Il serait plus efficace de combiner tous vos noms en une seule recherche. Si le nombre de modèles n'est pas trop important pour la limite de longueur de votre système sur une ligne de commande, vous pouvez joindre tous les noms avec -o, effectuer un seul findappel et post-traiter la sortie. Si aucun des noms ne contient de métacaractères shell (afin que les noms soient également des findmodèles), voici un moyen de post-traiter avec awk (non testé):

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}}}
'

Une autre approche consisterait à utiliser Perl et File::Find, ce qui facilite l'exécution du code Perl pour tous les fichiers d'un répertoire.

perl -MFile::Find -l -e '
    %missing = map {chomp; $_, 1} <STDIN>;
    find(sub {delete $missing{$_}}, ".");
    print foreach sort keys %missing'

Une autre approche consiste à générer une liste de noms de fichiers des deux côtés et à travailler sur une comparaison de texte. Version Zsh:

comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)
Gilles 'SO- arrête d'être méchant'
la source
J'accepte celui-ci pour deux raisons. J'aime la zshsolution avec la **syntaxe. C'est une solution très simple et même si elle n'est peut-être pas la plus efficace en termes de machine , elle est probablement la plus efficace si je m'en souviens! De plus, la première solution ici répond à la question réelle en ce qu'elle se findtransforme en quelque chose où le code de sortie distingue "J'ai obtenu une correspondance" de "Je n'ai pas obtenu de correspondance".
Andrew Stacey
9

Vous pouvez utiliser statpour déterminer si un fichier existe sur le système de fichiers.

Vous devez utiliser les fonctions intégrées du shell pour tester si des fichiers existent.

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

Le "test" est facultatif et le script fonctionnera réellement sans lui, mais je l'ai laissé là pour la lisibilité.

Edit: Si vous n'avez vraiment pas d'autre option que de travailler pour une liste de noms de fichiers sans chemins, je vous suggère de créer une liste de fichiers une fois avec find, puis de l'itérer avec grep pour déterminer quels fichiers sont là.

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

Notez que:

  • la liste des fichiers ne comprend que des fichiers et non des répertoires,
  • la barre oblique dans le modèle de correspondance grep est donc nous comparons les noms de fichiers complets et non partiels,
  • et le dernier «$» dans le modèle de recherche doit correspondre à la fin de la ligne afin que vous n'obteniez pas de correspondances de répertoire, uniquement des correctifs de nom de fichier complet.
Caleb
la source
stat a besoin de l'emplacement exact, n'est-ce pas? J'utilise find car j'ai juste une liste de noms de fichiers et ils pourraient être dans de nombreux répertoires. Désolé si ce n'était pas clair.
Andrew Stacey
Hmmm. Vous n'avez pas dit que vous aviez des noms de fichiers sans chemins! Peut-être que vous pouvez résoudre ce problème à la place? Ce serait bien plus efficace que d'exécuter plusieurs fois le même jeu de données.
Caleb
Merci pour la modification et désolé encore une fois de ne pas être précis. Le nom / chemin du fichier n'est pas quelque chose que je vais corriger - les fichiers peuvent être à des endroits différents sur les deux systèmes, donc je veux une solution suffisamment robuste pour contourner cela. L'ordinateur devrait fonctionner selon mes spécifications, et non l'inverse! Sérieusement, ce n'est pas quelque chose que je fais souvent - je cherchais des vieux fichiers à supprimer pour faire de la place et je voulais juste un moyen "rapide et sale" pour m'assurer qu'ils étaient dans mes sauvegardes.
Andrew Stacey
Tout d'abord, vous n'auriez pas besoin d'un chemin complet, juste un chemin relatif vers la structure de répertoires que vous sauvegardiez. Permettez-moi de suggérer que si le chemin d'accès n'est pas le même, il y a de fortes chances que le fichier ne soit pas le même et vous pourriez obtenir de faux positifs de votre test. Il semble que votre solution soit plus sale que rapide; Je ne voudrais pas te voir brûlé en pensant que tu avais quelque chose que tu n'avais pas. De plus, si les fichiers sont suffisamment précieux pour être sauvegardés en premier lieu, vous ne devez pas supprimer les primaires, sinon vous devez sauvegarder vos sauvegardes!
Caleb
Ak! J'ai laissé un tas de détails pour essayer de concentrer la question et vous remplissez ceux-ci avec un tas d'hypothèses qui - je devrais dire - sont parfaitement raisonnables mais se trouvent être complètement erronées! Il suffit de dire que je sais que si le fichier est là et se trouve dans un répertoire avec un type de nom particulier, je sais que c'est le fichier d'origine et qu'il est sûr de supprimer la copie sur ma machine.
Andrew Stacey
1

Une première approche simpliste pourrait être:

a) triez votre liste de fichiers:

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

pour trouver des disparus, ou

comm sorted.lst found.lst

pour trouver des correspondances

  • Pièges:
    • Les sauts de ligne dans les noms de fichiers sont très difficiles à gérer
    • les blancs et les choses similaires dans les noms de fichiers ne sont pas sympa non plus. Mais puisque vous contrôlez les fichiers dans la liste des fichiers, cette solution est peut-être déjà suffisante, cependant ...
  • Désavantages:

    • Lorsque find trouve un fichier, il continue de fonctionner pour en trouver un autre et un autre. Ce serait bien de sauter une autre recherche.
    • find pourrait rechercher plusieurs fichiers à la fois, avec une certaine préparation:

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

La localisation pourrait-elle être une option? Encore une fois, une liste présélectionnée de fichiers supposait:

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

Une recherche de foo.bar ne correspondra pas à un fichier foo.ba ou oo.bar avec la construction --regexp-(à ne pas confondre avec regex sans p).

Vous pouvez spécifier une base de données spécifique pour la localisation, et vous devez la mettre à jour avant la recherche, si vous avez besoin des résultats les plus récents.

Utilisateur inconnu
la source
1

Je pense que cela peut aussi être utile.

Il s'agit d'une solution sur une seule ligne, au cas où vous opteriez pour que votre "liste" soit de vrais fichiers que vous souhaitez synchroniser avec un autre dossier:

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

pour aider à la lecture:

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

cet exemple exclut les fichiers de sauvegarde "* ~" et limite le type de fichier normal "-type f"

Puissance du Verseau
la source
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}

Peut être?

Bonjour71
la source
0

Pourquoi ne pas simplement comparer la longueur de la liste de requêtes avec la longueur de la liste de résultats?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Holger Brandl
la source