Recherche de tous les fichiers avec une extension donnée dont le nom de base est le nom du répertoire parent


9

Je veux rechercher récursivement chaque *.pdffichier dans un répertoire ~/foodont le nom de base correspond au nom du répertoire parent du fichier.

Par exemple, supposons que la structure du répertoire ~/fooressemble à ceci

foo
├── dir1
│   ├── dir1.pdf
│   └── dir1.txt
├── dir2
│   ├── dir2.tex
│   └── spam
│       └── spam.pdf
└── dir3
    ├── dir3.pdf
    └── eggs
        └── eggs.pdf

L'exécution de ma commande souhaitée reviendrait

~/foo/dir1/dir1.pdf
~/foo/dir2/spam/spam.pdf
~/foo/dir3/dir3.pdf
~/foo/dir3/eggs/eggs.pdf

Est-ce possible d'utiliser findou un autre utilitaire de base? Je suppose que cela est faisable en utilisant l' -regexoption pour findmais je ne sais pas comment écrire le bon modèle.


Oui, je vais simuler un exemple maintenant.
Brian Fitzpatrick

1
@Inian Ajout d'un exemple. est-ce que cela aide?
Brian Fitzpatrick

Réponses:


16

Avec GNU find:

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep utilisez l'expression rationnelle de style egrep.
  • .*/ correspondre aux directires du grand parent.
  • ([^/]+)/ correspond au répertoire parent dans un groupe.
  • \1\.pdfutiliser backreferencepour faire correspondre le nom du fichier au répertoire parent.

mise à jour

On (moi-même pour un) pourrait penser que .*c'est assez gourmand, il n'est pas nécessaire d'exclure /de la correspondance des parents:

find . -regextype egrep -regex '.*/(.+)/\1\.pdf'

La commande ci-dessus ne fonctionnera pas bien, car elle corrige ./a/b/a/b.pdf:

  • .*/ allumettes ./
  • (.+)/ allumettes a/b/
  • \1.pdf allumettes a/b.pdf

Très cool. J'aurais aimé pouvoir regex aussi bien.
Brian Fitzpatrick

Ou find . -regex '.*/\([^/]*\)/\1\.pdf'alors cela fonctionnerait même avec BSD find.
Stéphane Chazelas

7

La variante de boucle traditionnelle du find .. -exec sh -c ''pour utiliser les constructions shell pour faire correspondre le nom de base et le chemin immédiat ci-dessus serait de faire ci-dessous.

find foo/ -name '*.pdf' -exec sh -c '
    for file; do 
        base="${file##*/}"
        path="${file%/*}"
        if [ "${path##*/}" =  "${base%.*}" ]; then
            printf "%s\n" "$file" 
        fi
    done' sh {} +

Pour décomposer les extensions de paramètres individuels

  • filecontient le chemin complet du .pdffichier renvoyé par la findcommande
  • "${file##*/}"contient uniquement la partie après la dernière, /c'est-à-dire uniquement le nom de base du fichier
  • "${file%/*}"contient le chemin jusqu'à la finale, /c'est-à-dire à l'exception de la partie du nom de base du résultat
  • "${path##*/}"contient la partie après la dernière /de la pathvariable, c'est-à-dire le chemin du dossier immédiat au-dessus du nom de base du fichier
  • "${base%.*}"contient la partie du nom de base avec l' .pdfextension supprimée

Donc, si le nom de base sans extension correspond au nom du dossier immédiat ci-dessus, nous imprimons le chemin.


7

L'inverse de la réponse d' Inian , c'est-à-dire rechercher des répertoires, puis voir s'ils contiennent un fichier avec un nom particulier.

Les informations suivantes affichent les chemins d'accès des fichiers trouvés par rapport au répertoire foo:

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        if [ -f "$pathname" ]; then
            printf "%s\n" "$pathname"
        fi
    done' sh {} +

