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


152

Как я пишу Баш скрипт , который проходит через каждый каталог внутри parent_directory и выполняет в команду в каждом каталоге .

Структура каталогов следующая:

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

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

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

Ответы:


110

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

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

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


5
Здесь это не имеет значения, потому что подстановочный знак не соответствует ничему, кроме чисел, но в общем случае вы всегда хотите поместить имя каталога в двойные кавычки внутри цикла. cd "$d"было бы лучше, если бы он переносился в ситуации, когда подстановочный знак действительно соответствует файлам, имена которых содержат пробелы и / или метасимволы оболочки.
— Tripleee

1
(Кажется, что все ответы здесь имеют один и тот же недостаток, но он имеет наибольшее значение в ответе, получившем наибольшее количество голосов.)
— tripleee

1
Кроме того, подавляющему большинству команд на самом деле все равно, в каком каталоге вы их выполняете. for f in foo bar; do cat "$f"/*.txt; done >outputфункционально эквивалентен cat foo/*.txt bar/*.txt >output. Тем не менее, lsэто одна команда, которая выводит немного разные результаты в зависимости от того, как вы передаете ей аргументы; аналогично, grepили, wcкоторый выводит относительное имя файла, будет другим, если вы запустите его из подкаталога (но часто вы хотите избежать входа в подкаталог именно по этой причине).
— tripleee

168

Этот ответ, опубликованный Тоддом, мне помог.

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' {} \;поскольку некоторые каталоги содержали одинарные и некоторые двойные кавычки.
— Чарли Горичаназ

13
Вы также можете опустить текущий каталог, добавив-mindepth 1
— mdziob

Как превратить это в функцию, принимающую команду типа «git remote get-url origin», а не pwdнапрямую?
— roachsinai

disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}не работает.
— roachsinai

50

Если вы используете 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).
— gniourf_gniourf

1
Фактически, вам действительно не нужен dirsмассив; вы могли бы петля на findвыходе «S (безопасно) , так как: find . -type d -print0 | while IFS= read -r -d '' file; do ...; done.
— gniourf_gniourf

@gniourf_gniourf Я визуальный и всегда пытаюсь найти / сделать вещи, которые выглядят простыми. Например, у меня есть этот сценарий, и с помощью массива bash я легко и легко могу читать (разделив логику на более мелкие части вместо использования каналов). Вводя дополнительные трубы, IFS, read, это создает впечатление дополнительной сложности. Придется подумать, может есть способ попроще.
— kenorb 04

Если под « проще» вы имеете в виду сломанный, то да, есть более простые способы сделать это. Теперь не волнуйтесь, все это IFSи read(как вы говорите) стать второй натурой , как вы привыкнете к ним ... они являются частью канонического и идиоматических способов написания сценариев.
— gniourf_gniourf

Между прочим, ваша первая команда find . -type d -execdir echo $(pwd)/{} ';'не делает то, что вы хотите ( $(pwd)расширяется до того, findкак даже выполняется)…
— gniourf_gniourf

33

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

ls -d */ | xargs -I {} bash -c "cd '{}' && pwd"

Вы можете заменить pwdлюбую команду, которую хотите выполнить в каждом каталоге.


4
Не знаю, почему за него не проголосовали, но это самый простой сценарий, который я нашел до сих пор. Спасибо
— Linvi

20

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

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выражение
— gforcada

14
for dir in PARENT/*
do
  test -d "$dir" || continue
  # Do something with $dir...
done

И это очень легко понять!
— Rbjz

6

Хотя один лайнер хорош для быстрого и грязного использования, я предпочитаю ниже более подробную версию для написания скриптов. Это шаблон, который я использую, он заботится о многих крайних случаях и позволяет вам писать более сложный код для выполнения в папке. Вы можете написать свой код на bash в функции dir_command. Ниже dir_coomand в качестве примера реализует пометку каждого репозитория в git. Остальная часть скрипта вызывает 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"

5

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

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

4

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

find .

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

Затем вы можете передать вывод команде xargs следующим образом

find . | xargs 'command here'

3
Это не то, о чем спрашивали.
— kenorb 04

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

Вам нужно будет снова изменить резервную копию каталога или сделать это cdв подоболочке.
— Марк Лонгэр

Голос против: редактирование потеряно, cdпоэтому этот код фактически не делает ничего полезного.
— Tripleee
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.