Làm thế nào để đi đến từng thư mục và thực hiện một lệnh?


143

Làm cách nào để tôi viết một tập lệnh bash đi qua từng thư mục bên trong Parent_directory và thực thi một lệnh trong mỗi thư mục .

Cấu trúc thư mục như sau:

Parent_directory (tên có thể là bất cứ điều gì - không theo một mẫu)

  • 001 (tên thư mục theo mẫu này)
    • 0001.txt (tên tệp theo mẫu này)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

số lượng thư mục không rõ.

Câu trả lời:


108

Bạn có thể làm như sau, khi thư mục hiện tại của bạn là parent_directory:

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

Việc tạo ()tạo một lớp con, vì vậy thư mục hiện tại không thay đổi trong tập lệnh chính.


5
Ở đây không có vấn đề gì vì ký tự đại diện không khớp với bất kỳ thứ gì khác ngoài số, nhưng trong trường hợp chung, về cơ bản, bạn luôn muốn đặt tên thư mục trong dấu ngoặc kép bên trong vòng lặp. cd "$d"sẽ tốt hơn ở chỗ nó chuyển đến các tình huống trong đó ký tự đại diện khớp với các tệp có tên chứa khoảng trắng và / hoặc siêu ký tự shell.
tripleee

1
(Tất cả các câu trả lời ở đây dường như có cùng một lỗ hổng, nhưng nó quan trọng nhất trong câu trả lời được bình chọn hàng đầu.)
tripleee

