在 Zsh 中组合文件测试

Combining file tests in Zsh

zsh 中最优雅的测试方式是什么,文件是否是可读的常规文件?

我知道我可以做类似的事情

if [[ -r "$name" && -f "$name" ]]
...

但它需要重复两次“$name”。我知道我们不能组合条件 (-rf $name),但也许可以使用 zsh 中的其他一些功能?

顺便说一句,我也考虑过

if ls ${name}(R.) >/dev/null 2>&1
...

但在这种情况下,当 $name 不满足条件时,shell 会抱怨 "no matches found"。设置 NULL_GLOB 在这里也无济于事,因为它只会用空字符串替换模式,并且表达式始终为真。

在非常新的 zsh 版本中(适用于 5.0.7,但不适用于 5.0.5),您可以这样做

setopt EXTENDED_GLOB

if [[ -n $name(#qNR.) ]]
...

$name(#qNR.) 匹配名称为 $name 的可读 (R) 和常规 (.) 文件。 N 为该匹配项启用 NULL_GLOB。也就是说,如果没有文件与模式匹配,它不会产生错误,但会从参数列表中删除。 -n 检查匹配是否实际上是非空的。需要 EXTENDED_GLOB 来启用 (#q...) 类型的扩展通配符,这又是必需的,因为括号在条件表达式 ([[ ... ]]) 中通常具有不同的含义。


不过,虽然确实可以写出只使用一次 $name 的东西,但我还是建议不要这样做。它比原来的解决方案更复杂,因此对于下一个阅读它的人来说更难理解(即需要思考)(最多半年后你未来的自己算作 "next guy")。至少这个解决方案只适用于 zsh 并且只适用于新版本,而原始版本 运行 在 bash.

像你提到的那样使小(?)shell 函数怎么样?

tests-raw () {
  setopt localoptions no_ksharrays
  local then=""; shift
  local f="${@[-1]}" t=
  local -i ret=0
  set -- "${@[1,-2]}"
  for t in ${@[@]}; do
    if test "$t" "$f"; then
      ret=$?
      "$then"
    else
      return $?
    fi
  done
  return ret
}

and () tests-raw continue "${@[@]}";
or  () tests-raw break "${@[@]}";


# examples
name=/dev/null
if and -r -c "$name"; then
  echo 'Ok, it is a readable+character special file.'
fi
#>> Ok, it is...

and -r -f ~/.zshrc ; echo $? #>> 0
or  -r -d ~/.zshrc ; echo $? #>> 0
and -r -d ~/.zshrc ; echo $? #>> 1
# It could be `and -rd ~/.zshrc` possible.

虽然我觉得这有点矫枉过正。