Câu trả lời tldr của tôi là:
function emptydir {
[ "$1/"* "" = "" ] 2> /dev/null &&
[ "$1/"..?* "" = "" ] 2> /dev/null &&
[ "$1/".[^.]* "" = "" ] 2> /dev/null ||
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
Nó tuân thủ POSIX và, không quan trọng lắm, nó thường nhanh hơn giải pháp liệt kê thư mục và chuyển đầu ra thành grep.
Sử dụng:
if emptydir adir
then
echo "nothing found"
else
echo "not empty"
fi
Tôi thích câu trả lời /unix//a/202276/160204 , mà tôi viết lại là:
function emptydir {
! { ls -1qA "./$1/" | grep -q . ; }
}
Nó liệt kê các thư mục và dẫn kết quả đến grep. Thay vào đó, tôi đề xuất một chức năng đơn giản dựa trên sự mở rộng và so sánh toàn cầu.
function emptydir {
[ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}
Chức năng này không phải là POSIX tiêu chuẩn và gọi một mạng con với $()
. Tôi giải thích chức năng đơn giản này trước để chúng ta có thể hiểu rõ hơn về giải pháp cuối cùng (xem câu trả lời tldr ở trên) sau.
Giải trình:
Phía bên tay trái (LHS) trống khi không có sự mở rộng xảy ra, đó là trường hợp khi thư mục trống. Tùy chọn nullglob là bắt buộc vì nếu không khi không có kết quả khớp, chính quả cầu là kết quả của việc mở rộng. (Có RHS khớp với các khối của LHS khi thư mục trống không hoạt động do dương tính giả xảy ra khi một quả cầu LHS khớp với một tệp có tên là chính quả cầu: *
trong quả cầu khớp với chuỗi con *
trong tên tệp. ) Biểu thức dấu ngoặc {,.[^.],..?}
bao gồm các tệp ẩn, nhưng không ..
hoặc .
.
Bởi vì shopt -s nullglob
được thực thi bên trong $()
(một lớp con), nó không thay đổi nullglob
tùy chọn của shell hiện tại, điều này thường là một điều tốt. Mặt khác, nên đặt tùy chọn này trong các tập lệnh, bởi vì nó dễ bị lỗi toàn cầu trả về một cái gì đó khi không có kết quả khớp. Vì vậy, người ta có thể đặt tùy chọn nullglob khi bắt đầu tập lệnh và nó sẽ không cần thiết trong hàm. Hãy ghi nhớ điều này: chúng tôi muốn một giải pháp hoạt động với tùy chọn nullglob.
Hãy cẩn thận:
Nếu chúng ta không có quyền truy cập đọc vào thư mục, hàm sẽ báo cáo giống như khi có một thư mục trống. Điều này cũng áp dụng cho một chức năng liệt kê thư mục và grep đầu ra.
Các shopt -s nullglob
lệnh không phải là tiêu chuẩn POSIX.
Nó sử dụng các lớp con được tạo bởi $()
. Nó không phải là một vấn đề lớn, nhưng thật tuyệt nếu chúng ta có thể tránh nó.
Chuyên nghiệp:
Không phải là nó thực sự quan trọng, nhưng chức năng này nhanh hơn bốn lần so với trước đó, được đo bằng lượng thời gian CPU dành cho kernel trong quy trình.
Các giải pháp khác:
Chúng ta có thể loại bỏ các phi POSIX shopt -s nullglob
lệnh trên LHS và đặt chuỗi "$1/* $1/.[^.]* $1/..?*"
trong RHS và loại bỏ riêng các dương tính giả xảy ra khi chúng tôi chỉ có tập tin có tên '*'
, .[^.]*
hoặc ..?*
trong thư mục:
function emptydir {
[ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
[ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}
Không có shopt -s nullglob
lệnh, bây giờ có ý nghĩa để loại bỏ lớp con, nhưng chúng ta phải cẩn thận vì chúng ta muốn tránh chia tách từ và cho phép mở rộng toàn cầu trên LHS. Đặc biệt trích dẫn để tránh chia tách từ không hoạt động, bởi vì nó cũng ngăn chặn việc mở rộng toàn cầu. Giải pháp của chúng tôi là xem xét các khối lượng riêng biệt:
function emptydir {
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
Chúng tôi vẫn có tính năng chia từ cho toàn cầu, nhưng giờ thì ổn rồi, vì nó sẽ chỉ gây ra lỗi khi thư mục không trống. Chúng tôi đã thêm 2> / dev / null, để loại bỏ thông báo lỗi khi có nhiều tệp khớp với toàn cầu đã cho trên LHS.
Chúng tôi nhớ lại rằng chúng tôi muốn một giải pháp hoạt động với tùy chọn nullglob. Giải pháp trên thất bại với tùy chọn nullglob, vì khi thư mục trống, LHS cũng trống. May mắn thay, nó không bao giờ nói rằng thư mục trống khi nó không. Nó chỉ thất bại khi nói rằng nó trống rỗng khi nó là. Vì vậy, chúng ta có thể quản lý tùy chọn nullglob một cách riêng biệt. Chúng ta không thể đơn giản thêm các trường hợp, [ "$1/"* = "" ]
v.v. bởi vì những trường hợp này sẽ mở rộng như [ = "" ]
, v.v ... không đúng về mặt cú pháp. Vì vậy, chúng tôi sử dụng [ "$1/"* "" = "" ]
vv để thay thế. Chúng tôi một lần nữa phải xem xét ba trường hợp *
, ..?*
và .[^.]*
để phù hợp với các tập tin ẩn, nhưng không .
và..
. Những điều này sẽ không can thiệp nếu chúng ta không có tùy chọn nullglob, bởi vì họ cũng không bao giờ nói rằng nó trống khi không có. Vì vậy, giải pháp đề xuất cuối cùng là:
function emptydir {
[ "$1/"* "" = "" ] 2> /dev/null &&
[ "$1/"..?* "" = "" ] 2> /dev/null &&
[ "$1/".[^.]* "" = "" ] 2> /dev/null ||
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
Mối quan tâm về an ninh:
Tạo hai tệp rm
và x
trong một thư mục trống và thực hiện *
trên dấu nhắc. Toàn cầu *
sẽ mở rộng rm x
và điều này sẽ được thực hiện để loại bỏ x
. Đây không phải là một mối quan tâm bảo mật, bởi vì trong chức năng của chúng tôi, các khối được đặt ở nơi các phần mở rộng không được xem là các lệnh, mà là các đối số, giống như trong for f in *
.