Comment obtenir uniquement le nom de fichier avec Linux 'find'?

266

J'utilise find pour tous les fichiers du répertoire, donc j'obtiens une liste de chemins. Cependant, je n'ai besoin que de noms de fichiers. c'est-à-dire que je reçois ./dir1/dir2/file.txtet je veux obtenirfile.txt

marverix
la source

Réponses:

359

Dans GNU, findvous pouvez utiliser des -printfparamètres pour cela, par exemple:

find /dir1 -type f -printf "%f\n"
SiegeX
la source
9
Clairement la réponse , mais elle manque de détails.
Jason McCreary
25
ceci est uniquement pour GNU
Osama F Elias
3
Cela ne fonctionne pas pour moi lorsque j'utilise plusieurs types de fichiers (commutateur -o)
Urchin
9
find: -printf: primaire ou opérateur inconnu
holms
@Urchin Aucune raison que cela ne devrait pas aussi longtemps que vous avez une logique correcte (c'est-à-dire -oa une priorité inférieure à celle implicite -a, vous voudrez donc souvent regrouper vos -oarguments)
Réinstallez Monica s'il vous plaît
157

Si votre recherche n'a pas d'option -printf, vous pouvez également utiliser le nom de base:

find ./dir1 -type f -exec basename {} \;
Kambus
la source
17
Citer le point-virgule est une autre façon de ... {} ';'
lever l'ambiguïté
28

Utilisation -execdirqui contient automatiquement le fichier actuel {}, par exemple:

find . -type f -execdir echo '{}' ';'

Vous pouvez également utiliser $PWDau lieu de. (sur certains systèmes, il ne produira pas de point supplémentaire à l'avant).

Si vous avez encore un point supplémentaire, vous pouvez également exécuter:

find . -type f -execdir basename '{}' ';'

-execdir utility [argument ...] ;

Le -execdirprincipal est identique au -execprincipal, à l'exception que l'utilitaire sera exécuté à partir du répertoire qui contient le fichier actuel .

Lorsqu'il est utilisé à la +place de ;, alors {}est remplacé par autant de chemins que possible pour chaque invocation d'utilitaire. En d'autres termes, il imprimera tous les noms de fichiers sur une seule ligne.

Kenorb
la source
Je reçois ./filenameau lieu de filename. Selon vos besoins, cela peut ou peut ne pas être bien.
user276648
@ user276648 Essayez avec $PWDau lieu de ..
kenorb
24

Si vous utilisez GNU find

find . -type f -printf "%f\n"

Ou vous pouvez utiliser un langage de programmation tel que Ruby (1.9+)

$ ruby -e 'Dir["**/*"].each{|x| puts File.basename(x)}'

Si vous avez envie d'une solution bash (au moins 4)

shopt -s globstar
for file in **; do echo ${file##*/}; done
kurumi
la source
J'ai été inspiré par votre réponse pour aider sleske: serverfault.com/a/745968/329412
Nolwennig
11

Si vous souhaitez exécuter une action uniquement contre le nom de fichier, utilisez basename peut être difficile.

Par exemple ceci:

find ~/clang+llvm-3.3/bin/ -type f -exec echo basename {} \; 

fera simplement écho au nom de base /my/found/path. Pas ce que nous voulons si nous voulons exécuter sur le nom de fichier.

Mais vous pouvez ensuite xargsla sortie. par exemple pour tuer les fichiers dans un répertoire basé sur des noms dans un autre répertoire:

cd dirIwantToRMin;
find ~/clang+llvm-3.3/bin/ -type f -exec basename {} \; | xargs rm
j03m
la source
dont echo -find ~/clang+llvm-3.3/bin/ -type f -exec basename {} \;
commonpike
7

Sur Mac (BSD find) utilisez:

find /dir1 -type f -exec basename {} \;
spectre
la source
1

-execet -execdirsont lents, xargsest roi.

$ alias f='time find /Applications -name "*.app" -type d -maxdepth 5'; \
f -exec basename {} \; | wc -l; \
f -execdir echo {} \; | wc -l; \
f -print0 | xargs -0 -n1 basename | wc -l; \
f -print0 | xargs -0 -n1 -P 8 basename | wc -l; \
f -print0 | xargs -0 basename | wc -l

     139
    0m01.17s real     0m00.20s user     0m00.93s system
     139
    0m01.16s real     0m00.20s user     0m00.92s system
     139
    0m01.05s real     0m00.17s user     0m00.85s system
     139
    0m00.93s real     0m00.17s user     0m00.85s system
     139
    0m00.88s real     0m00.12s user     0m00.75s system

xargsLe parallélisme aide également.

Curieusement, je ne peux pas expliquer le dernier cas de xargssans-n1 . Il donne le résultat correct et c'est le plus rapide¯\_(ツ)_/¯

( basenameprend seulement 1 argument de chemin mais xargsles enverra tous (en fait 5000) sans -n1. ne fonctionne pas sur linux et openbsd, seulement macOS ...)

Quelques chiffres plus importants d'un système Linux pour voir comment cela -execdiraide, mais toujours beaucoup plus lentement qu'un parallèle xargs:

$ alias f='time find /usr/ -maxdepth 5 -type d'
$ f -exec basename {} \; | wc -l; \
f -execdir echo {} \; | wc -l; \
f -print0 | xargs -0 -n1 basename | wc -l; \
f -print0 | xargs -0 -n1 -P 8 basename | wc -l

2358
    3.63s real     0.10s user     0.41s system
2358
    1.53s real     0.05s user     0.31s system
2358
    1.30s real     0.03s user     0.21s system
2358
    0.41s real     0.03s user     0.25s system
minusf
la source
1
juste un autre point de données: sur openbsd sur le long terme, findc'est -execdircelui qui devient le plus rapide car la création de nouveaux processus est une opération relativement coûteuse.
minusf
1

Honnêtement basename, les dirnamesolutions sont plus faciles, mais vous pouvez également vérifier ceci:

find . -type f | grep -oP "[^/]*$"

ou

find . -type f | rev | cut -d '/' -f1 | rev

ou

find . -type f | sed "s/.*\///"
Homme libre
la source
0

Comme d'autres l'ont souligné, vous pouvez combiner findet basename, mais par défaut, le basenameprogramme ne fonctionnera que sur un chemin à la fois, donc l'exécutable devra être lancé une fois pour chaque chemin (en utilisant l'un find ... -execou l' autre find ... | xargs -n 1), ce qui peut potentiellement être lent.

Si vous utilisez l' -aoption on basename, elle peut accepter plusieurs noms de fichiers en une seule invocation, ce qui signifie que vous pouvez ensuite utiliser xargssans le -n 1, pour regrouper les chemins d'accès en un nombre beaucoup plus faible d'invocations de basename, ce qui devrait être plus efficace.

Exemple:

find /dir1 -type f -print0 | xargs -0 basename -a

Ici, j'ai inclus le -print0et-0 (qui devrait être utilisé ensemble), afin de faire face à tout espace à l'intérieur des noms de fichiers et de répertoires.

Voici une comparaison temporelle, entre les xargs basename -axargs -n1 basename versions et . (Par souci de comparaison, les synchronisations signalées ici sont après une exécution fictive initiale, afin qu'elles soient toutes les deux effectuées une fois que les métadonnées du fichier ont déjà été copiées dans le cache d'E / S.) J'ai canalisé la sortie vers cksumdans les deux cas, juste pour démontrer que la sortie est indépendante de la méthode utilisée.

$ time sh -c 'find /usr/lib -type f -print0 | xargs -0 basename -a | cksum'
2532163462 546663

real    0m0.063s
user    0m0.058s
sys 0m0.040s

$ time sh -c 'find /usr/lib -type f -print0 | xargs -0 -n 1 basename | cksum' 
2532163462 546663

real    0m14.504s
user    0m12.474s
sys 0m3.109s

Comme vous pouvez le voir, il est vraiment beaucoup plus rapide d'éviter de lancer à basenamechaque fois.

alaniwi
la source
En lisant la réponse de @minusf de manière plus approfondie, je vois que sur un Mac basenameacceptera plusieurs noms de fichiers sans avoir besoin d'arguments de ligne de commande supplémentaires. L'utilisation -aici est sous Linux. ( basename --versionme dit basename (GNU coreutils) 8.28.)
alaniwi
-3

J'ai trouvé une solution (sur la page makandracards), qui donne juste le nom de fichier le plus récent:

ls -1tr * | tail -1

(merci à Arne Hartherz)

Je l'ai utilisé pour cp:

cp $(ls -1tr * | tail -1) /tmp/
jazzinka
la source
2
Cela ne répond pas du tout à la question.
kenorb