Как перейти в каждый каталог и выполнить команду?

117

Как написать bash script, который проходит через каждый каталог внутри родительского_каталога, и выполняет команду в каждой директории.

Структура каталогов выглядит следующим образом:

parent_directory (имя может быть любым - не следует шаблону)

  • 001 (имена каталогов следуют этому шаблону)
    • 0001.txt(имена файлов следуют этому шаблону)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

количество каталогов неизвестно.

Теги:
find

10 ответов

88
Лучший ответ

Вы можете сделать следующее, когда вашим текущим каталогом является parent_directory:

for d in [0-9][0-9][0-9]
do
    ( cd "$d" && your-command-here )
done

( И ) создает подоболочку, поэтому текущий каталог не изменяется в основном скрипте.

  • 3
    Здесь это не имеет значения, потому что подстановочный знак не совпадает ни с чем, кроме чисел, но в общем случае вы в основном всегда хотите поместить имя каталога в двойные кавычки внутри цикла. cd "$d" было бы лучше в том смысле, что он переносится в ситуации, когда подстановочный знак совпадает с файлами, имена которых содержат пробелы и / или метасимволы оболочки.
  • 0
    (Кажется, что все ответы здесь имеют один и тот же недостаток, но это имеет наибольшее значение в ответе с наибольшим количеством голосов.)
Показать ещё 1 комментарий
115

Этот ответ, отправленный Todd, помог мне.

find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;

\( ! -name . \) избегает выполнения команды в текущем каталоге.

  • 4
    А если вы хотите запустить команду только в определенных папках: find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
  • 3
    Я должен был сделать -exec bash -c 'cd "$0" && pwd' {} \; поскольку некоторые каталоги содержали одинарные кавычки и некоторые двойные кавычки.
Показать ещё 3 комментария
34

Если вы используете GNU find, вы можете попробовать параметр -execdir, например:

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

или (согласно комментарий @gniourf_gniourf):

find . -type d -execdir sh -c 'printf "%s/%s\n" "$PWD" "$0"' {} \;

