Làm thế nào để tôi lặp qua các thư mục chỉ trong bash?


249

Tôi có một thư mục với một số thư mục và một số tệp (một số bị ẩn, bắt đầu bằng dấu chấm).

for d in *; do
 echo $d
done

sẽ lặp qua tất cả các tệp, nhưng tôi chỉ muốn lặp qua các thư mục. Làm thế nào để làm điều đó?

Câu trả lời:


332

Bạn có thể chỉ định một dấu gạch chéo ở cuối để chỉ khớp với các thư mục:

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

7
Lưu ý rằng nó cũng bao gồm các liên kết tượng trưng đến thư mục.
Stéphane Chazelas

1
Vì vậy, làm thế nào bạn sẽ loại trừ symlink sau đó?
rubo77

3
@ rubo77: Bạn có thể kiểm tra [[ -L $d ]]xem $ d có phải là một liên kết tượng trưng hay không.
choroba

1
@AsymLabs Điều đó không chính xác, set -Pchỉ ảnh hưởng đến các lệnh thay đổi thư mục. spunge.us/TNac
Chris Down

4
@choroba: [[ -L "$f" ]]sẽ không loại trừ các liên kết tượng trưng trong trường hợp này */, bạn phải loại bỏ dấu gạch chéo với [[ -L "${f%/}" ]](xem Kiểm tra liên kết với dấu gạch chéo )
rubo77

78

Bạn có thể kiểm tra với -d:

for f in *; do
    if [ -d "$f" ]; then
        # $f is a directory
    fi
done

Đây là một trong những toán tử kiểm tra tập tin .


8
Lưu ý rằng nó sẽ bao gồm các liên kết tượng trưng đến thư mục.
Stéphane Chazelas

21
if [[ -d "$f" && ! -L "$f" ]]sẽ loại trừ các liên kết tượng trưng
rubo77

Sẽ phá vỡ trên thư mục trống. if [[ "$f" = "*" ]]; then continue; fi
Piskvor

30

Coi chừng giải pháp của choroba, mặc dù thanh lịch, có thể gợi ra hành vi bất ngờ nếu không có thư mục nào có sẵn trong thư mục hiện tại. Ở trạng thái này, thay vì bỏ qua forvòng lặp, bash sẽ chạy vòng lặp chính xác một lần khi dbằng */:

#!/usr/bin/env bash

for d in */; do
    # Will print */ if no directories are available
    echo $d
done

Tôi khuyên bạn nên sử dụng những điều sau đây để bảo vệ chống lại trường hợp này:

#!/usr/bin/env bash

for f in *; do
    if [ -d ${f} ]; then
        # Will not run if no directories are available
        echo $f
    fi
done

Mã này sẽ lặp qua tất cả các tệp trong thư mục hiện tại, kiểm tra xem có phải flà thư mục không, sau đó lặp lại fnếu điều kiện trả về đúng. Nếu fbằng */, echo $fsẽ không thực thi.


3
Dễ dàng hơn nhiều để shopt -s nullglob.
choroba

21

Nếu bạn cần chọn nhiều tệp cụ thể hơn chỉ thư mục sử dụng findvà chuyển nó tới while read:

shopt -s dotglob
find * -prune -type d | while IFS= read -r d; do 
    echo "$d"
done

Sử dụng shopt -u dotglobđể loại trừ các thư mục ẩn (hoặc setopt dotglob/ unsetopt dotglobtrong zsh).

IFS=để tránh chia tách tên tệp có chứa một trong số $IFS, ví dụ:'a b'

xem câu trả lời của AsymLabs dưới đây để có thêm findlựa chọn


chỉnh sửa:
Trong trường hợp bạn cần tạo một giá trị thoát từ trong vòng lặp while, bạn có thể phá vỡ lớp con phụ bằng thủ thuật này:

while IFS= read -r d; do 
    if [ "$d" == "something" ]; then exit 1; fi
done < <(find * -prune -type d)

2
Tôi đã tìm thấy giải pháp này trên: stackoverflow.com/a/8361394/1069083
rubo77

11

Bạn có thể sử dụng bash thuần cho điều đó, nhưng tốt hơn là sử dụng find:

find . -maxdepth 1 -type d -exec echo {} \;

(tìm thêm sẽ bao gồm các thư mục ẩn)


8
Lưu ý rằng nó không bao gồm symlink đến thư mục. Bạn có thể sử dụng shopt -s dotglobcho bashđến bao gồm các thư mục ẩn. Bạn cũng sẽ bao gồm .. Cũng lưu ý rằng đó -maxdepthkhông phải là một tùy chọn tiêu chuẩn ( -prunelà).
Stéphane Chazelas

2
Các dotglobtùy chọn là thú vị nhưng dotglobchỉ áp dụng cho việc sử dụng *. find .sẽ luôn bao gồm các thư mục ẩn (và cả thư mục hiện tại)
rubo77

