Trovare tutti i file con una determinata estensione il cui nome base è il nome della directory principale


9

Voglio cercare ricorsivamente ogni *.pdffile in una directory il ~/foocui nome di base corrisponda al nome della directory principale del file.

Ad esempio, supponiamo che la struttura delle directory sia ~/foosimile a questa

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

L'esecuzione del comando desiderato sarebbe tornata

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

Questo è possibile usando findo qualche altra utility di base? Presumo che questo sia fattibile usando l' -regexopzione per findma non sono sicuro di come scrivere il modello corretto.


Sì, prenderò ora un esempio.
Brian Fitzpatrick,

1
@Inian Aggiunto un esempio. questo aiuta?
Brian Fitzpatrick,

Risposte:


16

Con GNU find:

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep usa regex in stile egrep.
  • .*/ abbinare le direttive del nonno.
  • ([^/]+)/ abbina la directory principale in un gruppo.
  • \1\.pdfusare backreferenceper abbinare il nome del file come dir parent.

aggiornare

Uno (me stesso per uno) potrebbe pensare che .*sia abbastanza avido, non è necessario escludere /dalla corrispondenza dei genitori:

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

Il comando sopra non funzionerà bene, perché combatte ./a/b/a/b.pdf:

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

Molto bello. Vorrei poter regex così bene.
Brian Fitzpatrick,

Oppure find . -regex '.*/\([^/]*\)/\1\.pdf'funzionerebbe anche con BSD find.
Stéphane Chazelas,

7

La variante di ciclo tradizionale di find .. -exec sh -c ''utilizzare i costrutti di shell per abbinare il nome di base e il percorso immediato sopra sarebbe fare di seguito.

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

Per scomporre le singole espansioni dei parametri

  • filecontiene il percorso completo del .pdffile restituito dal findcomando
  • "${file##*/}"contiene solo la parte successiva all'ultimo, /ovvero solo il nome base del file
  • "${file%/*}"contiene il percorso fino al finale /cioè tranne la parte basename del risultato
  • "${path##*/}"contiene la parte successiva all'ultima /della pathvariabile, ovvero il percorso della cartella immediata sopra il nome base del file
  • "${base%.*}"contiene la parte del nome base con l' .pdfestensione rimossa

Quindi se il nome di base senza estensione corrisponde al nome della cartella immediata sopra, stampiamo il percorso.


7

Il contrario della risposta di Inian , vale a dire cercare le directory e quindi vedere se contengono un file con un nome specifico.

Di seguito vengono stampati i percorsi dei file trovati relativi alla directory 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##*/}verrà sostituito dalla parte del nome file del percorso della directory e potrebbe essere sostituito da $(basename "$dirpath").

Per le persone a cui piace la sintassi del corto circuito:

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

Il vantaggio di farlo in questo modo è che potresti avere più file PDF che directory. Il numero di test coinvolti viene ridotto se si limita la query al numero più piccolo (il numero di directory).

Ad esempio, se una singola directory contiene 100 file PDF, ciò proverebbe a rilevarne solo uno anziché testare i nomi di tutti i 100 file rispetto a quelli della directory.


3

con zsh:

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

Attenzione che mentre **/non seguirà i symlink, lo */farà.


2

Non è stato specificato, ma qui è una soluzione senza espressioni regolari se qualcuno è interessato.

Possiamo usare find . -type fsolo per ottenere file, quindi utilizzare dirnamee basenamescrivere il condizionale. Le utility hanno il seguente comportamento:

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

basenamerestituisce solo il nome file dopo l'ultimo /:

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

dirnamedà l'intero percorso fino alla finale /:

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

Pertanto, basename $(dirname $file)fornisce la directory principale del file.

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

Soluzione

Combina quanto sopra per formare il condizionale "$(basename $file)" = "$(basename $(dirname $file))".pdf, quindi stampa ogni risultato solo findse quel condizionale ritorna vero.

$ 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

Nell'esempio sopra, abbiamo aggiunto una directory / file con spazi nel nome per trattare quel caso (grazie a @Kusalananda nei commenti)


Questo purtroppo si interromperà su nomi di file come Final Thesis.pdf(con uno spazio).
Kusalananda

@Kusalananda Fixed.
user1717828,

0

Prendo bash globbing, semplici loop over test di stringa ogni giorno sul programma Trova . Chiamami irrazionale, e anche se potrebbe non essere ottimale un codice così semplice mi fa il trucco: leggibile e riutilizzabile, soddisfacente persino !. Consentitemi quindi di suggerire una combinazione di:

• bash globstar : for f in ** ; do ... ** loop oltre ogni file nella directory corrente e in tutte le sottocartelle .. Per controllare lo stato globstar nella sessione corrente: shopt -p globstar. Per attivare globstar: shopt -s globstar.

• Utilità "file" : if [[ $(file "$f") =~ pdf ]]; then ... per verificare il formato file effettivo per pdf - più robusto del test solo per l'estensione del file

• basename, dirname : per confrontare il nome del file con il nome della directory immediatamente sopra di esso. basenamerestituisce il nome file - dirnamerestituisce l'intero percorso della directory - combina le due funzioni per restituire solo una directory contenente il file corrispondente. Ho messo ognuno in una variabile ( _mydir e _myf ) per fare un semplice test usando = ~ per la corrispondenza delle stringhe.

Una sottotitolazione: rimuovere qualsiasi "punto" nel nome del file per evitare di abbinare il nome del file alla directory corrente il cui collegamento è anche "." - Ho usato la sostituzione diretta della stringa sulla variabile _myf : ${_myf//./}- non molto elegante ma funziona. Partite positive torneranno il percorso di ogni file - insieme con il percorso completo della cartella corrente facendo precedere l'uscita con: $(pwd)/.

Codice

for f in ** ; do
  if [[ $(file "$f") =~ PDF ]]; then
    _mydir="$(basename $(dirname $f))" ; 
    _myf="$(basename $f)" ; 
    [[ "${_myf//./}" =~ "$_mydir" ]] && echo -e "$(pwd)/$f" ; 
  fi ; 
done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.