Примечание. Вместо $0 можно использовать ${0#./} для исправления ./.

или более практический пример:

find . -name .git -type d -execdir git pull -v ';'

Если вы хотите включить текущий каталог, это еще проще, используя -exec:

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

или используя xargs:

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

Или аналогичный пример, предложенный @gniourf_gniourf:

find . -type d -print0 | while IFS= read -r -d '' file; do
# ...
done

Вышеприведенные примеры поддерживают каталоги с пробелами в их имени.


Или путем назначения в массив bash:

dirs=($(find . -type d))
for dir in "${dirs[@]}"; do
  cd "$dir"
  echo $PWD
done

Измените . на ваше имя конкретной папки. Если вам не нужно запускать рекурсивно, вы можете использовать: dirs=(*). В приведенном выше примере не поддерживаются каталоги с пробелами в имени.

Так как @gniourf_gniourf предположил, что единственный правильный способ поместить вывод find в массив без использования явного цикла будет доступен в bash 4.4 с

mapfile -t -d '' dirs < <(find . -type d -print0)

Или не рекомендуется (в том числе синтаксический анализ ls):

ls -d */ | awk '{print $NF}' | xargs -n1 sh -c 'cd $0 && pwd && echo Do stuff'

В приведенном выше примере будет игнорироваться текущий каталог (по запросу OP), но он будет разбивать имена на пробелы.

См. также:

  • 1
    Единственный правильный способ поместить вывод find в массив без использования явного цикла будет доступен в Bash 4.4 с mapfile -t -d '' dirs < <(find . -type d -print0) . До тех пор ваш единственный вариант - использовать цикл (или отложить операцию, чтобы find ).
  • 1
    На самом деле, вам не нужен массив dirs ; Вы можете циклически обработать вывод find (безопасно) следующим образом: find . -type d -print0 | while IFS= read -r -d '' file; do ...; done
Показать ещё 4 комментария
18

Если папка верхнего уровня известна, вы можете просто написать что-то вроде этого:

for dir in `ls $YOUR_TOP_LEVEL_FOLDER`;
do
    for subdir in `ls $YOUR_TOP_LEVEL_FOLDER/$dir`;
    do
      $(PLAY AS MUCH AS YOU WANT);
    done
done

На $(ИГРАТЬ, КАК ВЫ ХОТИТЕ); вы можете разместить столько кода, сколько хотите.

Обратите внимание, что я не "cd" в любом каталоге.

Приветствия,

  • 3
    Здесь есть несколько примеров «Бесполезного использования ls » - вместо этого вы можете просто сделать for dir in $YOUR_TOP_LEVEL_FOLDER/* .
  • 1
    Ну, может быть, в общем смысле это бесполезно, но позволяет выполнять фильтрацию непосредственно в ls (т. Е. Все каталоги заканчиваются на .tmp). Вот почему я использовал выражение ls $dir
10
for dir in PARENT/*
do
  test -d "$dir" || continue
  # Do something with $dir...
done
  • 1
    +1 за упоминание 'test -d'. Помог мне.
  • 0
    И это очень легко понять!
5

В то время как один лайнер хорош для быстрого и грязного использования, я предпочитаю ниже более подробные версии для написания сценариев. Это шаблон, который я использую, который заботится о многих случаях и позволяет писать более сложный код для выполнения в папке. Вы можете написать свой bash код в функции dir_command. Ниже dir_coomand реализует пометку каждого репозитория в git в качестве примера. Остальная часть script вызывает команду dir_command для каждой папки в каталоге. Пример итерации через только указанный набор папок также включает.

#!/bin/bash

#Use set -x if you want to echo each command while getting executed
#set -x

#Save current directory so we can restore it later
cur=$PWD
#Save command line arguments so functions can access it
args=("$@")

#Put your code in this function
#To access command line arguments use syntax ${args[1]} etc
function dir_command {
    #This example command implements doing git status for folder
    cd $1
    echo "$(tput setaf 2)$1$(tput sgr 0)"
    git tag -a ${args[0]} -m "${args[1]}"
    git push --tags
    cd ..
}

#This loop will go to each immediate child and execute dir_command
find . -maxdepth 1 -type d \( ! -name . \) | while read dir; do
   dir_command "$dir/"
done

#This example loop only loops through give set of folders    
declare -a dirs=("dir1" "dir2" "dir3")
for dir in "${dirs[@]}"; do
    dir_command "$dir/"
done

#Restore the folder
cd "$cur"
3

Я не понимаю смысла в создании файла, так как вы хотите только итерации по папкам... Вы ищете что-то вроде этого?

cd parent
find . -type d | while read d; do
   ls $d/
done
3

вы можете использовать

find .

для поиска всех файлов /dirs в текущей рекурсивной директории

Чем вы можете вывести на выходе команду xargs так

find . | xargs 'command here'
  • 3
    Это не то, что спросили.
0

Вы можете добиться этого, используя трубопровод и затем используя xargs. -I что вам нужно использовать флаг -I который заменит подстроку в вашей команде bash на подстроку, переданную каждым из xargs.

ls -d */ | xargs -I {} bash -c "cd '{}' && pwd"
  • 0
    Я не знаю, почему за это не проголосовали, но самый простой сценарий, который я нашел до сих пор. Спасибо
  • 0
    Добро пожаловать !
0
for p in [0-9][0-9][0-9];do
    (
        cd $p
        for f in [0-9][0-9][0-9][0-9]*.txt;do
            ls $f; # Your operands
        done
    )
done
  • 0
    Вам нужно снова изменить резервную копию каталога или выполнить cd в подоболочке.
  • 0
    Спасибо за исправления, Марк. Subshell отлично подходит здесь.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню