8 bash zsh dash wildcards portability
使用 Bash 和 Dash,您可以仅使用 shell 检查空目录(忽略点文件以保持简单):
set *
if [ -e "$1" ]
then
echo 'not empty'
else
echo 'empty'
fi
Run Code Online (Sandbox Code Playgroud)
但是我最近了解到 Zsh 在这种情况下失败了:
% set *
zsh: no matches found: *
% echo "$? $#"
1 0
Run Code Online (Sandbox Code Playgroud)
所以不仅set命令失败,而且它甚至没有设置$@. 我想我可以测试 if $#is 0,但似乎 Zsh 甚至停止执行:
% { set *; echo 2; }
zsh: no matches found: *
Run Code Online (Sandbox Code Playgroud)
与 Bash 和 Dash 比较:
$ { set *; echo 2; }
2
Run Code Online (Sandbox Code Playgroud)
这可以以在 bash、dash 和 zsh 中工作的方式完成吗?
虽然 zsh 的默认行为是给出错误,但这是由nomatch选项控制的。您可以取消设置选项,以*bash 和 dash 的方式保留原位:
setopt -o nonomatch
Run Code Online (Sandbox Code Playgroud)
虽然该命令在其他任何一个中都不起作用,但您可以忽略它:
setopt -o nonomatch 2>/dev/null || true ; set *
Run Code Online (Sandbox Code Playgroud)
这setopt在 zsh 上运行,并抑制其他命令的失败命令的错误输出 ( 2>/dev/null) 和返回代码 ( || true)。
正如所写的那样,如果有一个文件,则有问题,例如-e:然后您将运行set -e并更改 shell 选项以在命令失败时终止;如果你有创造力,结果会更糟。set -- *将更安全并防止选项更改。
有几个问题
Run Code Online (Sandbox Code Playgroud)set * if [ -e "$1" ] then echo 'not empty' else echo 'empty' fi
代码:
nullglob选项(从zsh但现在大多数其他 shell 支持),则set *变为set列出所有 shell 变量(和某些 shell 中的函数)-或开头+,它将被视为一个选项set。这两个问题可以通过使用set -- *来解决。*仅扩展非隐藏文件,因此它不是测试目录是否为空,而是测试它是否包含非隐藏文件。对于某些外壳,您可以使用dotglob或globdot选项或FIGNORE根据外壳使用特殊变量来解决该问题。[ -e "$1" ]测试stat()系统调用是否成功。如果第一个文件是指向不可访问位置的符号链接,则将返回 false。您不需要stat()(甚至不需要lstat())任何文件来知道目录是否为空,只需检查它是否包含一些内容。* 扩展涉及打开当前目录,检索所有条目,存储所有非隐藏条目并对其进行排序,这也是相当低效的。检查目录是否为非空(除了.and之外的任何条目..)的最有效方法zsh是使用Fglob 限定符(F对于full,这里表示非空):
if [ .(NF) ]; then
print . is not empty
else
print "it's empty or I can't read it"
fi
Run Code Online (Sandbox Code Playgroud)
N是nullglob全局限定符。所以.(NF)扩展为.if.已满,否则什么都没有。
在lstat()目录之后,如果zsh发现它的link-count大于2,那么这意味着它至少有一个子目录所以不为空,所以我们甚至不需要打开那个目录(这也意味着,在那在这种情况下,即使我们没有读取权限,我们也可以判断该目录是非空的)。否则,zsh 打开目录,读取其内容并在第一个条目处停止,该条目既不是.也..不必读取、存储或排序所有内容。
使用 POSIX shell(zsh仅在sh模拟中表现(更多)POSIXly ),仅使用 glob来检查目录是否为非空是非常尴尬的。
一种方法是:
set .[!.]* '.[!.]'[*] .[.]?* [*] *
if [ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ]; then
echo "empty or can't read"
else
echo not empty
fi
Run Code Online (Sandbox Code Playgroud)
(假设没有从默认值(POSIX 仅指定noglob)更改与 glob 相关的选项并且未设置GLOBIGNORE(for bash) 和FIGNORE(for ksh) 变量,并且 (for 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
Run Code Online (Sandbox Code Playgroud)
在任何情况下,zsh默认情况下的语法与 POSIX sh 语法不兼容,因为它以非向后兼容的方式修复了 Bourne shell(早在 POSIX.2 首次发布之前)中的大多数主要问题,包括*左膨胀但如果没有匹配(PRE-的Bourne shell没有这个问题,csh,tcsh并fish做也不),以及.*包括.和..,但几个类似拆分+水珠在参数算术扩展执行的,所以你不能指望代码写在 POSIX sh 中,zsh除非您打开sh仿真,否则始终可以使用。
该sh仿真尤其存在,因此您可以在zsh.
如果你想source在 POSIXsh里面写一个文件zsh,你可以这样做:
emulate sh -c 'source that-file'
Run Code Online (Sandbox Code Playgroud)
然后将在sh仿真中评估该文件,并且其中声明的任何函数都将保留该仿真模式。