Làm thế nào để kiểm tra thư mục trống


14

Tôi có một yêu cầu, nếu tôi thực thi một tập lệnh ./123với các đối số của đường dẫn trống, hãy nói /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(thư mục này trống). Nó sẽ hiển thị "thư mục trống"

Mã của tôi là:

#!/bin/bash
dir="$1"

if [ $# -ne 1 ]
then
    echo "please pass arguments" 
exit
fi

if [ -e $dir ]
then
printf "minimum file size: %s\n\t%s\n" \
 $(du $dir -hab | sort -n -r | tail -1)

printf "maximum file size: %s\n\t%s\n" \
 $(du $dir -ab | sort -n | tail -1)

printf "average file size: %s"
du $dir -sk | awk '{s+=$1}END{print s/NR}'
else
   echo " directory doesn't exists"
fi

if [ -d "ls -A $dir" ]
 then
    echo " directory is  empty"
fi

Tôi có một lỗi hiển thị như thế, nếu tôi thực thi tên tập lệnh ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(thư mục này trống).

minimum file size: 4096
    /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions
maximum file size: 4096
    /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions
average file size: 4

thay vì chỉ hiển thị đầu ra "thư mục trống", nó hiển thị đầu ra ở trên

Đầu ra dưới đây phải được hiển thị nếu tôi thực hiện kịch bản với các đối số chính xác (ý ​​tôi là với đường dẫn thư mục chính xác). Nói./123 /usr/share

minimum file size: 196
        /usr/share
    maximum file size: 14096
        /usr/share
    average file size: 4000

sản lượng dự kiến ​​của tôi là: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.


ai đó có thể xin vui lòng kiểm tra mã của tôi một lần. Tôi đã chỉnh sửa nó
vị phật saletanth

Câu trả lời:


20
if    ls -1qA ./somedir/ | grep -q .
then  ! echo somedir is not empty
else  echo somedir is empty
fi

Trên đây là một thử nghiệm tương thích POSIX - và sẽ rất nhanh. lssẽ liệt kê tất cả các tệp / thư mục trong một thư mục ngoại trừ ...mỗi tệp trên mỗi dòng và sẽ -qghi tất cả các ký tự không in được (để bao gồm các \newlines) trong đầu ra với ?dấu chấm hỏi. Theo cách này, nếu grepnhận được ngay cả một ký tự đơn trong đầu vào, nó sẽ trả về true - khác false.

Để làm điều đó trong một vỏ POSIX:

cd  ./somedir/ || exit
set ./* ./.[!.]* ./..?*
if   [ -n "$4" ] ||
     for e do 
         [ -L "$e" ] ||
         [ -e "$e" ] && break
     done
then ! echo somedir is not empty
else   echo somedir is empty
fi
cd "$OLDPWD"

Một POSIX vỏ (trong đó có không sớm khuyết tật -fhệ ilename) sẽ setcác "$@"vị trí tham số mảng cho một trong hai chuỗi chữ tiếp theo là setlệnh trên, nếu không đến các lĩnh vực được tạo ra bởi các nhà khai thác glob ở phần cuối của mỗi người. Cho dù nó làm như vậy là tùy thuộc vào việc các quả bóng thực sự phù hợp với bất cứ điều gì. Trong một số shell, bạn có thể hướng dẫn một quả cầu không phân giải mở rộng thành null - hoặc không có gì cả. Điều này đôi khi có thể có lợi, nhưng nó không khả dụng và thường đi kèm với các vấn đề khác - chẳng hạn như phải đặt các tùy chọn shell đặc biệt và sau đó bỏ đặt chúng.

Các phương tiện di động duy nhất để xử lý các đối số có giá trị null liên quan đến các biến rỗng hoặc không đặt hoặc ~mở rộng dấu ngã. Và sau này, an toàn hơn nhiều so với trước đây.

Phía trên hệ vỏ chỉ kiểm tra bất kỳ tệp nào về độ chính -exác nếu cả ba khối được chỉ định giải quyết nhiều hơn một trận đấu. Vì vậy, forvòng lặp chỉ chạy hoàn toàn trong ba lần lặp hoặc ít hơn và chỉ trong trường hợp thư mục trống hoặc trong trường hợp một hoặc nhiều mẫu chỉ giải quyết thành một tệp duy nhất. Các forcũng breaklà nếu bất kỳ của những đống đại diện cho một tập tin thực tế - và như tôi đã sắp xếp những đống theo thứ tự nhiều khả năng ít có khả năng, cần khá nhiều bỏ trên phiên đầu tiên mỗi lần.

Dù bằng cách nào bạn làm điều đó nên đòi hỏi chỉ là một hệ thống duy nhất stat()gọi - vỏ và lscả hai nên chỉ cần stat()vào thư mục truy vấn và danh sách các tập tin của nó dentries báo cáo rằng nó chứa. Điều này trái ngược với hành vi findthay vào đó là stat()mọi tệp bạn có thể liệt kê với nó.


Có thể xác nhận. Tôi sử dụng ví dụ ban đầu của mikeerv trong các kịch bản tôi đã viết.
Otheus

Điều đó sẽ báo cáo là các thư mục trống không tồn tại hoặc mà bạn không có quyền truy cập đọc. Đối với cái thứ hai, nếu bạn có quyền truy cập đọc nhưng không truy cập tìm kiếm, YMMV.
Stéphane Chazelas

@ StéphaneChazelas - có lẽ, nhưng các báo cáo như vậy sẽ được kèm theo thông báo lỗi để thông báo cho người dùng về điều đó.
mikeerv

1
Chỉ trong lstrường hợp. hả hê và [im lặng khi họ không có quyền truy cập. Chẳng hạn, [ -e /var/spool/cron/crontabs/stephane ]sẽ âm thầm báo cáo sai, trong khi có một tệp có tên đó.
Stéphane Chazelas

Cũng lưu ý rằng những giải pháp đó không tương đương nếu somedir là một liên kết tượng trưng đến một thư mục (hoặc không phải là một thư mục).
Stéphane Chazelas

6

Với GNU hoặc BSD hiện đại find, bạn có thể làm:

if find -- "$dir" -prune -type d -empty | grep -q .; then
  printf '%s\n' "$dir is an empty directory"
else
  printf >&2 '%s\n' "$dir is not empty, or is not a directory" \
                    "or is not readable or searchable in which case" \
                    "you should have seen an error message from find above."
fi

(giả định $dirkhông giống như một findvị ngữ ( !, (, -name...).

POSIXly:

if [ -d "$dir" ] && files=$(ls -qAL -- "$dir") && [ -z "$files" ]; then
  printf '%s\n' "$dir is an empty directory"
else
  printf >&2 '%s\n' "$dir is not empty, or is not a directory" \
                    "or is not readable or searchable in which case" \
                    "you should have seen an error message from ls above."
fi

4

[-z $dir ]phàn nàn rằng không có lệnh nào được gọi [-ztrên hầu hết các hệ thống. Bạn cần không gian xung quanh dấu ngoặc .

[ -z $dir ]xảy ra là đúng nếu dirtrống và là sai đối với hầu hết các giá trị khác của dir, nhưng nó không đáng tin cậy, ví dụ như nó đúng nếu giá trị của dir= -zhoặc -o -o -n -n. Luôn luôn sử dụng dấu ngoặc kép xung quanh các thay thế lệnh (điều này cũng đúng với phần còn lại của tập lệnh của bạn).

[ -z "$dir" ]kiểm tra xem giá trị của biến dircó trống không. Giá trị của biến là một chuỗi, đó là đường dẫn đến thư mục. Điều đó không cho bạn biết bất cứ điều gì về chính thư mục.

Không có toán tử nào kiểm tra xem một thư mục có trống không, giống như có một tệp thông thường ( [ -s "$dir" ]đúng với một thư mục ngay cả khi nó trống). Một cách đơn giản để kiểm tra xem một thư mục có trống hay không là liệt kê nội dung của nó; nếu bạn nhận được văn bản trống, thư mục trống.

if [ -z "$(ls -A -- "$dir")" ]; then 

Trên các hệ thống cũ không có ls -A, bạn có thể sử dụng ls -a, nhưng sau đó ...được liệt kê.

if [ -z "$(LC_ALL=C ls -a -- "$dir")" = ".
.." ]; then 

(Không thụt dòng bắt đầu ..vì chuỗi phải chỉ chứa một dòng mới, không phải dòng mới và không gian thừa.)


bạn có thể vui lòng giải thích phần này "$ (ls -A -" $ dir ")". $ trong và ngoài dấu ngoặc làm gì?
vị phật saletanth


@Giles "$ (ls -A -" $ dir ")" không hoạt động với lỗi ném của nó.
vị phật giáo

@buddhasaletanth Lỗi gì? Sao chép dán. Bạn không thể mong đợi mọi người sẽ giúp đỡ bạn nếu bạn chỉ nói rằng không hoạt động mà không giải thích chính xác những gì bạn quan sát.
Gilles 'SO- ngừng trở thành ác quỷ'

@Giles đây là lỗi. Khi tôi thực thi tập lệnh của mình ./filestats, lỗi ném của nó .. $ / filestats / home / saletanth / testdir / aki / schatz Kích thước tệp tối thiểu: 4096 / home / saletanth / testdir / aki / schatz Kích thước tệp tối đa: 4096 / home / suxanth / testdir / aki / schatz kích thước tệp trung bình: 4 thư mục trống. Thay vì hiển thị "thư mục trống". Nó hiển thị với kích thước tối thiểu, kích thước tối đa và kích thước avg.
phật giáo

3

Bạn đang xem các thuộc tính của chính thư mục.

$ mkdir /tmp/foo
$ ls -ld /tmp/foo
drwxr-xr-x 2 jackman jackman 4096 May  8 11:32 /tmp/foo
# ...........................^^^^

Bạn muốn đếm có bao nhiêu tệp trong đó:

$ dir=/tmp/foo
$ shopt -s nullglob
$ files=( "$dir"/* "$dir"/.* )
$ echo ${#files[@]}
2
$ printf "%s\n" "${files[@]}"
/tmp/foo/.
/tmp/foo/..

Vì vậy, bài kiểm tra cho "thư mục trống" là:

function is_empty {
    local dir="$1"
    shopt -s nullglob
    local files=( "$dir"/* "$dir"/.* )
    [[ ${#files[@]} -eq 2 ]]
}

Như thế này:

$ if is_empty /tmp/foo; then echo "it's empty"; else echo "not empty"; fi
it's empty
$ touch /tmp/foo/afile
$ if is_empty /tmp/foo; then echo "it's empty"; else echo "not empty"; fi
not empty

Điều đó sẽ báo cáo thư mục trống nếu bạn không có quyền truy cập vào nó hoặc nó không tồn tại.
Stéphane Chazelas

1

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 nullglobtù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 nullgloblệ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 nullgloblệ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 nullgloblệ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 *, ..?*.[^.]*để phù hợp với các tập tin ẩn, nhưng không .... 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 rmxtrong 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 xvà đ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 *.


$(set -f; echo "$1"/*)có vẻ là một cách viết khá phức tạp "$1/*". Và điều này sẽ không phù hợp với các thư mục hoặc tập tin ẩn.
muru

Điều tôi muốn nói là không có sự mở rộng tên tệp trong "$1/*"(lưu ý các trích dẫn bao gồm *), do đó, đặc biệt là set -fvà không cần thiết cho điều đó .
muru

Nó trả lời một câu hỏi khác: liệu thư mục chứa các tệp hoặc thư mục không bị ẩn. Tôi lấy làm tiếc vì điều đó. Tôi sẽ rất vui khi xóa nó.
Đaminh 108

Có nhiều cách để phù hợp với các tập tin ẩn quá (xem glob phức tạp trong câu trả lời của mikeserv : ./* ./.[!.]* ./..?*). Có lẽ bạn có thể kết hợp điều đó để làm cho chức năng hoàn thành.
muru

Ah ! Tôi sẽ xem xét điều này và tìm hiểu và có lẽ chúng ta sẽ có câu trả lời hoàn chỉnh dựa trên việc mở rộng toàn cầu!
Đaminh 108

0
#/bin/sh

somedir=somedir
list="`echo "$somedir"/*`"

test "$list" = "$somedir/*" && echo "$somedir is empty"

# dot prefixed entries would be skipped though, you have to explicitly check them

1
Xin chào và chào mừng đến với trang web. Xin vui lòng không gửi câu trả lời chỉ là mã và không có lời giải thích. Nhận xét mã hoặc giải thích khác những gì bạn đang làm. Nhân tiện, không có lý do để sử dụng echo, tất cả những gì bạn cần là list="$somedir/*". Ngoài ra, điều này là khó khăn và dễ bị lỗi. Bạn đã không đề cập rằng OP sẽ đưa ra đường dẫn của mục tiêu, bao gồm cả dấu gạch chéo, chẳng hạn.
terdon

0

Đây là một cách đơn giản để làm điều đó. Giả sử rằng D là tên đường dẫn đầy đủ của thư mục bạn muốn kiểm tra sự trống rỗng.

Sau đó

if [[ $( du -s  D |awk ' {print $1}') = 0 ]]
then
      echo D is empty
fi

Điều này hoạt động vì

du -s D

có đầu ra

size_of_D   D

awk xóa D và nếu kích thước bằng 0 thì thư mục trống.

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.