Jak przejść do każdego katalogu i wykonać polecenie?


143

Jak napisać skrypt bash, który przechodzi przez każdego katalogu wewnątrz parent_directory i wykonuje się polecenia w każdym katalogu .

Struktura katalogów jest następująca:

katalog_nadrzędny (nazwa może być dowolna - nie podąża za wzorcem)

  • 001 (nazwy katalogów są zgodne z tym wzorcem)
    • 0001.txt (nazwy plików są zgodne z tym wzorcem)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

liczba katalogów jest nieznana.

Odpowiedzi:


108

Możesz wykonać następujące czynności, gdy Twój bieżący katalog to parent_directory:

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

(I )tworzyć podpowłoce, więc katalog bieżący nie jest zmieniany w głównym skrypcie.


5
Nie ma to znaczenia, ponieważ symbol wieloznaczny nie pasuje do niczego innego niż liczby, ale w ogólnym przypadku w zasadzie zawsze chcesz umieścić nazwę katalogu w podwójnych cudzysłowach wewnątrz pętli. cd "$d"byłoby lepsze, ponieważ przenosi się do sytuacji, w których symbol wieloznaczny pasuje do plików, których nazwy zawierają białe spacje i / lub metaznaki powłoki.
— tripleee

1
(Wszystkie odpowiedzi tutaj wydają się mieć tę samą wadę, ale ma to największe znaczenie w przypadku odpowiedzi z największą liczbą głosów).
— tripleee

