Thực hiện một hành động trong mọi thư mục con bằng Bash


194

Tôi đang làm việc trên một kịch bản cần thực hiện một hành động trong mỗi thư mục con của một thư mục cụ thể.

Cách hiệu quả nhất để viết đó là gì?


1
Vui lòng xem xét quay lại và đánh giá lại các câu trả lời cho chính xác - bạn đã có một câu trả lời được chấp nhận nhận được rất nhiều lượt xem mặc dù có lỗi lớn (f / e, chạy nó qua một thư mục mà ai đó đã chạy trước đó mkdir 'foo * bar'sẽ gây ra foobarbị lặp lại ngay cả khi chúng không tồn tại và *sẽ được thay thế bằng một danh sách tất cả các tên tệp, ngay cả những tên không có trong thư mục).
Charles Duffy

1
... thậm chí tệ hơn là nếu ai đó chạy mkdir -p '/tmp/ /etc/passwd /'- nếu ai đó chạy tập lệnh theo thông lệ này /tmp, giả sử, tìm thư mục để xóa, họ có thể sẽ xóa /etc/passwd.
Charles Duffy

Câu trả lời:


177
for D in `find . -type d`
do
    //Do whatever you need with D
done

81
cái này sẽ vỡ trên các khoảng trắng
ghostdog74

2
Ngoài ra, lưu ý bạn cần điều chỉnh các thông số findnếu bạn muốn hành vi đệ quy hoặc không đệ quy.
Chris Tonkinson

32
Câu trả lời ở trên cũng cho tôi thư mục tự động, vì vậy những điều sau đây hoạt động tốt hơn một chút đối với tôi: find . -mindepth 1 -type d
jzheaux

1
@JoshC, cả hai biến thể sẽ phá vỡ thư mục được tạo bởi mkdir 'directory name with spaces'bốn từ riêng biệt.
Charles Duffy

4
Tôi cần phải thêm -mindepth 1 -maxdepth 1hoặc nó đã đi quá sâu.
ScrappyDev

282

Một phiên bản tránh tạo quy trình phụ:

for D in *; do
    if [ -d "${D}" ]; then
        echo "${D}"   # your processing here
    fi
done

Hoặc, nếu hành động của bạn là một lệnh duy nhất, thì điều này ngắn gọn hơn:

for D in *; do [ -d "${D}" ] && my_command; done

Hoặc một phiên bản thậm chí ngắn gọn hơn ( cảm ơn @enzotib ). Lưu ý rằng trong phiên bản này, mỗi giá trị Dsẽ có dấu gạch chéo:

for D in */; do my_command; done