${dirpath##*/}sera remplacé par la partie nom de fichier du chemin du répertoire et pourrait être remplacé par $(basename "$dirpath").

Pour les personnes qui aiment la syntaxe de court-circuit:

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        [ -f "$pathname" ] && printf "%s\n" "$pathname"
    done' sh {} +

L'avantage de procéder de cette façon est que vous pouvez avoir plus de fichiers PDF que de répertoires. Le nombre de tests impliqués est réduit si l'on restreint la requête par le plus petit nombre (le nombre de répertoires).

Par exemple, si un seul répertoire contient 100 fichiers PDF, cela n'essaierait que de détecter l'un d'entre eux plutôt que de tester les noms des 100 fichiers par rapport à celui du répertoire.


3

avec zsh:

printf '%s\n' **/*/*.pdf(e@'[[ $REPLY:t = $REPLY:h:t.pdf ]]'@)

Attention, bien que **/ne suivra pas les liens symboliques, le */fera.


2

Cela n'a pas été spécifié, mais voici une solution sans expressions régulières si quelqu'un est intéressé.

Nous pouvons utiliser find . -type fpour obtenir simplement des fichiers, puis utiliser dirnameet basenameécrire le conditionnel. Les utilitaires ont le comportement suivant:

$ find . -type f
./dir2/spam/spam.pdf
./dir2/dir2.tex
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./dir1/dir1.txt

basenamerenvoie juste le nom de fichier après le dernier /:

$ for file in $(find . -type f); do basename $file; done
spam.pdf
dir2.tex
dir3.pdf
eggs.pdf
dir1.pdf
dir1.txt

dirnamedonne le chemin complet jusqu'à la finale /:

$ for file in $(find . -type f); do dirname $file; done
./dir2/spam
./dir2
./dir3
./dir3/eggs
./dir1
./dir1

Par conséquent, basename $(dirname $file)donne le répertoire parent du fichier.

$ for file in $(find . -type f); do basename $(dirname $file) ; done
spam
dir2
dir3
eggs
dir1
dir1

Solution

Combinez ce qui précède pour former le conditionnel "$(basename $file)" = "$(basename $(dirname $file))".pdf, puis n'imprimez chaque résultat findque si ce conditionnel renvoie true.

$ while read file; do if [ "$(basename "$file")" = "$(basename "$(dirname "$file")")".pdf ]; then echo $file; fi done < <(find . -type f)
./dir2/spam/spam.pdf
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./Final Thesis/grits/grits.pdf
./Final Thesis/Final Thesis.pdf

Dans l'exemple ci-dessus, nous avons ajouté un répertoire / fichier avec des espaces dans le nom pour traiter ce cas (merci à @Kusalananda dans les commentaires)


Cela cassera malheureusement sur les noms de fichiers comme Final Thesis.pdf(avec un espace).
Kusalananda

@Kusalananda Fixed.
user1717828

0

Je prends bash globbing, simple boucle sur des tests de chaîne tous les jours sur le programme Find . Appelez-moi irrationnel, et bien qu'il puisse être sous-optimal, ce code simple fait l'affaire pour moi: lisible et réutilisable, satisfaisant même!. Permettez-moi donc de suggérer une combinaison de:

• bash globstar : for f in ** ; do ... ** boucle sur tous les fichiers dans le répertoire courant et tous les sous - dossiers .. pour vérifier l' état de globstar dans votre session en cours: shopt -p globstar. Pour activer globstar: shopt -s globstar.

• utilité "fichier" : if [[ $(file "$f") =~ pdf ]]; then ... pour vérifier le format de fichier réel pour pdf - plus robuste que de tester uniquement l'extension du fichier

• basename, dirname : pour comparer le nom du fichier au nom du répertoire immédiatement au-dessus. basenameretourne le nom du fichier - dirnameretourne le chemin complet du répertoire - combinez les deux fonctions pour ne retourner que le seul répertoire contenant le fichier correspondant. Je mets chacun dans une variable ( _mydir et _myf ) pour ensuite faire un test simple en utilisant = ~ pour la correspondance de chaîne.

Une subtilité: supprimez tout "point" dans le nom de fichier pour éviter de faire correspondre le nom de fichier au répertoire actuel dont le raccourci est également "." - J'ai utilisé la substitution de chaîne directe sur la variable _myf : ${_myf//./}- pas très élégante mais ça marche. Matchs positifs retourneront le chemin de chaque fichier - avec le chemin complet du dossier en cours en précédant la sortie avec: $(pwd)/.

Code

for f in ** ; do
  if [[ $(file "$f") =~ PDF ]]; then
    _mydir="$(basename $(dirname $f))" ; 
    _myf="$(basename $f)" ; 
    [[ "${_myf//./}" =~ "$_mydir" ]] && echo -e "$(pwd)/$f" ; 
  fi ; 
done
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.