1
Ponadto zdecydowanej większości poleceń tak naprawdę nie obchodzi, w którym katalogu je wykonujesz. for f in foo bar; do cat "$f"/*.txt; done >outputjest funkcjonalnie równoważny z cat foo/*.txt bar/*.txt >output. Jednak lsjest jedno polecenie, które daje nieco inny wynik w zależności od tego, jak przekazujesz mu argumenty; podobnie, a greplub, wcktóry wypisuje względną nazwę pliku, będzie inny, jeśli uruchomisz go z podkatalogu (ale często chcesz uniknąć wchodzenia do podkatalogu właśnie z tego powodu).
— tripleee

158

Ta odpowiedź wysłana przez Todda pomogła mi.

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

W \( ! -name . \) unika wykonywania polecenia w bieżącym katalogu.


4
A jeśli chcesz uruchomić polecenie tylko w określonych folderach:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
— Martin K

3
Musiałem to zrobić, -exec bash -c 'cd "$0" && pwd' {} \;ponieważ niektóre katalogi zawierały pojedyncze cudzysłowy, a niektóre podwójne cudzysłowy.
— Charlie Gorichanaz

12
Możesz również pominąć bieżący katalog, dodając-mindepth 1
— mdziob

Jak zmienić to na funkcję akceptującą polecenie, takie jak "git remote get-url origin` zamiast pwdbezpośrednio?
— roachsinai

disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}nie działa.
— roachsinai

48

Jeśli używasz GNU find, możesz wypróbować -execdirparametr, np:

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

lub (zgodnie z komentarzem @gniourf_gniourf ):

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

Uwaga: można użyć ${0#./}zamiast $0mocowania ./z przodu.

lub bardziej praktyczny przykład:

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

Jeśli chcesz dołączyć bieżący katalog, jest to jeszcze prostsze, używając -exec:

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

lub używając xargs:

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

Lub podobny przykład sugerowany przez @gniourf_gniourf :

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

Powyższe przykłady obsługują katalogi ze spacjami w nazwie.


Lub przypisując do tablicy bash:

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

Zmień .na określoną nazwę folderu. Jeśli nie potrzebujesz uruchamiać rekurencyjnie, możesz użyć: dirs=(*)zamiast. Powyższy przykład nie obsługuje katalogów ze spacjami w nazwie.

Tak więc, jak zasugerował @gniourf_gniourf , jedyny właściwy sposób umieszczenia wyniku find w tablicy bez użycia jawnej pętli będzie dostępny w Bash 4.4 z:

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

Lub nie jest to zalecany sposób (który obejmuje analizęls ):

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

Powyższy przykład zignorowałby bieżący katalog (zgodnie z żądaniem OP), ale zepsuje nazwy ze spacjami.

Zobacz też:


1
Jedyny poprawny sposób umieszczenia wyniku findw tablicy bez użycia jawnej pętli będzie dostępny w Bash 4.4 z mapfile -t -d '' dirs < <(find . -type d -print0). Do tego czasu jedyną opcją jest użycie pętli (lub odroczenie operacji find).
— gniourf_gniourf

1
Tak naprawdę dirstablica nie jest potrzebna ; mógłbyś pętla na find„wyjście S (bezpiecznie) tak: find . -type d -print0 | while IFS= read -r -d '' file; do ...; done.
— gniourf_gniourf

@gniourf_gniourf Jestem wizualny i zawsze staram się znaleźć / uczynić rzeczy, które wyglądają na proste. Np. Mam ten skrypt i dzięki zastosowaniu tablicy bash jest dla mnie łatwe i czytelne (poprzez rozdzielenie logiki na mniejsze części, zamiast używania potoków). Wprowadzenie dodatkowych rur, IFS, read, daje wrażenie dodatkowej złożoności. Muszę się nad tym zastanowić, może jest na to prostszy sposób.
— kenorb

Jeśli przez łatwiejszy masz na myśli zepsuty, to tak, są łatwiejsze sposoby. A teraz nie martw się, wszystko to IFSi read(jak mówisz) stają się drugą naturą, gdy się do nich przyzwyczaisz… są częścią kanonicznych i idiomatycznych sposobów pisania scenariuszy.
— gniourf_gniourf

Nawiasem mówiąc, twoje pierwsze polecenie find . -type d -execdir echo $(pwd)/{} ';'nie robi tego, co chcesz ( $(pwd)jest rozwijane, zanim findzostanie nawet wykonane)…
— gniourf_gniourf

29

Możesz to osiągnąć przez orurowanie, a następnie za pomocą xargs. Chwyt polega na tym, że musisz użyć -Iflagi, która zamieni podciąg w twoim poleceniu bash na podciąg przekazany przez każdy z xargs.

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

Możesz zamienić na pwddowolne polecenie, które chcesz wykonać w każdym katalogu.


2
Nie wiem, dlaczego nie zostało to przyjęte, ale najprostszy skrypt, jaki do tej pory znalazłem. Dzięki
— Linvi,

20

Jeśli znany jest folder najwyższego poziomu, możesz po prostu napisać coś takiego:

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

Na $ (GRAJ JAK TYLKO CHCESZ); możesz umieścić tyle kodu, ile chcesz.

Zauważ, że nie "cd" w żadnym katalogu.

Twoje zdrowie,


3
Jest tutaj kilka przypadków „bezużytecznego wykorzystania ls” - możesz po prostu to zrobić for dir in $YOUR_TOP_LEVEL_FOLDER/*.
— Mark Longair,

1
No cóż, może jest to ogólnie bezużyteczne, ale pozwala na filtrowanie bezpośrednio w ls (czyli wszystkie katalogi zakończone .tmp). Dlatego użyłem ls $dirwyrażenia
— gforcada

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

I to jest bardzo łatwe do zrozumienia!
— Rbjz,

6

Podczas gdy jeden liner jest dobry do szybkiego i brudnego użycia, wolę poniżej bardziej rozwlekłą wersję do pisania skryptów. To jest szablon, którego używam, który zajmuje się wieloma skrajnymi przypadkami i umożliwia pisanie bardziej złożonego kodu do wykonania w folderze. Możesz napisać swój kod basha w funkcji dir_command. Poniżej dir_coomand implementuje tagowanie każdego repozytorium w git jako przykład. Reszta skryptu wywołuje dir_command dla każdego folderu w katalogu. Dołączono również przykład iteracji po tylko podanym zestawie folderów.

#!/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

Nie rozumiem, o co chodzi z formatowaniem pliku, ponieważ chcesz tylko iterować po folderach ... Szukasz czegoś takiego?

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

4

możesz użyć

find .

aby przeszukać wszystkie pliki / katalogi w bieżącym katalogu cyklicznie

Następnie możesz potokować wyjście polecenie xargs w ten sposób

find . | xargs 'command here'

3
Nie o to pytano.
— 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

Będziesz musiał ponownie zmienić kopię zapasową katalogu lub zrobić to cdw podpowłoce.
— Mark Longair,

Głos przeciw: edycja przegrała, cdwięc ten kod nie robi nic pożytecznego.
— tripleee
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.