Виконайте дію у кожному під-каталозі за допомогою Bash


196

Я працюю над сценарієм, який повинен виконувати дію у кожному підкаталозі певної папки.

Який найефективніший спосіб написати це?


1
Подумайте про повернення та переоцінку відповідей на правильність - ви отримали прийняту відповідь, отримавши багато переглядів, незважаючи на основні помилки (f / e, запуск її в каталог, де хтось раніше запускався mkdir 'foo * bar', спричинить fooі barбуде повторений, навіть якщо їх не існує, і *заміна буде замінена списком усіх імен файлів, навіть тих, що не мають каталогів).
Чарльз Даффі

1
... ще гірше, якщо хтось побіг mkdir -p '/tmp/ /etc/passwd /'- якщо хтось запускає сценарій, дотримуючись цієї практики /tmp, скажімо, знайти каталоги для видалення, вони можуть закінчити видалення /etc/passwd.
Чарльз Даффі

Відповіді:


179
for D in `find . -type d`
do
    //Do whatever you need with D
done

81
це порушиться на білих просторах
ghostdog74

2
Також зауважте, що вам потрібно налаштувати findпарами, якщо ви хочете рекурсивну чи нерекурсивну поведінку.
Кріс Тонкінсон

32
Вищеназвана відповідь дала мені і сам каталог, тому наступне спрацювало трохи краще для мене: find . -mindepth 1 -type d
jzheaux

1
@JoshC, обидва варіанти розбить каталог, створений mkdir 'directory name with spaces'на чотири окремих слова.
Чарльз Даффі

4
Мені потрібно було додати, -mindepth 1 -maxdepth 1або це зайшло занадто глибоко.
ScrappyDev

284

Версія, яка уникає створення підпроцесу:

for D in *; do
    if [ -d "${D}" ]; then
        echo "${D}"   # your processing here
    fi
done

Або, якщо ваша дія є однією командою, це більш стисло:

for D in *; do [ -d "${D}" ] && my_command; done

Або ще більш стислий варіант ( спасибі @enzotib ). Зауважте, що в цій версії кожне значення значень Dмає кінцеву косу рису:

for D in */; do my_command; done

