./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
引用所有不可打印的字符(包括\n
elines)在输出中带有?
问号。这样,如果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
也是break
s - 由于我已经按照最可能到最不可能的顺序排列了 glob,所以它几乎每次都会在第一次迭代时退出。
无论采用哪种方式,都应该只涉及一个系统stat()
调用 - shell,并且ls
应该只需要stat()
查询目录并列出其文件目录项报告其中包含。这find
与stat()
您可能列出的每个文件的行为形成鲜明对比。
答案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 nullglob
LHS 上的非 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 x
x
for f in *