使用 Bash 和 Dash,您可以仅使用 shell 检查空目录(为了简单起见,忽略点文件):
set *
if [ -e "$1" ]
then
echo 'not empty'
else
echo 'empty'
fi
然而我最近了解到 Zsh 在这种情况下失败了:
% set *
zsh: no matches found: *
% echo "$? $#"
1 0
因此,该命令不仅set
失败,而且甚至没有设置$@
。我想我可以测试 if $#
is 0
,但看起来 Zsh 甚至停止执行:
% { set *; echo 2; }
zsh: no matches found: *
与 Bash 和 Dash 比较:
$ { set *; echo 2; }
2
这可以通过在 bash、dash 和 zsh 中工作的方式来完成吗?
答案1
这有几个问题
set * if [ -e "$1" ] then echo 'not empty' else echo 'empty' fi
代码:
- 如果启用该
nullglob
选项(以前大多数其他 shell 都支持),则变为列出所有 shell 变量(以及某些 shell 中的函数)zsh
set *
set
- 如果第一个非隐藏文件的名称以
-
或开头+
,则它将被视为选项set
。在 zsh 和 ksh 的某些实现/版本中,这甚至使其成为命令注入漏洞,例如当存在名为+A[$(reboot)]
.这两个问题可以通过使用来解决set -- *
。 *
仅扩展非隐藏文件,因此不是测试目录是否为空,而是测试目录是否包含非隐藏文件。对于某些 shell,您可以使用dotglob
orglobdot
选项或使用FIGNORE
特殊变量(具体取决于 shell)来解决该问题。[ -e "$1" ]
测试stat()
系统调用是否成功。如果第一个文件是指向不可访问位置的符号链接,则将返回 false。您不需要stat()
(甚至不需要lstat()
)任何文件来知道目录是否为空,只需检查它是否有一些内容。*
扩展需要打开当前目录,检索所有条目,存储所有非隐藏条目并对其进行排序,这也是相当低效的。
检查目录是否为非空(具有除 和 之外的任何条目)的最有效方法.
是..
使用zsh
globF
限定符(F
for满的,这里表示非空):
if [ .(NF) ]; then
print . is not empty
else
print "it's empty or I can't read it"
fi
N
是nullglob
全局限定符。因此.(NF)
扩展到.
if.
已满,否则没有任何内容。
lstat()
在目录上之后,如果zsh
发现它的链接计数大于2,那么这意味着它至少有一个子目录,所以不为空,所以我们甚至不需要打开该目录(这也意味着,在在这种情况下,即使我们没有读取权限,我们也可以知道该目录非空)。否则,zsh 打开目录,读取其内容并停在第一个既不是也不是的条目处,.
而..
无需读取、存储或排序所有内容。
使用 POSIX shell(zsh
仅在模拟中表现出(更多)POSIXly sh
),仅使用 glob 检查目录是否为非空是非常尴尬的。
一种方法是:
set .[!.]* '.[!.]'[*] .[.]?* [*] *
if [ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ]; then
echo "empty or can't read"
else
echo not empty
fi
(假设没有与 glob 相关的选项从默认值更改(POSIX 仅指定noglob
)并且未设置GLOBIGNORE
(for bash
) 和FIGNORE
(for ) 变量,并且 (for ) 没有任何文件名包含不形成有效字符的字节序列)。ksh
yash
这个想法是,在 POSIX shell 中,当 glob 不匹配时,它不会展开(这是 70 年代末 Bourne shell 引入的错误功能)。因此set -- *
,如果我们得到$1
== *
,我们不知道是否是因为没有匹配项,或者是否存在名为 的文件*
。
您解决该问题的(有缺陷的)方法是使用[ -e "$1" ]
.在这里,我们使用set -- [*] *
.这可以消除两种情况的歧义,因为如果没有文件,上面的内容将保留[*] *
,如果有一个名为 的文件*
,则变为* *
.我们对隐藏文件执行类似的操作。这有点尴尬,因为 Bourne shell 的另一个缺陷(也由zsh
、Forsyth shell、pdksh 和修复fish
),其中 的扩展.*
确实包括特殊(伪)条目.
和..
当 报告时readdir()
。
因此,要使其在所有这些 shell 中工作,您可以这样做:
cwd_empty()
if [ -n "$ZSH_VERSION" ]; then
eval '! [ .(NF) ]'
else
set .[!.]* '.[!.]'[*] .[.]?* [*] *
[ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ]
fi
无论如何,zsh
默认情况下的语法与 POSIX sh 语法不兼容,因为它以非向后兼容的方式修复了 Bourne shell 中的大多数主要问题(早在 POSIX.2 首次发布之前),包括*
当没有匹配时保持未展开(Bourne shell 之前的 shell 没有这个问题,csh
并且tcsh
也fish
没有),并且.*
包括.
and..
但其他几个如 split+glob 在参数或算术扩展上执行,所以你不能期望代码用 POSIX sh 编写,zsh
除非您打开sh
仿真,否则始终可以工作。
该sh
模拟尤其存在,因此您可以在zsh
.
如果你想在 POSIX里面source
写入一个文件,你可以这样做:sh
zsh
emulate sh -c 'source ./that-file'
然后该文件将在仿真中进行评估sh
,并且其中声明的任何函数都将保留该仿真模式。
答案2
虽然 zsh 的默认行为是给出错误,这是由nomatch
选项控制的。您可以取消设置该选项,*
以像 bash 和 dash 那样保留原样:
setopt -o nonomatch
虽然该命令在其他任何一个命令中都不起作用,但您可以忽略它:
setopt -o nonomatch 2>/dev/null || true ; set *
它在 zsh 上运行,并抑制其他命令上失败命令的setopt
错误输出 ( 2>/dev/null
) 和返回代码 ( )。|| true
正如所写,如果有一个文件,例如:-e
那么您将运行set -e
并更改 shell 选项以在命令失败时终止;如果你有创造力,结果会更糟。set -- *
将更安全并防止选项更改。
答案3
Zsh 的语法与 sh 不兼容。它足够接近,看起来像 sh,但还不够接近,您可以获取 sh 代码并不加修改地运行它。
如果您想在 zsh 中运行 sh 代码,例如因为您有一个为 sh 编写的 sh 函数或代码片段并希望在 zsh 脚本中使用,则可以使用emulate
内置。例如,要在 zsh 脚本中获取为 sh 编写的文件:
emulate sh -c 'source /path/to/file.sh'
要使用 sh 语法编写函数或脚本并使其可以在 zsh 中运行,请将其放在开头附近:
emulate -L sh 2>/dev/null || true
在 sh 语法中,zsh 支持所有 POSIX 结构(它与bash --posix
ksh93 或 mksh 一样兼容 POSIX)。它还支持一些 ksh 和 bash 扩展,例如数组(在 ksh、bash 和 下为 0 索引emulate sh
,但在本机 zsh 中为 1 索引)和[[ … ]]
.如果您想要 POSIX sh 加上 ksh glob,请使用,emulate … ksh …
代替, 并为 bashemulate … sh …
添加(请注意,这对于脚本/函数来说不是本地的)。if [[ -n $BASH ]]; then shopt -s extglob; fi
.
枚举目录中除和之外的所有条目的本机 zsh 方法..
是
set -- *(DN)
这使用了全局限定符 D
包含点文件并N
在没有匹配项时生成一个空列表。
.
枚举目录中除和之外的所有条目的可移植 sh 方法..
要复杂得多。您需要列出点文件,如果您在当前目录或不能保证绝对路径中列出文件,则需要小心,以防文件名以破折号开头。这是一种方法,即使用模式..?* .[!.]* *
列出除 和 之外的所有文件.
,..
并删除未展开的模式。
set -- ..?*
if [ "$#" -eq 1 ] && ! [ -e "$1" ] && ! [ -L "$1" ]; then shift; fi
set -- .[!.]* "$@"
if [ "$#" -eq 1 ] && ! [ -e "$1" ] && ! [ -L "$1" ]; then shift; fi
set -- * "$@"
if [ "$#" -eq 1 ] && ! [ -e "$1" ] && ! [ -L "$1" ]; then shift; fi
如果您只想测试目录是否为空,那么有一种更简单的方法。
if ls -A /path/to/directory/ | grep -q '^'; then
echo "/path/to/directory is not empty"
else
echo "/path/to/directory is empty"
fi
答案4
我不确定解决方案set *
。这个怎么样?
dir="./empty"
has_file=false
for i in "$dir"/*; do
if test ! "$i" = "$dir/*"; then
has_file=true
echo "$i"
if
done
echo "$has_file"
经过测试,这对于zsh
, bash
, ash
, ksh
, bourne
AKA效果很好heirloom