如何检查目录是否为空


15

我有一个要求,如果我执行./123带有空路径参数的脚本,请说/usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(此目录为空)。它应该显示“目录为空”

我的代码是:

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

如果执行脚本名称./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(此目录为空),则会显示类似的错误。

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

而不是只显示输出“目录为空”,而是显示上面的输出

如果我使用正确的参数执行脚本(我的意思是使用正确的目录路径),则必须显示以下输出。说./123 /usr/share

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

我的预期输出是: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.


有人可以检查一下我的代码吗?我编辑了它
佛陀sreekanth

Answers:


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

上面是与POSIX兼容的测试-应该非常快。ls将列出目录中的所有文件/目录(每行除外)...每行一个,并在输出中用问号标记-q所有不可打印的字符(包括\n粗斜线)?。这样,如果grep在输入中甚至接收到单个字符,它将返回true-否则返回false。

要单独在POSIX-shell中执行此操作:

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"

甲POSIX -壳(其具有不早禁用-filename代)set所述"$@"位置参数阵列以任一文字串接着set命令的上方,或者在每个的一端由圆顶封装运营商产生的场。是否这样做取决于这些小球是否实际匹配任何东西。在某些shell中,您可以指示一个非解析的glob扩展为null-或根本不扩展。有时这可能是有益的,但它不是可移植的,并且经常会带来其他问题-例如必须设置特殊的shell选项,然后再取消设置它们。

处理空值参数的唯一可移植方法涉及空值或未设置的变量或~代字号扩展。顺便说一下,后者比前者安全得多。

-e如果指定的三个glob中的任何一个都不解析为一个以上的匹配项,则仅在Shell上方测试任何文件的存在性。因此,for循环只运行了三个或更少的迭代,并且仅在目录为空的情况下,或者在一个或多个模式仅解析为一个文件的情况下。该forbreak■如果任何水珠的代表实际的文件-和我在大部分的顺序有可能会安排在水珠到最低,应该几乎在第一次迭代,每次退出。

无论哪种方式,它都只涉及一个系统stat()调用-shell,并且ls都只需要stat()查询目录并列出其牙科报告包含的文件。与此相反的是findstat()您可能会在其中列出的每个文件的行为与之相反。


可以确认。我在编写的脚本中使用了mikeserv的初始示例。
奥西斯

这将报告为不存在或您没有读取访问权的空目录。对于第二个,如果您具有读取访问权但没有搜索访问权,则为YMMV。
斯特凡Chazelas

@StéphaneChazelas-也许,但是这样的报告将伴随错误消息通知用户。
mikeserv

