Ottieni un elenco di sottodirectory che contengono un file il cui nome contiene una stringa


45

Come posso ottenere un elenco delle sottodirectory che contengono un file il cui nome corrisponde a un modello particolare?

Più specificamente, sto cercando directory che contengano un file con la lettera 'f' da qualche parte nel nome del file.

Idealmente, l'elenco non dovrebbe avere duplicati e contenere solo il percorso senza il nome file.

Risposte:


43
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

Quanto sopra trova tutti i file sotto la directory corrente ( .) che sono file regolari ( -type f) e hanno un fposto nel loro nome ( -name '*f*'). Quindi, sedrimuove il nome del file, lasciando solo il nome della directory. Quindi, l'elenco delle directory viene ordinato ( sort) e i duplicati rimossi ( uniq).

Il sedcomando è costituito da un singolo sostituto. Cerca corrispondenze con l'espressione regolare /[^/]+$e sostituisce qualsiasi cosa che corrisponda a nulla. Il simbolo del dollaro indica la fine della linea. [^/]+'indica uno o più caratteri che non sono barre. Pertanto, /[^/]+$significa tutti i personaggi dalla barra finale alla fine della riga. In altre parole, corrisponde al nome del file alla fine del percorso completo. Pertanto, il comando sed rimuove il nome del file, lasciando invariato il nome della directory in cui si trovava il file.

semplificazioni

Molti sortcomandi moderni supportano una -ubandiera che rende uniqsuperflua. Per GNU sed:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

E, per MacOS sed:

find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u

Inoltre, se il findcomando lo supporta, è possibile findstampare direttamente i nomi delle directory. Questo evita la necessità di sed:

find . -type f -name '*f*' -printf '%h\n' | sort -u

Versione più robusta (richiede strumenti GNU)

Le versioni precedenti saranno confuse dai nomi dei file che includono le nuove righe. Una soluzione più solida consiste nell'eseguire l'ordinamento su stringhe terminate con NUL:

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'

Ho molti file che li rendono troppo costosi. Lanciare uniqnel mix aiuta molto rimuovendo le linee ripetute che sono già una accanto all'altra. find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'. Oppure, se i tuoi strumenti sono un po 'più vecchi, uniq potrebbe non avere l'opzione -z. find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u
jbo5112,

1
Utenti MacOS: il flag sed non è -r. Per qualche ragione è -E
David

@ David molto vero. Risposta aggiornata per mostrare -Eper MacOS.
Giovanni 1024

23

Perché non provare questo:

find / -name '*f*' -printf "%h\n" | sort -u

Migliore risposta. Totalmente compatibile con POSIX, a differenza di alcune risposte sopra, sopra, e guadagna anche il premio speciale The Shortest Pipeline :).
kkm

Mi piacerebbe vedere qualcuno mostrare i tempi di questo rispetto agli altri sopra, perché ho la sensazione che questo sia di gran lunga il più veloce.
dlamblin,

4
@kkm Sono d'accordo che questa sia la soluzione migliore ma le specifiche POSIX perfind sono in realtà abbastanza scarse: l' -printfoperatore non è specificato. Questo non funziona con BSD find. Quindi, non "completamente compatibile POSIX". (Anche se sort -u è in POSIX .)
Wildcard

8

Ci sono essenzialmente 2 metodi che puoi usare per farlo. Uno analizzerà la stringa mentre l'altro opererà su ciascun file. L'analisi della stringa utilizza uno strumento come grep, sedo awksarà ovviamente più veloce, ma ecco un esempio che mostra entrambi, oltre a come "profilare" i 2 metodi.

Dati di esempio

Per gli esempi seguenti utilizzeremo i seguenti dati

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

Elimina alcuni *f*file da dir1/*:

$ rm dir1/dir10{0..2}/*f*

Approccio n. 1 - Analisi tramite stringhe

Qui stiamo andando a utilizzare i seguenti strumenti, find, grep, e sort.

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/

Approccio n. 2: analisi tramite file

Stessa catena di strumenti di prima, tranne che questa volta useremo dirnameinvece di grep.

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107

NOTA: gli esempi sopra riportati stanno head -5semplicemente limitando la quantità di output con cui abbiamo a che fare con questi esempi. Normalmente verrebbero rimossi per ottenere il tuo elenco completo!

Confronto dei risultati

Possiamo usare timeper dare un'occhiata ai 2 approcci.

dirname

real        0m0.372s
user        0m0.028s
sys         0m0.106s

grep

real        0m0.012s
user        0m0.009s
sys         0m0.007s

Quindi, se possibile, è sempre meglio occuparsi delle stringhe.

Metodi alternativi di analisi delle stringhe

grep & PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u

sed

$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u

awk

$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u

+1 Perché funziona, ma è interessante notare che ciò richiede molte volte più tempo della risposta di @ John1024
Muhd,

@Muhd - sì, le chiamate a dirname sono lente. Sto lavorando a un'alternativa.
slm

2

Eccone uno che trovo utile:

find . -type f -name "*somefile*" | xargs dirname | sort | uniq

1

Questa risposta si basa spudoratamente sulla risposta slm. Era un approccio interessante, ma ha una limitazione se i nomi di file e / o directory avevano caratteri speciali (spazio, semicolonna ...). Una buona abitudine è usare find /somewhere -print0 | xargs -0 someprogam.

Dati di esempio

Per gli esempi seguenti utilizzeremo i seguenti dati

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

Elimina alcuni *f*file da dir1/*/:

rm dir1/dir\ 10{0..2}/*f*

Approccio n. 1: analisi tramite file

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

NOTA : gli esempi sopra riportati stanno head -5semplicemente limitando la quantità di output con cui abbiamo a che fare con questi esempi. Normalmente verrebbero rimossi per ottenere il tuo elenco completo! inoltre, sostituisci echoquale comando vuoi usare.


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.