48
Bạn có thể tránh ifhoặc [với:for D in */; do
enzotib

2
+1 vì tên thư mục không bắt đầu bằng ./ trái ngược với câu trả lời được chấp nhận
Hayri Uğur Koltuk

3
Điều này đúng ngay cả với khoảng trắng trong tên thư mục +1
Alex Reinking

5
Có một vấn đề với lệnh cuối cùng: nếu bạn đang ở trong một thư mục không có thư mục con; nó sẽ trả về "* /". Vì vậy, tốt hơn nên sử dụng lệnh thứ hai for D in *; do [ -d "${D}" ] && my_command; donehoặc kết hợp cả hai mới nhất:for D in */; do [ -d $D ] && my_command; done
Chris Maes

5
Lưu ý rằng câu trả lời này bỏ qua các thư mục ẩn. Để bao gồm các thư mục ẩn sử dụng for D in .* *; dothay thế for D in *; do.
patryk.beza

105

Cách đơn giản nhất không đệ quy là:

for d in */; do
    echo "$d"
done

Các /cuối cùng nói, chỉ sử dụng các thư mục.

Không cần

  • tìm thấy
  • ôi
  • ...

11
Lưu ý: điều này sẽ không bao gồm các thư mục dấu chấm (có thể là một điều tốt, nhưng điều quan trọng là phải biết).
wvducky

Hữu ích cần lưu ý rằng bạn có thể sử dụng shopt -s dotglobđể bao gồm dotfiles / dotdirs khi mở rộng ký tự đại diện. Xem thêm: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Steen Schütt

Tôi nghĩ rằng bạn có nghĩa là /*thay vì */với /đại diện các đường dẫn mà bạn muốn sử dụng.
Brōtsyorfuzthrāx

2
@Shule /*sẽ dành cho đường dẫn tuyệt đối trong khi */sẽ bao gồm các thư mục con từ vị trí hiện tại
Dan G

3
gợi ý hữu ích: nếu bạn cần cắt bớt dấu '/' từ $ d, hãy sử dụng${d%/*}
Hafthor

18

Sử dụng findlệnh.

Trong GNU find, bạn có thể sử dụng -execdirtham số:

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

hoặc bằng cách sử dụng -exectham số:

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

hoặc với xargslệnh:

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

Hoặc sử dụng cho vòng lặp:

for d in */; { echo "$d"; }

Đối với đệ quy, hãy thử mở rộng globalbing ( **/) (enable by shopt -s extglob:).


Để biết thêm ví dụ, xem: Làm thế nào để đi đến từng thư mục và thực hiện một lệnh? tại SO


1
-exec {} +là POSIX được chỉ định, -exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +là một tùy chọn hợp pháp khác và gọi ít shell hơn -exec sh -c '...' {} \;.
Charles Duffy

13

Một lớp lót tiện dụng

for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A

find * -type d ### Option B

Tùy chọn A là chính xác cho các thư mục có khoảng trắng ở giữa. Ngoài ra, thường nhanh hơn vì nó không in từng từ trong tên thư mục dưới dạng một thực thể riêng biệt.

# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real    0m0.327s
user    0m0.084s
sys     0m0.236s

# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real    0m0.787s
user    0m0.484s
sys     0m0.308s

10

find . -type d -print0 | xargs -0 -n 1 my_command


Tôi có thể cung cấp giá trị trả về của tìm thấy đó vào một vòng lặp for không? Đây là một phần của một kịch bản lớn hơn ...
mikewilliamson

@Mike, không chắc. $? có thể sẽ giúp bạn có được trạng thái của lệnh find hoặc lệnh xargs chứ không phải my_command.
Paul Tomblin

7

Điều này sẽ tạo ra một lớp con (có nghĩa là các giá trị biến sẽ bị mất khi whilethoát khỏi vòng lặp):

find . -type d | while read -r dir
do
    something
done

Điều này sẽ không:

while read -r dir
do
    something
done < <(find . -type d)

Một trong hai sẽ hoạt động nếu có khoảng trắng trong tên thư mục.


Để xử lý tốt hơn các tên tệp lạ (bao gồm các tên kết thúc bằng khoảng trắng và / hoặc bao gồm các nguồn cấp dữ liệu), hãy sử dụng find ... -print0while IFS="" read -r -d $'\000' dir
Gordon Davisson

@GordonDavisson, ... thực sự, tôi thậm chí còn lập luận rằng -d ''ít gây hiểu lầm về cú pháp và khả năng bash, vì -d $'\000'ngụ ý (sai) $'\000'theo một cách nào đó khác với ''- thực sự, người ta có thể dễ dàng (và một lần nữa, giả mạo) suy luận từ bash hỗ trợ các chuỗi kiểu Pascal (được chỉ định theo độ dài, có thể chứa các ký tự NUL) thay vì các chuỗi C (được phân cách bằng NUL, không thể chứa NUL).
Charles Duffy

5

Bạn có thể thử:

#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/

for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
  cd "$f"
  <your job here>
done

hoặc tương tự...

Giải trình:

find . -maxdepth 1 -mindepth 1 -type d: Chỉ tìm các thư mục có độ sâu đệ quy tối đa là 1 (chỉ các thư mục con $ 1) và độ sâu tối thiểu là 1 (không bao gồm thư mục hiện tại .)


Đây là lỗi - hãy thử với một tên thư mục có khoảng trắng. Xem BashPit thác # 1DontReadLinesWithFor .
Charles Duffy

Tên thư mục có khoảng trắng được đặt trong dấu ngoặc kép và do đó hoạt động và OP không cố đọc các dòng từ tệp.
Henry Dobson

nó hoạt động trong cd "$f". Nó không hoạt động khi đầu ra từ findđược tách chuỗi, vì vậy bạn sẽ có các phần riêng biệt của tên dưới dạng các giá trị riêng biệt $f, làm cho bạn làm tốt như thế nào hoặc không trích dẫn $fviệc mở rộng.
Charles Duffy

Tôi không nói họ đang cố đọc các dòng từ một tập tin. findĐầu ra của định hướng là dòng (theo tên một dòng, theo lý thuyết - nhưng xem bên dưới) với -printhành động mặc định .
Charles Duffy

Đầu ra hướng dòng, vì từ đó find -printkhông phải là một cách an toàn để vượt qua tên tệp tùy ý, vì người ta có thể chạy một cái gì đó mkdir -p $'foo\n/etc/passwd\nbar'và có được một thư mục có /etc/passwdmột dòng riêng biệt trong tên của nó. Xử lý tên từ các tệp trong /uploadhoặc /tmpthư mục mà không cần quan tâm là một cách tuyệt vời để có được các cuộc tấn công leo thang đặc quyền.
Charles Duffy

4

câu trả lời được chấp nhận sẽ ngắt trên các khoảng trắng nếu tên thư mục có chúng và cú pháp ưa thích là $()dành cho bash / ksh. Sử dụng GNU find -exec tùy chọn với +;ví dụ

find .... -exec mycommand +; #this is same as passing to xargs

hoặc sử dụng vòng lặp while

find .... |  while read -r D
do
  ...
done 

những gì param sẽ giữ tên thư mục? ví dụ: chmod + x $ DIR_NAME (vâng, tôi biết có một tùy chọn chmod chỉ dành cho các thư mục)
Mike Graf

Có một sự khác biệt tinh tế giữa find -execvà chuyển đến xargs: findsẽ bỏ qua giá trị thoát của lệnh đang được thực thi, trong khi xargssẽ thất bại ở lối thoát khác. Hoặc có thể đúng, tùy thuộc vào nhu cầu của bạn.
Jason Wodicka

find ... -print0 | while IFS= read -r dan toàn hơn - hỗ trợ các tên bắt đầu hoặc kết thúc trong khoảng trắng và các tên có chứa các dòng chữ mới.
Charles Duffy
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.