Как получить только имя файла в Linux 'find'?

267

Я использую поиск для всех файлов в каталоге, поэтому я получаю список путей. Однако мне нужны только имена файлов. т.е. я получаю ./dir1/dir2/file.txtи хочу получитьfile.txt

marverix
источник

Ответы:

360

В GNU findвы можете использовать -printfпараметр для этого, например:

find /dir1 -type f -printf "%f\n"
SiegeX
источник
9
Четкий ответ , но в нем не хватает деталей.
Джейсон МакКрири
25
это только для GNU
Усама Ф Элиас
3
Это не работает для меня, когда я использую несколько типов файлов (ключ -o)
Urchin
9
найти: -printf: неизвестный первичный или оператор
holms
@ Urchin Нет причин, по которым это должно происходить, если у вас правильная логика (то есть -oимеет более низкий приоритет, чем подразумеваемый -a, поэтому вы часто захотите сгруппировать свои -oаргументы)
Восстановите Монику Пожалуйста,
157

Если в вашем поиске нет опции -printf, вы также можете использовать basename:

find ./dir1 -type f -exec basename {} \;
Kambus
источник
17
Цитирование точки с запятой - это еще один способ устранения неоднозначности:... {} ';'
davidchambers
28

Используйте -execdirкоторый автоматически содержит текущий файл {}, например:

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

Вы также можете использовать $PWDвместо .(в некоторых системах это не приведет к появлению дополнительной точки спереди).

Если у вас все еще есть лишняя точка, вы можете запустить:

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

-execdir utility [argument ...] ;

-execdirПервична идентична -execпервичный за исключением того , утилита будет выполнена из каталога, держит текущий файл .

Когда используется +вместо ;, то {}заменяется таким количеством путей, сколько возможно для каждого вызова утилиты. Другими словами, он напечатает все имена файлов в одну строку.

kenorb
источник
Я получаю ./filenameвместо filename. В зависимости от ваших потребностей, это может или не может быть хорошо.
user276648
@ user276648 Попробуйте с $PWDвместо ..
kenorb
25

Если вы используете GNU найти

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

Или вы можете использовать язык программирования, такой как Ruby (1.9+)

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

Если вам нравится решение bash (как минимум 4)

shopt -s globstar
for file in **; do echo ${file##*/}; done
Каруми
источник
Я был вдохновлен вашим ответом, чтобы помочь sleske: serverfault.com/a/745968/329412
Nolwennig
11

Если вы хотите выполнить какое-либо действие только с именем файла, использовать basenameможет быть сложно.

Например это:

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

будет просто повторять базовое имя /my/found/path. Не то, что мы хотим, если мы хотим выполнить по имени файла.

Но вы можете затем xargsна выходе. например, чтобы убить файлы в каталоге, основываясь на именах в другом каталоге:

cd dirIwantToRMin;
find ~/clang+llvm-3.3/bin/ -type f -exec basename {} \; | xargs rm
j03m
источник
не эхом -find ~/clang+llvm-3.3/bin/ -type f -exec basename {} \;
commonpike
7

На Mac (BSD find) используйте:

find /dir1 -type f -exec basename {} \;
спектр
источник
1

-execи -execdirмедленно, xargsэто король.

$ 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

xargsПараллелизм также помогает.

Как ни странно, я не могу объяснить последний случай xargsбез -n1. Это дает правильный результат, и это самый быстрый¯\_(ツ)_/¯

( basenameпринимает только 1 аргумент пути, но xargsотправит их все (на самом деле 5000) без -n1. не работает на linux и openbsd, только macOS ...)

Несколько больших цифр из системы Linux, чтобы увидеть, как это -execdirпомогает, но все же намного медленнее, чем параллель 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
источник
1
еще одна точка данных: на openbsd в долгосрочной перспективе findэто -execdirстановится самым быстрым, поскольку создание новых процессов - относительно дорогая операция.
минус
1

Честно говоря, basenameи dirnameрешения проще, но вы также можете проверить это:

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

или

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

или

find . -type f | sed "s/.*\///"
почетный гражданин
источник
0

Как уже отмечали другие, вы можете комбинировать findи basename, но по умолчанию basenameпрограмма будет работать только по одному пути за раз, поэтому исполняемый файл придется запускать один раз для каждого пути (используя либо find ... -execили find ... | xargs -n 1), что потенциально может быть медленным.

Если вы используете -aопцию on basename, то он может принимать несколько имен файлов за один вызов, что означает, что вы можете затем использовать xargsбез -n 1, чтобы сгруппировать пути вместе в гораздо меньшее количество вызовов basename, что должно быть более эффективным.

Пример:

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

Здесь я включил -print0и -0(который должен использоваться вместе), чтобы справиться с любым пробелом в именах файлов и каталогов.

Вот сравнение времени, между xargs basename -aи xargs -n1 basenameверсиями. (Для сравнения «похоже на аналогию» время, указанное здесь, указано после первоначального фиктивного запуска, так что они оба выполняются после того, как метаданные файла уже скопированы в кэш ввода / вывода.) Я передал выходные данные в cksumв обоих случаях просто для демонстрации того, что вывод не зависит от используемого метода.

$ 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

Как вы можете видеть, это действительно намного быстрее, чтобы избежать запуска basenameкаждый раз.

alaniwi
источник
Читая ответ от @minusf более подробно, я вижу, что на Mac basenameбудут приниматься несколько имен файлов без дополнительных аргументов командной строки. Использование -aздесь на Linux. ( basename --versionговорит мне basename (GNU coreutils) 8.28.)
alaniwi
-3

Я нашел решение (на странице makandracards), которое дает только самое новое имя файла:

ls -1tr * | tail -1

(спасибо Арне Хартцер)

Я использовал это для cp:

cp $(ls -1tr * | tail -1) /tmp/
jazzinka
источник
2
Это не отвечает на вопрос вообще.
Кенорб