1
仅在这种ls情况下。小球,[当他们无法访问时保持沉默。例如[ -e /var/spool/cron/crontabs/stephane ],当有一个具有该名称的文件时,将默默报告为false。
斯特凡Chazelas

还要注意,如果somedir是指向目录(或不是目录)的符号链接,则这些解决方案并不等效。
斯特凡Chazelas

6

使用GNU或现代BSD find,您可以执行以下操作:

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

(假设$dir看起来并不像一个find谓语(!(-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 ]抱怨[-z大多数系统上没有调用命令。您需要在方括号周围留空格

[ -z $dir ]发生,如果是真的dir是空的,并且是大多数其他值均为假dir,但它是不可靠的,例如它是真实的,如果值dir= -z-o -o -n -n始终在命令替换处使用双引号(这也适用于脚本的其余部分)。

[ -z "$dir" ]测试变量的值是否dir为空。变量的值是一个字符串,它恰好是目录的路径。这并没有告诉您有关目录本身的任何信息。

没有操作员可以测试目录是否为空,就像常规文件一样([ -s "$dir" ]即使目录为空,也是如此)。测试目录是否为空的一种简单方法是列出其内容。如果您得到空文本,则目录为空。

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

在不具有较老的系统ls -A,就可以使用ls -a,但随后...上市。

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

(不要缩进以该行开头的行,..因为字符串必须只包含换行符,不能包含换行符和多余的空间。)


您能否解释一下“ $(ls -A-“ $ dir”)”这一部分。括号内外的$有什么作用?
2015年

@buddhasreekanth $(…)命令替换
别再成为邪恶了'

@Giles“ $(ls -A-” $ dir“)”不能正常工作。
2015年

@buddhasreekanth有什么错误?复制粘贴。如果您只说“不工作”而没有确切解释您所观察到的内容,那么您不会指望人们会帮助您。
吉尔斯(Gilles)'所以

@吉尔斯这是错误。当我执行脚本./filestats时,将引发抛出错误。$ / filestats / home / sreekanth / testdir / aki / schatz最小文件大小:4096 / home / sreekanth / testdir / aki / schatz最大文件大小:4096 / home / sreekanth / testdir / aki / schatz平均文件大小:4目录为空。而不是显示“目录为空”。以最小尺寸,最大尺寸和平均尺寸显示。
2015年

3

您正在查看目录本身的属性。

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

您要计算其中有多少个文件:

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

因此,“目录为空”的测试是:

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

像这样:

$ 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

如果您没有对该目录的读取访问权或该目录不存在,则将报告该目录为空。
斯特凡Chazelas

1

我的tldr答案是:

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/..?*" ]
}

它符合POSIX,并且并不重要,它通常比列出目录并将输出通过管道传输到grep的解决方案更快。

用法:

if emptydir adir
then
  echo "nothing found" 
else 
  echo "not empty" 
fi

我喜欢答案https://unix.stackexchange.com/a/202276/160204,我将其重写为:

function emptydir {
  ! { ls -1qA "./$1/" | grep -q . ; }
}

它列出了目录并将结果通过管道传递到grep。相反,我提出了一个基于全局扩展和比较的简单函数。

function emptydir {   
 [ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}

此函数不是标准的POSIX,而是使用调用子shell $()。我首先解释这个简单的函数,以便以后可以更好地理解最终的解决方案(请参见上面的tldr答案)。

说明:

如果不进行扩展,则左侧(LHS)为空,目录为空时就是这种情况。nullglob选项是必需的,因为否则,如果没有匹配项,则glob本身就是扩展的结果。(当目录为空时,使RHS与LHS的glob匹配将不起作用,因为当LHS glob匹配名为glob本身的单个文件时会出现误报:*glob中的匹配*文件名中的子字符串。 )大括号表达式{,.[^.],..?}包含隐藏文件,但不包含 ...

因为shopt -s nullglob是在$()(子外壳程序)内部执行的,所以它不会更改nullglob当前外壳程序的选项,这通常是一件好事。另一方面,在脚本中设置此选项是一个好主意,因为在没有匹配项的情况下,全局变量很容易返回错误。因此,可以在脚本开始处设置nullglob选项,该函数将不需要它。让我们牢记这一点:我们想要一个与nullglob选项一起使用的解决方案。

注意事项:

如果我们没有目录访问权限,则该函数报告的内容与目录为空时相同。这也适用于列出目录并grep输出的函数。

shopt -s nullglob命令不是标准的POSIX。

它使用由创建的子外壳 $()。没什么大不了的,但是如果我们能避免的话,那很好。

优点:

并不是说它真的很重要,但是根据进程中内核所花费的CPU时间量来衡量,此功能比上一个功能快四倍。

其他解决方案:

我们可以删除非POSIX shopt -s nullglob的LHS命令,把串"$1/* $1/.[^.]* $1/..?*"在RHS并分别消除所出现的误报的时候我们只有命名的文件'*'.[^.]*..?*在目录:

function emptydir {
 [ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
 [ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}

如果没有该shopt -s nullglob命令,则删除子外壳现在很有意义,但是我们必须小心,因为我们希望避免单词拆分,但允许在LHS上进行全局扩展。尤其是避免单词拆分的引用是行不通的,因为它还可以防止全局扩展。我们的解决方案是单独考虑这些问题:

function emptydir {
 [ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
 [ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
 [ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}

我们仍然可以为各个glob进行单词拆分,但是现在可以了,因为只有在目录不为空时,它才会导致错误。我们添加了2> / dev / null,以在LHS上有许多与给定glob匹配的文件时丢弃错误消息。

我们回想起我们想要一种也可以与nullglob选项一起使用的解决方案。上面的解决方案使用nullglob选项失败,因为当目录为空时,LHS的也为空。幸运的是,它从未说过目录为空。它只能说它是空的。因此,我们可以单独管理nullglob选项。我们不能简单地添加案例[ "$1/"* = "" ]等,因为这些案例将扩展为[ = "" ]在语法上不正确的等。因此,我们改用 [ "$1/"* "" = "" ]etc.。我们不得不再次考虑三种情况*..?*以及.[^.]*相匹配的隐藏文件,但不...。如果我们没有nullglob选项,则这些不会干扰,因为它们也永远不会说不为null时为空。因此,最终提出的解决方案是:

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/..?*" ]
}

安全问题:

在一个空目录中创建两个文件,rm然后x*提示符下执行。该glob *将扩展为rm x,然后将其删除x。这与安全性无关,因为在我们的函数中,全局位置位于扩展的位置,而不是将其视为命令,而是将其视为参数,就像in中一样for f in *


$(set -f; echo "$1"/*)似乎是一种相当复杂的写作方式"$1/*"。这将与隐藏的目录或文件不匹配。
muru

我的意思是,有没有在文件名扩展"$1/*"(注意引号包含*),所以set -f和子shell是不必要的那个特别。
muru

它回答了一个不同的问题:目录是否包含非隐藏文件或目录。对此我感到抱歉。我很乐意将其删除。
Dominic108 '19

1
也有一些方法可以匹配隐藏文件(请参阅mikeserv的答案:中的复杂问题./* ./.[!.]* ./..?*)。也许您可以将其合并以完成功能。
muru

啊!我将研究并学习,也许我们会根据全球扩展获得完整的答案!
Dominic108 '19

0

这是另一种简单的方法。假设D是您要测试其是否为空的目录的全路径名。

然后

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

这行得通,因为

du -s D

作为输出

size_of_D   D

awk删除D,如果大小为0,则目录为空。


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

2
嗨,欢迎来到该网站。请不要发布仅仅是代码而没有解释的答案。注释代码或以其他方式解释您在做什么。顺便说一句,没有理由要使用echo,您所需要的只是list="$somedir/*"。而且,这很棘手,容易出错。您没有提到过OP应该给出目标目录的路径,例如包括斜杠。
terdon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.