Bagaimana cara pergi ke setiap direktori dan menjalankan perintah?


143

Bagaimana cara menulis script bash yang melewati setiap direktori di dalam parent_directory dan mengeksekusi sebuah perintah di setiap direktori .

Struktur direktori adalah sebagai berikut:

parent_directory (nama bisa berupa apa saja - tidak mengikuti pola)

  • 001 (nama direktori mengikuti pola ini)
    • 0001.txt (nama file mengikuti pola ini)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

jumlah direktori tidak diketahui.

Jawaban:


108

Anda dapat melakukan hal berikut, ketika direktori Anda saat ini adalah parent_directory:

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

The (dan )membuat subkulit, sehingga direktori saat ini tidak berubah dalam naskah utama.


5
Tidak masalah di sini karena wildcard tidak cocok dengan apa pun selain angka, tetapi dalam kasus umum, Anda pada dasarnya selalu ingin memasukkan nama direktori dalam tanda kutip ganda di dalam loop. cd "$d"akan lebih baik karena transfer ke situasi di mana wildcard tidak cocok dengan file yang namanya berisi meta karakter spasi dan / atau shell.
tripleee

1
(Semua jawaban di sini tampaknya memiliki kelemahan yang sama, tetapi yang paling penting adalah jawaban teratas.)
tripleee

