Выполните действие в каждом подкаталоге, используя 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.