1
Ngoài ra, phần lớn các lệnh không thực sự quan tâm đến thư mục nào bạn thực thi chúng. for f in foo bar; do cat "$f"/*.txt; done >outputlà chức năng tương đương với cat foo/*.txt bar/*.txt >output. Tuy nhiên, lslà một lệnh tạo ra đầu ra hơi khác nhau tùy thuộc vào cách bạn truyền đối số; tương tự, một grephoặc wcxuất ra một tên tệp tương đối sẽ khác nếu bạn chạy nó từ thư mục con (nhưng thông thường, bạn muốn tránh đi vào thư mục con chính xác vì lý do đó).
tripleee

158

Câu trả lời này được đăng bởi Todd đã giúp tôi.

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

Việc \( ! -name . \) tránh thực thi lệnh trong thư mục hiện tại.


4
Và nếu bạn chỉ muốn chạy lệnh trong một số thư mục nhất định:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
Martin K

3
Tôi đã phải làm -exec bash -c 'cd "$0" && pwd' {} \;vì một số thư mục chứa dấu ngoặc đơn và một số dấu ngoặc kép.
Charlie Gorichanaz

12
Bạn cũng có thể bỏ qua thư mục hiện tại bằng cách thêm-mindepth 1
mdziob

Làm cách nào để thay đổi hàm này thành hàm chấp nhận lệnh như "git remote get-url origin` thay vì pwdtrực tiếp?
roachsinai 14/2/19

disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}không làm việc.
roachsinai

48

Nếu bạn đang sử dụng GNU find, bạn có thể thử -execdirtham số, ví dụ:

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

hoặc (theo nhận xét @gniourf_gniourf ):

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

Lưu ý: Bạn có thể sử dụng ${0#./}thay vì $0để sửa ./ở phía trước.

hoặc ví dụ thực tế hơn:

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

Nếu bạn muốn bao gồm thư mục hiện tại, nó thậm chí còn đơn giản hơn bằng cách sử dụng -exec:

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

hoặc sử dụng xargs:

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

Hoặc ví dụ tương tự được đề xuất bởi @gniourf_gniourf :

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

Các ví dụ trên hỗ trợ các thư mục có khoảng trắng trong tên của chúng.


Hoặc bằng cách gán vào mảng bash:

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

Thay đổi .tên thư mục cụ thể của bạn. Nếu bạn không cần chạy đệ quy, bạn có thể sử dụng: dirs=(*)thay vào đó. Ví dụ trên không hỗ trợ các thư mục có khoảng trắng trong tên.

Vì vậy, như @gniourf_gniourf đã đề xuất, cách thích hợp duy nhất để đưa đầu ra của find vào một mảng mà không sử dụng một vòng lặp rõ ràng sẽ có sẵn trong Bash 4.4 với:

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

Hoặc không phải là một cách được đề xuất (liên quan đến phân tích cú phápls ):

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

Ví dụ trên sẽ bỏ qua thư mục hiện tại (theo yêu cầu của OP), nhưng nó sẽ phá vỡ các tên có khoảng trắng.

Xem thêm:


1
Cách thích hợp duy nhất để đặt đầu ra của findmột mảng mà không sử dụng vòng lặp rõ ràng sẽ có sẵn trong Bash 4.4 với mapfile -t -d '' dirs < <(find . -type d -print0). Cho đến lúc đó, tùy chọn duy nhất của bạn là sử dụng một vòng lặp (hoặc trì hoãn hoạt động để find).
gniourf_gniourf

1
Trong thực tế, bạn không thực sự cần dirsmảng; bạn có thể lặp trên findđầu ra (một cách an toàn) như vậy : find . -type d -print0 | while IFS= read -r -d '' file; do ...; done.
gniourf_gniourf

@gniourf_gniourf Tôi trực quan và luôn cố gắng tìm / làm cho mọi thứ trông đơn giản. Ví dụ: Tôi có tập lệnh này và bằng cách sử dụng mảng bash rất dễ đọc và dễ đọc (bằng cách tách logic thành các phần nhỏ hơn, thay vì sử dụng đường ống). Giới thiệu Ống bổ sung, IFS, read, nó mang lại ấn tượng về phức tạp thêm. Tôi sẽ phải suy nghĩ về nó, có lẽ có một cách dễ dàng hơn để làm điều đó.
kenorb

Nếu dễ dàng hơn bạn có nghĩa là bị hỏng thì có, có những cách dễ dàng hơn để làm. Bây giờ đừng lo lắng, tất cả những điều này IFSread(như bạn nói) trở thành bản chất thứ hai khi bạn quen với chúng, chúng là một phần của các cách viết kịch bản chính tắc và thành ngữ.
gniourf_gniourf

Nhân tiện, lệnh đầu tiên của bạn find . -type d -execdir echo $(pwd)/{} ';'không làm những gì bạn muốn ( $(pwd)được mở rộng trước findcả khi được thực thi),
gniourf_gniourf

29

Bạn có thể đạt được điều này bằng cách sử dụng đường ống và sau đó sử dụng xargs. Điều hấp dẫn là bạn cần sử dụng -Icờ sẽ thay thế chuỗi con trong lệnh bash của bạn bằng chuỗi con được truyền bởi mỗi chuỗi xargs.

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

Bạn có thể muốn thay thế pwdbằng bất kỳ lệnh nào bạn muốn thực hiện trong mỗi thư mục.


2
Tôi không biết tại sao điều này chưa được nêu lên, nhưng kịch bản đơn giản nhất tôi tìm thấy cho đến nay. Cảm ơn
Linvi

20

Nếu thư mục toplevel được biết đến, bạn có thể viết một cái gì đó như thế này:

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

Trên $ (CHƠI NHƯ RẤT NHIỀU BẠN MUỐN); bạn có thể đặt nhiều mã như bạn muốn.

Lưu ý rằng tôi đã không "cd" trên bất kỳ thư mục.

Chúc mừng


3
Có một vài trường hợp "Sử dụng vô dụng ls" ở đây - bạn chỉ có thể làm for dir in $YOUR_TOP_LEVEL_FOLDER/*thay thế.
Mark Longair

1
Vâng, có thể nó vô dụng theo nghĩa chung nhưng nó cho phép lọc trực tiếp trong ls (tức là tất cả các thư mục kết thúc bằng .tmp). Đó là lý do tại sao tôi sử dụng ls $dirbiểu thức
gforcada

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

Và điều này rất dễ hiểu!
Rbjz

6

Mặc dù một lớp lót tốt cho việc sử dụng nhanh và bẩn, tôi thích phiên bản dài hơn để viết kịch bản. Đây là mẫu tôi sử dụng để xử lý nhiều trường hợp cạnh và cho phép bạn viết mã phức tạp hơn để thực thi trên một thư mục. Bạn có thể viết mã bash của bạn trong hàm dir_command. Dưới đây, dir_coomand thực hiện gắn thẻ mỗi kho lưu trữ trong git làm ví dụ. Phần còn lại của tập lệnh gọi dir_command cho mỗi thư mục trong thư mục. Ví dụ về việc lặp qua chỉ bộ thư mục đã cho cũng được bao gồm.

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

Tôi không nhận được điểm với việc định dạng tệp, vì bạn chỉ muốn lặp qua các thư mục ... Bạn đang tìm kiếm một cái gì đó như thế này?

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

4

bạn có thể dùng

find .

để tìm kiếm tất cả các tập tin / thư mục trong thư mục hiện tại đệ quy

Hơn bạn có thể dẫn đầu ra lệnh xargs như vậy

find . | xargs 'command here'

3
Đây không phải là những gì đã được hỏi.
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ạn cần phải thay đổi sao lưu thư mục một lần nữa hoặc thực hiện cdtrong một lớp con.
Mark Longair

Downvote: chỉnh sửa bị mất cdvì vậy mã này không thực sự làm gì hữu ích.
tripleee
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.