Suchen aller Dateien mit einer bestimmten Erweiterung, deren Basisname der Name des übergeordneten Verzeichnisses ist

9

Ich möchte rekursiv nach jeder *.pdfDatei in einem Verzeichnis suchen, ~/foodessen Basisname mit dem Namen des übergeordneten Verzeichnisses der Datei übereinstimmt.

Angenommen, die Verzeichnisstruktur ~/foosieht folgendermaßen aus

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

Das Ausführen meines gewünschten Befehls würde zurückkehren

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

Ist dies mit findoder einem anderen Kerndienstprogramm möglich? Ich gehe davon aus, dass dies mit der -regexOption möglich ist, findbin mir aber nicht sicher, wie ich das richtige Muster schreiben soll.

Brian Fitzpatrick
quelle
Ja, ich werde jetzt ein Beispiel verspotten.
Brian Fitzpatrick
1
@Inian Ein Beispiel hinzugefügt. Hilft das?
Brian Fitzpatrick

Antworten:

16

Mit GNU find:

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep Verwenden Sie egrep style regex.
  • .*/ Match Großeltern Direktiven.
  • ([^/]+)/ Übereinstimmung mit dem übergeordneten Verzeichnis in einer Gruppe.
  • \1\.pdfVerwenden Sie backreferencediese Option, um den Dateinamen als übergeordnetes Verzeichnis abzugleichen.

aktualisieren

Einer (ich für meinen Teil) könnte denken, dass .*das gierig genug ist, es ist unnötig, /vom Eltern-Matching auszuschließen :

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

Der obige Befehl funktioniert nicht gut, weil er rechnet ./a/b/a/b.pdf:

  • .*/ Streichhölzer ./
  • (.+)/ Streichhölzer a/b/
  • \1.pdf Streichhölzer a/b.pdf
dedowsdi
quelle
Sehr cool. Ich wünschte, ich könnte das gut wiedergeben.
Brian Fitzpatrick
Oder find . -regex '.*/\([^/]*\)/\1\.pdf'und dann würde es sogar mit BSD funktionieren find.
Stéphane Chazelas
7

Die traditionelle Schleifenvariante der find .. -exec sh -c ''Verwendung der Shell-Konstrukte zur Übereinstimmung mit dem Basisnamen und dem unmittelbaren Pfad darüber wäre unten.

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 {} +

Aufschlüsselung der einzelnen Parametererweiterungen

  • fileenthält den vollständigen Pfad der .pdfvom findBefehl zurückgegebenen Datei
  • "${file##*/}"enthält nur den Teil nach dem letzten, /dh nur den Basisnamen der Datei
  • "${file%/*}"enthält den Pfad bis zum Ende, /dh mit Ausnahme des Basisnamens des Ergebnisses
  • "${path##*/}"enthält den Teil nach dem letzten /aus der pathVariablen, dh den unmittelbaren Ordnerpfad über dem Basisnamen der Datei
  • "${base%.*}"enthält den Teil des Basisnamens, bei dem die .pdfErweiterung entfernt wurde

Wenn also der Basisname ohne Erweiterung mit dem Namen des unmittelbaren Ordners oben übereinstimmt, drucken wir den Pfad.

Inian
quelle
7

Die Umkehrung von Inians Antwort , dh nach Verzeichnissen suchen und dann prüfen, ob sie eine Datei mit einem bestimmten Namen enthalten.

Im Folgenden werden die Pfadnamen der gefundenen Dateien relativ zum Verzeichnis gedruckt 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##*/}wird durch den Dateinamen des Verzeichnispfads ersetzt und kann durch ersetzt werden $(basename "$dirpath").

Für Leute, die die Kurzschlusssyntax mögen:

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

Der Vorteil dieser Vorgehensweise besteht darin, dass Sie möglicherweise mehr PDF-Dateien als Verzeichnisse haben. Die Anzahl der beteiligten Tests wird reduziert, wenn man die Abfrage um die kleinere Anzahl (die Anzahl der Verzeichnisse) einschränkt.

