Выполните действие в каждом подкаталоге, используя Bash


196

Я работаю над сценарием, который должен выполнить действие в каждом подкаталоге определенной папки.

Какой самый эффективный способ написать это?


1
Пожалуйста , обратите внимание , возвращаясь через и пересматривают ответы на корректность - у вас есть общепринятый ответ получает много мнений , несмотря на серьезные ошибки (F / е, запустив его через каталог , в котором кто - то ранее запускало 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
Приведенный выше ответ также дал мне каталог self, так что следующее сработало для меня немного лучше: 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
Алекс Рейкинг

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

109

Самый простой нерекурсивный способ:

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

В /конце говорит, использовать только каталоги.

Там нет необходимости

  • найти
  • AWK
  • ...

11
Примечание: это не будет включать в себя точечные каталоги (что может быть хорошо, но важно знать).
Висбуки

Полезно отметить, что вы можете использовать shopt -s dotglobдля добавления файлов точек / точек при расширении подстановочных знаков. См. Также: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Стин Шютт,

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

2
@Shule /*был бы для абсолютного пути, тогда как */включал бы подкаталоги от текущего местоположения
Дэн G

3
полезный совет: если вам нужно урезать трейлинг '/' от $ d, используйте${d%/*}
Hafthor

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"; }

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


Дополнительные примеры см .: Как перейти в каждый каталог и выполнить команду? в СО


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 22.10.10

@ Майк, вряд ли. $? скорее всего, вы получите статус команды find или 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действием по умолчанию .
Чарльз Даффи

Линейно-ориентированный вывод, так как from 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

или используйте цикл while

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

какой параметр будет содержать имя каталога? например, chmod + x $ DIR_NAME (да, я знаю, что есть опция chmod только для каталогов)
Майк Граф

Существует одно тонкое различие между find -execпередачей и передачей xargs: findигнорирует значение выхода выполняемой команды, но при xargsненулевом выходе завершится неудачей. Любой из них может быть правильным, в зависимости от ваших потребностей.
Джейсон Водичка

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