1
Juga, sebagian besar perintah tidak benar-benar peduli di direktori mana Anda menjalankannya. for f in foo bar; do cat "$f"/*.txt; done >outputsecara fungsional setara dengan cat foo/*.txt bar/*.txt >output. Namun, lsadalah satu perintah yang memang menghasilkan output yang sedikit berbeda tergantung pada bagaimana Anda menyampaikannya argumen; demikian pula, grepatau wcyang menghasilkan nama file relatif akan berbeda jika Anda menjalankannya dari subdirektori (tetapi sering kali, Anda ingin menghindari masuk ke subdirektori justru karena alasan itu).
tripleee

158

Jawaban ini diposting oleh Todd membantu saya.

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

The \( ! -name . \) Menghindari mengeksekusi perintah di direktori saat ini.


4
Dan jika Anda hanya ingin menjalankan perintah di folder tertentu:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
Martin K

3
Saya harus melakukannya -exec bash -c 'cd "$0" && pwd' {} \;karena beberapa direktori berisi tanda kutip tunggal dan beberapa tanda kutip ganda.
Charlie Gorichanaz

12
Anda juga dapat menghilangkan direktori saat ini dengan menambahkan-mindepth 1
mdziob

Bagaimana cara mengubahnya ke suatu fungsi menerima perintah seperti "git remote get-url origin` alih-alih pwdsecara langsung?
roachsinai

disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}tidak bekerja.
roachsinai

48

Jika Anda menggunakan GNU find, Anda dapat mencoba -execdirparameter, misalnya:

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

atau (sesuai komentar @gniourf_gniourf ):

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

Catatan: Anda bisa menggunakan ${0#./}alih-alih $0memperbaiki ./di depan.

atau contoh yang lebih praktis:

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

Jika Anda ingin memasukkan direktori saat ini, itu bahkan lebih sederhana dengan menggunakan -exec:

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

atau menggunakan xargs:

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

Atau contoh serupa yang disarankan oleh @gniourf_gniourf :

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

Contoh di atas mendukung direktori dengan spasi di namanya.


Atau dengan menetapkan ke dalam bash array:

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

Ubah .nama folder spesifik Anda. Jika Anda tidak perlu menjalankan secara rekursif, Anda dapat menggunakan: dirs=(*)sebagai gantinya. Contoh di atas tidak mendukung direktori dengan spasi di namanya.

Jadi seperti yang disarankan @gniourf_gniourf , satu-satunya cara yang tepat untuk meletakkan output find dalam array tanpa menggunakan loop eksplisit akan tersedia di Bash 4.4 dengan:

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

Atau bukan cara yang disarankan (yang melibatkan penguraianls ):

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

Contoh di atas akan mengabaikan dir saat ini (seperti yang diminta oleh OP), tetapi itu akan merusak nama dengan spasi.

Lihat juga:


1
Satu-satunya cara yang tepat untuk menempatkan output finddalam array tanpa menggunakan loop eksplisit akan tersedia di Bash 4.4 dengan mapfile -t -d '' dirs < <(find . -type d -print0). Sampai saat itu, satu-satunya pilihan Anda adalah menggunakan loop (atau menunda operasi untuk find).
gniourf_gniourf

1
Sebenarnya, Anda tidak benar-benar membutuhkan dirsarray; Anda bisa loop pada findkeluaran 's (aman) seperti: find . -type d -print0 | while IFS= read -r -d '' file; do ...; done.
gniourf_gniourf

@ gniourf_gniourf Saya visual dan selalu berusaha menemukan / membuat hal-hal yang terlihat sederhana. Misal saya sudah skrip ini dan dengan menggunakan bash array mudah dan dapat dibaca untuk saya (dengan memisahkan logika menjadi potongan-potongan kecil, daripada menggunakan pipa). Memperkenalkan pipa tambahan, IFS, read, memberikan kesan kompleksitas tambahan. Saya harus memikirkannya, mungkin ada beberapa cara yang lebih mudah untuk melakukannya.
kenorb

Jika dengan mudah Anda maksudkan rusak maka ya, ada cara yang lebih mudah untuk dilakukan. Sekarang jangan khawatir, semua ini IFSdan read(seperti yang Anda katakan) menjadi sifat kedua ketika Anda terbiasa dengan mereka ... mereka adalah bagian dari cara kanonik dan idiomatik cara menulis skrip.
gniourf_gniourf

Omong-omong, perintah pertama find . -type d -execdir echo $(pwd)/{} ';'Anda tidak melakukan apa yang Anda inginkan ( $(pwd)diperluas sebelum findbahkan dieksekusi) ...
gniourf_gniourf

29

Anda dapat mencapai ini dengan memipis dan kemudian menggunakan xargs. Tangkapannya adalah Anda harus menggunakan -Iflag yang akan menggantikan substring di perintah bash Anda dengan substring yang dilewati oleh masing-masing xargs.

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

Anda mungkin ingin mengganti pwddengan perintah apa pun yang ingin Anda jalankan di setiap direktori.


2
Saya tidak tahu mengapa ini belum di-upgrade, tetapi skrip paling sederhana yang saya temukan sejauh ini. Terima kasih
Linvi

20

Jika folder tingkat atas diketahui, Anda bisa menulis sesuatu seperti ini:

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

Pada $ (MAINKAN BANYAK YANG ANDA INGINKAN); Anda dapat memasukkan kode sebanyak yang Anda inginkan.

Perhatikan bahwa saya tidak "cd" pada direktori apa pun.

Bersulang,


3
Ada beberapa contoh "Penggunaan Tidak Berguna ls" di sini - Anda bisa melakukannya for dir in $YOUR_TOP_LEVEL_FOLDER/*saja.
Mark Longair

1
Yah, mungkin itu tidak berguna dalam arti umum tetapi memungkinkan untuk melakukan penyaringan langsung di ls (yaitu semua direktori berakhir dengan .tmp). Itu sebabnya saya menggunakan ls $dirungkapan
gforcada

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

Dan ini sangat mudah dipahami!
Rbjz

6

Sementara satu liner bagus untuk penggunaan cepat dan kotor, saya lebih suka versi verbose di bawah ini untuk menulis skrip. Ini adalah template yang saya gunakan yang menangani banyak kasus tepi dan memungkinkan Anda untuk menulis kode yang lebih kompleks untuk dieksekusi pada folder. Anda dapat menulis kode bash Anda di fungsi dir_command. Di bawah ini, dir_coomand mengimplementasikan pemberian tag pada setiap repositori di git sebagai contoh. Sisa dari skrip memanggil dir_command untuk setiap folder dalam direktori. Contoh iterasi melalui hanya set folder yang diberikan juga termasuk.

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

Saya tidak mengerti dengan format file, karena Anda hanya ingin beralih melalui folder ... Apakah Anda mencari sesuatu seperti ini?

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

4

kamu bisa memakai

find .

untuk mencari semua file / direktori dalam direktori saat ini secara rekursif

Daripada Anda bisa mem-pipe output, perintah xargs seperti itu

find . | xargs 'command here'

3
Ini bukan yang diminta.
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

Anda perlu mengubah cadangan direktori lagi, atau melakukan cdsubshell.
Mark Longair

Downvote: hasil edit hilang cdsehingga kode ini tidak benar-benar melakukan sesuatu yang bermanfaat.
tripleee
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.