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


143

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

Структура каталогу така:

parent_directory (ім'я може бути чим завгодно - не слід за шаблоном)

  • 001 (назви каталогів відповідають цій схемі)
    • 0001.txt (назви файлів відповідають цій схемі)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

кількість каталогів невідома.

Відповіді:


108

Ви можете зробити наступне, коли ваш поточний каталог parent_directory:

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

(І )створити подоболочкі, тому поточний каталог не змінюється в основному скрипті.


5
Тут не має значення, тому що підстановка не відповідає нічого іншого, крім чисел, але в загальному випадку ви завжди завжди бажаєте вводити ім'я каталогу в подвійні лапки всередині циклу. cd "$d"було б краще, оскільки він передає ситуацію, коли підстановочний код відповідає файлам, імена яких містять пробіли та / або метахарактеристики оболонки.
трійчатка

1
(Усі відповіді тут, мабуть, мають однаковий недолік, але це найбільше значення має у відповіді, що голосує вище.)
tripleee

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

158

Ця відповідь, опублікована Тоддом, допомогла мені.

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" \;
Martin K

3
Мені довелося це зробити, -exec bash -c 'cd "$0" && pwd' {} \;оскільки деякі каталоги містили одиничні цитати та деякі подвійні цитати.
Чарлі Горічаназ

12
Ви також можете опустити поточний каталог, додавши-mindepth 1
mdziob

Як змінити цю функцію, прийняти таку команду, як "git remote get-url origin", а не pwdбезпосередньо?
roachsinai

disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}не працює.
roachsinai

48

Якщо ви використовуєте 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 запропонував, єдиний правильний спосіб розмістити вихід знаходження в масиві без використання явного циклу буде доступний у 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'

Вищенаведений приклад ігнорує поточний dir (як вимагає ОП), але він порушить імена з пробілами.

Дивитися також:


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

Якщо легше ви маєте на увазі ламане, то так, є простіші способи зробити. Тепер не хвилюйтеся, все це IFSі read(як ви кажете) стають другою природою, коли ви звикаєте до них ... вони є частиною канонічних та ідіоматичних способів написання сценаріїв.
gniourf_gniourf

До речі, ваша перша команда find . -type d -execdir echo $(pwd)/{} ';'не робить те, що ви хочете ( $(pwd)розгорнуте раніше find, навіть виконане)…
gniourf_gniourf

29

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

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

Ви можете замінити pwdбудь-яку команду, яку ви хочете виконати у кожному каталозі.


2
Я не знаю, чому це не було схвалено, але найпростіший сценарій я знайшов досі. Спасибі
Лінві

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


6

Хоча один вкладиш хороший для швидкого та брудного використання, я віддаю перевагу нижче більш детальної версії для написання сценаріїв. Це шаблон, який я використовую, який опікується багатьма крайовими справами і дозволяє писати складніший код для виконання у папці. Ви можете написати свій баш-код у функції 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 .

для пошуку всіх файлів / dirs у поточному каталозі відновлюється

Тоді як ви можете передати висновок команди xargs так

find . | xargs 'command here'

3
Це не те, про що питали.
kenorb

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в нижній частині.
Марк Лонгейр

Downvote: редагування втрачено, cdтому цей код насправді не робить нічого корисного.
трійка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.