49
Ви можете уникнути ifабо [з:for D in */; do
enzotib

2
+1, оскільки імена каталогів не починаються з ./ на відміну від прийнятої відповіді
Hayri Uğur Koltuk

3
Цей правильний навіть до пробілів у іменах каталогу +1
Alex Reinking

5
Існує одна проблема з останньою командою: якщо ви знаходитесь в каталозі без підкаталогів; воно поверне "* /". Тож краще скористайтеся другою командою for D in *; do [ -d "${D}" ] && my_command; doneабо комбінацією двох останніх:for D in */; do [ -d $D ] && my_command; done
Кріс Мейс

5
Зауважте, що ця відповідь ігнорує приховані каталоги. Для включення прихованих каталогів використовуйте for D in .* *; doнатомість for D in *; do.
patryk.beza

107

Найпростіший не рекурсивний спосіб:

for d in */; do
    echo "$d"
done

В /кінці підказує, використовуйте лише каталоги.

У цьому немає потреби

  • знайти
  • awk
  • ...

11
Примітка: сюди не будуть входити крапки з точками (що може бути хорошою справою, але це важливо знати).
wisbucky

Корисно зазначити, що ви можете використовувати shopt -s dotglobдля включення dotfiles / dotdirs під час розширення символів. Дивіться також: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Стін Шютт

Я думаю , що ви мали в виду , /*а не */з , /що представляє шлях , який ви хочете використовувати.
Brōtsyorfuzthrāx

2
@Shule /*буде абсолютним шляхом, тоді як він */буде включати в себе підкаталоги з поточного місця розташування
Dan G

3
корисна підказка: якщо вам потрібно обрізати ${d%/*}
пробіл

18

Використовувати findкоманду.

У GNU findможна використовувати -execdirпараметр:

find . -type d -execdir realpath "{}" ';'

або за допомогою -execпараметра:

find . -type d -exec sh -c 'cd -P "$0" && pwd -P' {} \;

або з xargsкомандою:

find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'

Або , використовуючи для циклу:

for d in */; { echo "$d"; }

Для рекурсивності **/замість цього спробуйте розширений глобулінг ( ) (увімкнути :) shopt -s extglob.


Докладніші приклади див. У розділі: Як перейти до кожного каталогу та виконати команду? при SO


1
-exec {} +є специфікою POSIX, -exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +є іншим правовим варіантом і викликає меншу кількість оболонок, ніж -exec sh -c '...' {} \;.
Чарльз Даффі

13

Зручні однолінійки

for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A

find * -type d ### Option B

Варіант A є правильним для папок з пробілами між ними. Крім того, як правило, швидше, оскільки воно не друкує кожне слово у назві папки як окремий об'єкт.

# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real    0m0.327s
user    0m0.084s
sys     0m0.236s

# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real    0m0.787s
user    0m0.484s
sys     0m0.308s

10

find . -type d -print0 | xargs -0 -n 1 my_command


чи можу я подати повернене значення цієї знахідки у цикл for? Це частина більшого сценарію ...
mikewilliamson

@Mike, навряд чи. $? швидше за все, ви отримаєте статус знахідки чи команди xargs, а не my_command.
Пол Томблін

7

Це створить нижню частину оболонки (це означає, що значення змінних втрачаються при whileвиході циклу):

find . -type d | while read -r dir
do
    something
done

Це не буде:

while read -r dir
do
    something
done < <(find . -type d)

Будь-яка з них буде працювати, якщо в іменах каталогу є пробіли.


Для ще кращого керування дивними іменами (включаючи імена, які закінчуються пробілом та / або включають лінійки), використовуйте find ... -print0таwhile IFS="" read -r -d $'\000' dir
Гордон Девіссон

@GordonDavisson, ... дійсно, я навіть заперечую, що -d ''це менш оману щодо синтаксису та можливостей bash, оскільки -d $'\000'має на увазі (помилково), який $'\000'якимось чином відрізняється від ''- дійсно, можна було легко (і знову ж, помилково) зробити висновок про це, що bash підтримує рядки в стилі Pascal (вказані довжиною, здатні містити літерали NUL), а не рядки C (обмежено NUL, не може містити NUL).
Чарльз Даффі

5

Ви можете спробувати:

#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/

for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
  cd "$f"
  <your job here>
done

чи подібне ...

Пояснення:

find . -maxdepth 1 -mindepth 1 -type d: Знайдіть лише каталоги з максимальною рекурсивною глибиною 1 (лише підкаталоги $ 1) та мінімальною глибиною 1 (виключаючи поточну папку .)


Це баггі - спробуйте назву каталогу з пробілами. Див. Розділ BashPitfalls №1 та DontReadLinesWithFor .
Чарльз Даффі

Ім'я каталогів з пробілами укладено в лапки і тому працює, і OP не намагається читати рядки з файлу.
Генрі Добсон

він працює в cd "$f". Він не працює, коли вихідні дані з findрозділених рядків, тому ви будете мати окремі фрагменти імені як окремі значення $f, завдяки чому ви робите або не цитуєте $fсуперечливий аргумент розширення.
Чарльз Даффі

Я не сказав , що вони були намагаються читати рядки з файлу. findВихідні дані орієнтовані на рядки (теоретично одне ім'я для рядка, але див. нижче) із -printдією за замовчуванням .
Чарльз Даффі

Вихід, орієнтований на рядки, як з find -print- це не безпечний спосіб передавати довільні назви файлів, оскільки можна запустити щось mkdir -p $'foo\n/etc/passwd\nbar'і отримати каталог, що має /etc/passwdв своєму імені окремий рядок. Поводження з іменами з файлів /uploadабо /tmpкаталогів без обережності - це чудовий спосіб отримати атаку ескалації привілеїв.
Чарльз Даффі

4

прийнята відповідь буде розбита на пробіли, якщо вони мають імена каталогів, а кращий синтаксис - $()для bash / ksh. Використовуйте GNU find -exec варіант з +;напр

find .... -exec mycommand +; #this is same as passing to xargs

або скористайтеся циклом

find .... |  while read -r D
do
  ...
done 

який парам буде містити ім'я каталогу? наприклад, chmod + x $ DIR_NAME (так, я знаю, що є параметр chmod лише для каталогів)
Mike Graf

Існує одна тонка різниця між find -execі переходом до xargs: findбуде ігнорувати значення виходу виконуваної команди, а xargsне вдасться вийти з ненульового виходу. Це може бути правильним, залежно від ваших потреб.
Jason Wodicka

find ... -print0 | while IFS= read -r dє безпечнішим - підтримує імена, які починаються або закінчуються в пробілі, і імена, що містять літерали нового рядка.
Чарльз Даффі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.