Как использовать команду find для вывода списка имен файлов, но не пути?

Jan 06 2021

Я проверил несколько ответов в Интернете, но они не работают, когда -nameв команде есть параметр (возможно, я что-то делаю не так, не уверен). Я хотел бы указать только имена файлов (а не каталог / путь), и, чтобы усложнить, у меня такое же имя файла с другим расширением, поэтому мне нужно отфильтровать результат, используя -nameи -o, проблема в предложениях, которые я видел использовать либо -printfили basename, и они не работают, когда есть -name *.txt -o name *.pdf. Есть ли другой способ решить эту проблему?

Пример моих файлов:

/dir1/dir2/dir3/dir4/
                    /file1.txt
                    /file1.pdf
                    /file1.ods ...etc.

Меня интересует только перечисление одного или нескольких типов файлов для каждого списка (с использованием -nameдля фильтрации моих результатов.

Можно ли сделать это, используя только 1 findкоманду, или мне нужно сделать это за 2 шага (сохранить результат во временном файле, а затем отфильтровать / удалить каталог из временного файла)? Результатом, которого я пытаюсь добиться, является текстовый файл с именем файла в каждой строке:

file1.txt
file1.pdf
file1.ods
file2.txt
file2.pdf
etc.

Я использовал команду

find /dir1/dir2/dir3/dir4/ -type f -name '*.txt' -o -name '*.pdf' -o -name '*.ods' -printf "%f\n"

Я использую GNU find.

Обновить

Оказалось, я должен поставить \(...\), как напомнил αғsнιη

Ответы

3 αғsнιη Jan 06 2021 at 21:02

При использовании нескольких выражений в режиме логического ИЛИ -oвам необходимо заключить их все внутрь \( ... \), иначе всегда будет учитываться результат последнего выражения; и -printf '%f\n'используется для вывода имен файлов только с удаленными ведущими каталогами.

find . -type f \( -name '*.pdf' -o -name '*.txt' \) -printf '%f\n'

см man findпод ОПЕРАТОРАМИ раздела о ( expr ):

ОПЕРАТОРЫ
...

(expr)
Приоритет силы. Поскольку круглые скобки являются специальными для оболочки, вам обычно нужно заключать их в кавычки.

поэтому вы можете использовать \( ... \)или '(' ... ')'(пробелы после и перед круглыми скобками важны), чтобы избежать их.

1 Kusalananda Jan 06 2021 at 22:58

Как уже было сказано, вам нужно сгруппировать свои -nameтесты в скобках. Эти круглые скобки должны быть экранированы \( ... \)или '(' ... ')'заключены в кавычки, например, для защиты от оболочки (где в противном случае они обозначили бы подоболочку).

Используя стандарт find, а также немного упростив обработку суффиксов имени файла:

set -- txt pdf ods

for suffix do
    set -- "$@" -o -name "*.$suffix"
    shift
done
shift

find /dir1/dir2/dir3/dir4 \( "$@" \) -type f -exec basename {} \;

Это сначала устанавливает позиционные параметры в список суффиксов имени файла, которые мы хотели бы видеть (это также может быть взято из командной строки, если это сценарий), и создает список -nameтестов по ИЛИ . Затем это используется в findкомандной строке.

Затем сама findкоманда вызывает basenameутилиту для каждого найденного имени.

В качестве альтернативы мы могли бы вместо этого использовать подстановку параметра для удаления всех компонентов каждого пути, кроме имени файла, но для этого мы должны передать имена пути во встроенный sh -cскрипт:

# [...] as before [...]

find /dir1/dir2/dir3/dir4 \( "$@" \) -type f -exec sh -c '
    for pathname do
        printf "%s\n" "${pathname##*/}"
    done' sh {} +

Я ожидал, что это будет работать быстрее, чем вариант с использованием basename, по крайней мере, если есть много имен файлов для обработки, хотя с GNU basenameиз coreutils вы можете использовать -aили --multipleнравится так:

find /dir1/dir2/dir3/dir4 \( "$@" \) -type f -exec basename -a {} +

Это будет звонить basenameкак можно реже. Но опять же, если у вас установлен coreutils, у вас также может быть findutils с GNU find, и вы можете использовать его -printf '%f\n'для того же.