Как выполнить действие для всех файлов с определенным расширением в подпапках элегантным способом?


18

Моя текущая лучшая ставка:

for i in $(find . -name *.jpg); do echo $i; done

Проблема: не обрабатывает пробелы в именах файлов.

Примечание: я также хотел бы графического способа сделать это, например, команду "дерево".


Вы не можете просто поместить цитаты вокруг $i? Я делаю это, и это прекрасно работает. Что-то не так с этим подходом?
Умар Фарук Хаваджа

Ответы:


26

Канонический способ сделать

find . -name '*.jpg' -exec echo {} \;

(заменить \;с , +чтобы передать более одного файла echoв то время)

или (специфично для GNU, хотя некоторые BSD теперь также имеют его):

find . -name '*.jpg' -print0 | xargs -r0 echo

ЗШ:

for i (**/*.jpg(D)) echo $i

2
Если вы используете xargs -0 , вы должны сопоставить его с find -0
itsbruce

1
@ MichaelKjörling, без цитирования {} нет никакой разницы. {} расширяется поиском, а не оболочкой. Улучшение было бы не использовать, echoкоторый расширяет escape-последовательности, такие как \b(по крайней мере, соответствующие Unix echo).
Стефан Шазелас

1
@sch Ты уверен? В findManpage показано find . -type f -exec file '{}' \;в EXAMPLESразделе. Может быть, некоторые снаряды будут обрабатывать брекеты специально.
QuasarDonkey

1
Цитаты не вредят, но не имеют значения. Все '{}', \{\}, {}, "{}", '{'}разлагаются оболочкой (независимо от Борна типа оболочки) к одному аргументу , чтобы найти , что это два символа «{» и «}». Замените «find» на «echo find», или printf '<%s>\n' findесли вы хотите перепроверить.
Стефан Шазелас


2

Лучшие ответы уже были даны.

Но обратите внимание, что пробелы - не единственная проблема в коде, который вы дали. Символы табуляции, перевода строки и подстановочных знаков также являются проблемой.

С

IFS='
'
set -f
for i in $(find . -name '*.jpg'); do echo "$i"; done

Тогда только символы новой строки являются проблемой.


1

То, что вы упомянули, является одной из основных проблем, с которыми сталкиваются люди, когда они пытаются прочитать имена файлов. Иногда люди с ограниченными знаниями и неправильным представлением о структуре файлов и папок, как правило, забывают, что « в UNIX все является файлом ». Поэтому они не понимают, что им также нужно обрабатывать пробелы, поскольку имя файла может состоять из 2 или более слов с пробелами.

Решение. Таким образом, один из наиболее известных способов сделать это - сделать чистое чтение .

Здесь мы прочтем все имеющиеся файлы и сохраним их в переменной, в следующий раз, когда мы будем выполнять требуемую обработку, нам просто нужно будет сохранить эту переменную внутри кавычек, которая сохранит имена файлов с пробелами. Это один из основных способов сделать это, однако, другие ответы, предоставленные другими людьми здесь, работают так же хорошо.

СЦЕНАРИЙ:

 find /path/to/files/ -iname "*jpg" | \
  while read I; do
   cp -v --parent "$I" /backup/dir/
  done

Здесь я читаю файлы, задавая им путь, и, что бы я ни читал, я храню их в переменной I, которую я процитирую позже при обработке, чтобы сохранить пробелы для правильной обработки.

Надеюсь, это поможет вам в некотором роде.


1
«все это файл» относится к чему-то еще. Ваше решение зависит от GUN и не справляется с символами обратной косой черты или новой строки в именах файлов. Циклы "while read" в оболочках (IMO) часто являются признаком плохой практики сценариев оболочки.
Стефан Шазелас

@sch: да, и я думал, что все в порядке. Спасибо за указание на это. Мне, вероятно, нужно больше на это смотреть.
Темный рыцарь

извините, я имел в виду "GNU", а не "GUN" выше (это первый раз, когда я понимаю, что это анаграммы ...)
Стефан Шазелас

1

Просто с findи bash:

find . -name '*.jpg' -exec bash -c '
    echo "treating file $1 in bash, from path : ${1%/*}" 
' -- {} \;

Таким образом, мы используем $1в bash, как и в базовом скрипте, которые открывают хорошие перспективы для выполнения любых сложных (или нет) задач.

Более эффективный способ (чтобы избежать необходимости запуска нового bash для каждого файла):

find . -name '*.jpg' -exec bash -c 'for i do
    echo "treating file $i in bash, from path : ${i%/*}" 
  done' -- {} +

0

В последней версии bash вы можете использовать globstarопцию:

shopt -s globstar
for file in **/*.jpg ; do
    echo "$file"
done

Для простых действий вы можете даже полностью пропустить цикл:

echo **/*.jpg

Хотя это работает в zsh (откуда появилась эта функция) и в ksh93 (с set -G), его не следует использовать в bash, поскольку версия bash принципиально сломана (как GNU grep -r), так как она спускается в символические ссылки на каталоги (эквивалентные find -Lили zsh). ***/*.jpg). Также обратите внимание, что, в отличие от поиска, он пропускает точечные файлы и не спускается в точечные (по умолчанию).
Стефан Шазелас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.