Як я можу елегантно виконати дію над усіма файлами із певним розширенням у папках?


18

Моя поточна найкраща ставка:

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

Проблема: не обробляє пробіли у назви файлів.

Примітка. Я також хотів би графічний спосіб зробити це, наприклад, команда "дерево".


Ви не можете просто поставити цитати $i? Я це роблю, і це прекрасно працює. Чи щось не так у цьому підході?
Umar Farooq Khawaja

Відповіді:


26

Канонічний спосіб - це зробити

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

(Замінити \;з , +щоб передати більше одного файлу echoв той час)

або (специфічно для GNU, хоча деякі BSD також є):

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

zsh:

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

2
Якщо ви використовуєте xargs -0 , вам слід зіставити його з find -0
itsbruce

1
@ MichaelKjörling, жодне цитування {} не має значення. {} розгортається знахідкою, а не оболонкою. Поліпшенням не буде використання, echoяке розширює схожі послідовності на зразок \b(принаймні, відповідних Unix echo).
Стефан Шазелас

1
@sch Ви впевнені? Сторінка findвідображається 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 і не справляється із символами зворотної косої риски чи новою лінією у назви файлів. циклі "час читання" в оболонках (ІМО) часто є ознакою поганої практики сценаріїв оболонок.
Стефан Шазелас

@sch: так, і я думав, що це нормально. Дякуємо, що вказали на це. Мені, напевно, потрібно докладніше розглянути це.
Темний лицар

вибачте, я мав на увазі "GNU", а не "GUN" вище (це перший раз, коли я усвідомлюю, що це анаграми BTW ...)
Stéphane Chazelas

1

Просто з findта bash:

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

Таким чином ми використовуємо $1 в 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 (with set -G), він не повинен використовуватися в bash, оскільки версія bash принципово зламана (як і GNU grep -r), оскільки вона спускається в посилання на каталоги (еквівалентні find -Lабо zsh ***/*.jpg). Також зауважте, що всупереч знаходженню він опустить dotfiles і не спуститься в dotdirs (за замовчуванням).
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.