6

Điều này được thực hiện để tìm cả thư mục hiển thị và ẩn trong thư mục làm việc hiện tại, ngoại trừ thư mục gốc:

để chỉ lặp qua các thư mục:

 find -path './*' -prune -type d

để bao gồm các liên kết tượng trưng trong kết quả:

find -L -path './*' -prune -type d

để làm một cái gì đó cho mỗi thư mục (không bao gồm symlink):

find -path './*' -prune -type d -print0 | xargs -0 <cmds>

để loại trừ các thư mục ẩn:

find -path './[^.]*' -prune -type d

để thực thi nhiều lệnh trên các giá trị được trả về (một ví dụ rất dễ hiểu):

find -path './[^.]*' -prune -type d -print0 | xargs -0 -I '{}' sh -c \
"printf 'first: %-40s' '{}'; printf 'second: %s\n' '{}'"

thay vì 'sh -c' cũng có thể sử dụng 'bash -c', v.v.


Điều gì xảy ra nếu echo '* /' in 'cho d trong echo * /' chứa 60.000 thư mục?
AsymLabs

Bạn sẽ gặp lỗi "Quá nhiều tệp" nhưng điều đó có thể được giải quyết: Cách phá vỡ Quá nhiều tệp đang mở ra trong debian
rubo77

1
Ví dụ xargs chỉ hoạt động cho một tập hợp con tên thư mục 9e.g. những người có không gian hoặc dòng mới sẽ không hoạt động). Tốt hơn để sử dụng ... -print0 | xargs -0 ...nếu bạn không biết tên chính xác là gì.
Anthon

1
@ rubo77 đã thêm cách để loại trừ các tệp / thư mục ẩn và thực thi nhiều lệnh - tất nhiên điều này cũng có thể được thực hiện với một tập lệnh.
AsymLabs

1
Tôi đã thêm một ví dụ trong câu trả lời của tôi ở phía dưới, cách làm một cái gì đó cho mỗi thư mục bằng cách sử dụng một hàm vớifind * | while read file; do ...
rubo77

3

Bạn có thể lặp qua tất cả các thư mục bao gồm các thư mục ẩn (bắt đầu bằng dấu chấm) trong một dòng và nhiều lệnh với:

for file in */ .*/ ; do echo "$file is a directory"; done

Nếu bạn muốn loại trừ các liên kết tượng trưng:

for file in *; do 
  if [[ -d "$file" && ! -L "$file" ]]; then
    echo "$file is a directory"; 
  fi; 
done

lưu ý: sử dụng danh sách */ .*/hoạt động trong bash, nhưng cũng hiển thị các thư mục ...trong khi ở zsh, nó sẽ không hiển thị những lỗi này nhưng sẽ xuất hiện lỗi nếu không có tệp ẩn trong thư mục

Một phiên bản sạch hơn sẽ bao gồm các thư mục ẩn và loại trừ ../ sẽ có tùy chọn dotglob:

shopt -s dotglob
for file in */ ; do echo "$file is a directory"; done

(hoặc setopt dotglobtrong zsh)

bạn có thể hủy đặt dotglob với

shopt -u dotglob

2

Điều này sẽ bao gồm đường dẫn đầy đủ trong mỗi thư mục trong danh sách:

for i in $(find $PWD -maxdepth 1 -type d); do echo $i; done

1
nghỉ khi sử dụng các thư mục có dấu cách
Alejandro Sazo

0

Sử dụng findvới -execđể lặp qua các thư mục và gọi một hàm trong tùy chọn exec:

dosomething () {
  echo "doing something with $1"
}
export -f dosomething
find -path './*' -prune -type d -exec bash -c 'dosomething "$0"' {} \;

Sử dụng shopt -s dotglobhoặc shopt -u dotglobđể bao gồm / loại trừ các thư mục ẩn


0

Điều này liệt kê tất cả các thư mục cùng với số lượng thư mục con trong một đường dẫn nhất định:

for directory in */ ; do D=$(readlink -f "$directory") ; echo $D = $(find "$D" -mindepth 1 -type d | wc -l) ; done

-1
ls -d */ | while read d
do
        echo $d
done

This is one directory with spaces in the name- nhưng được phân tích cú pháp thành nhiều.
Piskvor

-5
ls -l | grep ^d

hoặc là:

ll | grep ^d

Bạn có thể đặt nó làm bí danh


1
Thật không may, tôi không nghĩ rằng câu trả lời này là "Tôi chỉ muốn lặp qua các thư mục" - hơi khác so với lscâu trả lời dựa trên câu hỏi này, trong đó liệt kê các thư mục .
Jeff Schaller

1
Không bao giờ là một ý tưởng tốt để phân tích đầu ra của ls: mywiki.wooledge.org/ParsingLs
codeforester
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.