如何检查目录是否为空?

如何检查目录是否为空?

./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.

答案1

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

上面是 POSIX 兼容测试 - 并且应该非常快。ls将列出目录中的所有文件/目录,除了...(来自-A)每行(来自-1)每个文件/目录,并将-q引用所有不可打印的字符(包括\nelines)在输出中带有?问号。这样,如果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-shell(之前没有禁用-f文件名生成)将位置参数set数组"$@"转换为上面命令后跟的文字字符串set,或者转换为每个末尾的 glob 运算符生成的字段。是否这样做取决于 glob 是否实际上匹配任何内容。在某些 shell 中,您可以指示非解析 glob 扩展为 null - 或根本不扩展。这有时是有益的,但它不可移植,并且通常会带来其他问题 - 例如必须设置特殊的 shell 选项,然后再取消设置。

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

-e如果指定的三个 glob 都解析为多个匹配项,则上面的 shell 仅测试任何文件是否存在。因此,for循环仅运行三次或更少的迭代,并且仅在空目录的情况下,或者在一个或多个模式仅解析为单个文件的情况下。如果任何一个 glob 代表一个实际的文件,那么它for也是breaks - 由于我已经按照最可能到最不可能的顺序排列了 glob,所以它几乎每次都会在第一次迭代时退出。

无论采用哪种方式,都应该只涉及一个系统stat()调用 - shell,并且ls应该只需要stat()查询目录并列出其文件目录项报告其中包含。这findstat()您可能列出的每个文件的行为形成鲜明对比。

答案2

使用 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 -qAH -- "$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

那一个检查那$dir是一个目录符号链接解析。

答案3

[-z $dir ][-z抱怨大多数系统上都没有调用命令。括号周围需要空格

[ -z $dir ]dir如果为空,则恰好为 true;对于 的大多数其他值,则为 false,但它是不可靠的,例如,如果的dir值为或 ,则为 true 。dir= -z-o -o -n -n始终在命令替换周围使用双引号(这也适用于脚本的其余部分)。

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

没有运算符可以测试目录是否为空,就像常规文件一样([ -s "$dir" ]对于目录来说也是如此,即使它是空的)。测试目录是否为空的一个简单方法是列出其内容;如果您得到空文本,则该目录为空。

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

在没有 的旧系统上ls -A,您可以使用ls -a,但随后会列出.和。..


if [ -z "$(LC_ALL=C ls -a -- "$dir")" = "$(printf '.\n..')" ]; then
...
fi

答案4

我的 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,并且使用$().我首先解释这个简单的函数,以便我们稍后可以更好地理解最终的解决方案(请参阅上面的 tldr 答案)。

解释:

当不发生扩展时,左侧(LHS)为空,即目录为空时的情况。 nullglob 选项是必需的,因为否则当没有匹配项时,glob 本身就是扩展的结果。 (当目录为空时,让 RHS 与 LHS 的 glob 匹配是行不通的,因为当 LHS glob 与名为 glob 本身的单个文件匹配时,会发生误报: glob 中的与文件名中的*子字符串匹配。 *) 大括号表达式{,.[^.],..?}覆盖隐藏文件,但不覆盖 ...

因为shopt -s nullglob是在(子 shell)内部执行的$(),所以它不会更改nullglob当前 shell 的选项,这通常是一件好事。另一方面,在脚本中设置此选项是个好主意,因为当没有匹配项时,glob 返回某些内容很容易出错。因此,可以在脚本开头设置 nullglob 选项,并且函数中不需要它。让我们记住这一点:我们需要一个与 nullglob 选项一起使用的解决方案。

注意事项:

如果我们没有对该目录的读取访问权限,该函数将报告与存在空目录相同的信息。这也适用于列出目录并 grep 输出的函数。

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

它使用由 $().这不是什么大事,但如果我们能避免它就好了。

专业人士:

这并不重要,但根据进程内内核所花费的 CPU 时间来衡量,该函数比前一个函数快四倍。

其他解决方案:

我们可以删除shopt -s nullglobLHS 上的非 POSIX 命令并将字符串放在RHS 中,并单独消除当我们只有名为、或在目录中的"$1/* $1/.[^.]* $1/..?*"文件时发生的误报:'*'.[^.]*..?*

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

如果没有该shopt -s nullglob命令,现在删除子 shell 是有意义的,但我们必须小心,因为我们希望避免分词,但又允许在 LHS 上进行全局扩展。特别是引用来避免分词是行不通的,因为它还会阻止全局扩展。我们的解决方案是单独考虑 glob:

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 选项。我们不能简单地添加 case[ "$1/"* = "" ]等,因为它们将扩展为 等[ = "" ],这在语法上是不正确的。所以,我们使用 [ "$1/"* "" = "" ]etc.来代替。我们必须再次考虑三种情况*..?*.[^.]*来匹配隐藏文件,但不是...。如果我们没有 nullglob 选项,这些不会造成干扰,因为它们也不会在它不为空时说它是空的。所以,最终提出的解决方案是:

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并在提示符下执行。该 glob将扩展为 ,并且将执行此操作以删除。这不是安全问题,因为在我们的函数中,glob 所在的扩展不被视为命令,而是被视为参数,就像在.x**rm xxfor f in *

相关内容