Wenn ein einzelnes Verzeichnis beispielsweise 100 PDF-Dateien enthält, wird nur versucht, eine davon zu erkennen, anstatt die Namen aller 100 Dateien mit denen des Verzeichnisses zu vergleichen.

Kusalananda
quelle
3

mit zsh:

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

Beachten Sie, dass **/Symlinks zwar nicht folgen, aber folgen */.

Stéphane Chazelas
quelle
2

Es wurde nicht angegeben, aber hier ist eine Lösung ohne reguläre Ausdrücke, wenn jemand interessiert ist.

Wir können find . -type fnur Dateien abrufen, dann die Bedingung verwenden dirnameund basenameschreiben. Die Dienstprogramme haben das folgende Verhalten:

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

basenameGibt nur den Dateinamen nach dem letzten zurück /:

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

dirnamegibt den gesamten Weg bis zum Finale /:

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

Gibt daher basename $(dirname $file)das übergeordnete Verzeichnis der Datei an.

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

Lösung

Kombinieren Sie das Obige, um die Bedingung zu bilden "$(basename $file)" = "$(basename $(dirname $file))".pdf, und drucken Sie dann jedes Ergebnis nur aus, findwenn diese Bedingung true zurückgibt.

$ 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

Im obigen Beispiel haben wir ein Verzeichnis / eine Datei mit Leerzeichen im Namen hinzugefügt, um diesen Fall zu behandeln (danke an @Kusalananda in den Kommentaren).

user1717828
quelle
Dies wird leider bei Dateinamen wie Final Thesis.pdf(mit einem Leerzeichen) unterbrochen .
Kusalananda
@Kusalananda behoben.
user1717828
0

Ich mache jeden Tag Bash Globbing, einfache Loop-Over-String-Tests über das Find- Programm. Nennen Sie mich irrational, und obwohl es vielleicht suboptimal ist, macht solch einfacher Code den Trick für mich: lesbar und wiederverwendbar, sogar befriedigend!. Lassen Sie mich daher eine Kombination vorschlagen aus:

• bash globstar : for f in ** ; do ... ** Durchläuft alle Dateien im aktuellen Verzeichnis und alle Unterordner, um den Globstar-Status in Ihrer aktuellen Sitzung zu überprüfen : shopt -p globstar. So aktivieren Sie globstar : shopt -s globstar.

• "file" utlity : if [[ $(file "$f") =~ pdf ]]; then ... Zum Überprüfen des tatsächlichen Dateiformats auf PDF - robuster als nur das Testen der Dateierweiterung

• Basisname, Verzeichnisname : Zum Vergleichen des Dateinamens mit dem Namen des Verzeichnisses unmittelbar darüber. basenamegibt den Dateinamen zurück - dirnamegibt den gesamten Verzeichnispfad zurück - kombinieren Sie die beiden Funktionen, um nur das eine Verzeichnis zurückzugeben, das die übereinstimmende Datei enthält. Ich habe jedes in eine Variable ( _mydir und _myf ) eingefügt, um dann einen einfachen Test mit = ~ für den String-Abgleich durchzuführen .

Eine Subtilität: Entfernen Sie alle "Punkte" im Dateinamen, um zu vermeiden, dass der Dateiname mit dem aktuellen Verzeichnis übereinstimmt, dessen Verknüpfung ebenfalls "ist". - Ich habe die direkte Zeichenfolgenersetzung für die Variable _myf verwendet : ${_myf//./}- nicht sehr elegant, aber es funktioniert. Positive Übereinstimmungen geben den Pfad jeder Datei zurück - zusammen mit dem vollständigen Pfad des aktuellen Ordners, indem der Ausgabe Folgendes vorangestellt wird : $(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
docgyneco69
quelle