Bash检查显示不存在的文件存在文件?

rek*_*nuc 3 bash

在bash中运行以下命令:

   stuff=`rpm -ql <some package> | grep dasdasdfd`
Run Code Online (Sandbox Code Playgroud)

(包中不存在的文件,退出代码= 1,标准输出为空)

  if [ -f $stuff ]; then echo "whaaat"; fi
Run Code Online (Sandbox Code Playgroud)

上面的命令检查文件是否存在...但是:

file $stuff
Run Code Online (Sandbox Code Playgroud)

只需打印文件的使用信息......和

stat $stuff
Run Code Online (Sandbox Code Playgroud)

缺少操作数......

有人可以解释一下原因吗?这是一个错误吗?难道我做错了什么?我只是想确保包中的文件出现在fs上

Mar*_*oij 6

你可能需要$stuff用引号括起来

if [ -f "$stuff" ]; then
Run Code Online (Sandbox Code Playgroud)

作为一般规则,您几乎总是希望在使用它们的任何地方添加路径名周围的引号.

我觉得将shell脚本中的变量或变量视为"宏"更有用,它们在首次使用时会扩展为它们的值.这与几乎所有其他编程语言中的变量不同.

因此,如果$stuff包含hello world(注意空格),它将与您键入的内容相同:

[ -f hello world ]
Run Code Online (Sandbox Code Playgroud)

这显然是一个错误.

在这种情况下,您提到您正在处理一个不存在的文件,因此$stuff实际上是空的,这就像键入:

[ -f ]
Run Code Online (Sandbox Code Playgroud)

这实际上是有效的,但总是成功的.这是一个有点模糊的test行为,从我们读到的POSIX规范,test如果只有一个参数(在这种情况下,参数是-f),总是成功:

1参数:
如果$ 1不为null,则退出true(0); 否则,退出false.

这可能是为了方便撰写:

[ $variable_that_may_or_may_not_be_defined ]
Run Code Online (Sandbox Code Playgroud)

如果添加引号,则传递2个参数,并且会发生更多理智:

if [ -f "" ]; then
Run Code Online (Sandbox Code Playgroud)

  • `[string]`被解释为`[ - n string]`.`[-f]`相当于`[ - n -f]`,它总是为真:`-f`不是空字符串. (6认同)

mkl*_*nt0 6

Martin Tournoij 的答案DevSolar 的答案都提供了正确的解决方案和有用的背景信息:分别针对[ ... ]一种情况和[[ ... ]]另一种情况。

由于是否以及何时选择[[ ... ]][ ... ]及其(虚拟)别名)可能并不明显test ...,让我尝试总结一下:

  • 如果您的代码必须是可移植的(符合 POSIX 标准),则必须使用[ ... ](或test ...)
    • 内部的标记就像传递给可执行文件的参数一样进行解析[ ... ],因此您必须用双引号引用变量引用,除非您明确希望将所有 shell 扩展 - 特别是分词(通过空格自动拆分为多个标记)和通配符 - 应用于它们。
    • [ -f "$stuff" ] # double-quoting required, if $stuff has embedded whitespace
  • 如果您知道您的代码将运行bash您可以使用它[[ ... ]]来获得更多功能并减少意外
    • 内部的标记在特殊的上下文[[ ... ]]中进行解析,在该上下文中既不应用分词也不应用路径名扩展(通配符) (尽管确实会发生其他扩展,例如参数扩展),因此通常不需要双引号变量引用
    • [[ -f $stuff ]] # double-quoting optional

请注意,ksh并且zsh还支持[[ ... ]](大概在行为上有细微的变化)。

有关更多背景信息,例如[[ ... ]]提供的附加功能,请继续阅读。


[[ ... ]]改进[ ... ]/test ...如下

下面的“RHS”表示“右侧”,即二元运算符的右操作数。

  • (通常)不需要引用变量引用(除了在==和 的右侧=~指定文字字符串或子字符串)

    • f='some file'; [[ -f $f ]] # ok, double quotes optional
    • v='*'; [[ $v == '*' ]] # ok, double quotes optional
    • 内部不会应用分词或路径名扩展[[ ... ]],因此可以安全地使用对值嵌入空格和/或通常*会导致通配符的值的变量的不带引号的引用。
  • 提供与/匹配的字符串模式,在 RHS 上具有不带引号的模式(或至少不带引号的模式元字符。)===

    • [[ abc == a* ]] && echo yes # matches; use of = instead of == works too
    • 警告=:因此,在/的 RHS 上,如果==您希望变量引用(或单引号文字)的值被视为文字,则必须用双引号引起来。
      • v='a*'; [[ abc == "$v" ]] # does NOT match
  • 提供与 , 匹配的正则表达式=~,以及RHS 上 不带引号的扩展正则表达式(或至少不带引号的正则表达式元字符)。
    • [[ abc =~ ^a.+$ ]] && echo yes # matches
    • 警告:因此,如果=~您希望将变量引用(或单引号文字)的值视为文字,则必须在右侧使用双引号引起来。
      • v='a.+'; [[ abc =~ ^"$v"$ ]] # does NOT match
      • 另请注意,不带引号/带引号的区别仅在 bash 3.2 中引入 - 您仍然可以使用shopt -s compat31将单引号和双引号字符串视为正则表达式。
    • 警告: 所理解的正则表达式方言=~特定于平台的,因此在一个平台上工作的正则表达式可能无法在另一个平台上工作(这是 bash 的行为依赖于平台的少数情况之一)。例如,在 Linux 上,您可以使用\band \</\>进行字边界断言,而 BSD/macOS 仅支持[[:<]]and [[:>]],而 Linux 又不支持 - 请参阅我的这个答案。
  • 提供使用未转义的 (, ), 和!chars进行分组和否定。
  • 提供and(布尔 AND 和 OR)的使用&&||

    • [[ (3 -gt 2) && ! -f / ]] && echo yes
    • 请注意, inside [[ ... ]],&&的优先级高于||- ,与 OUTSIDE (所谓的 [command-]list 运算符,它们组合整个命令/命令列表)不同,它们具有相同的优先级。
    • (而[test都有 -a-o甚至 POSIX 规范也有关于test它们使用的警告
  • 在 中[[ ... ]],您可以将条件分布在多行中以提高可读性,而无需使用行继续字符。( \),假设换行符位于或 之后&&||codeforester指出的那样。

  • [[ ... ]]更快[ ... ]尽管这通常并不重要。


[test实施说明

  • [[a 是 shell关键字(在 、 和 中受支持bashkshzsh它允许不同的解析规则,如上所述。
  • 相比之下,[test是所有主要的类 POSIX shell 中的内置函数bash( , ksh, zsh, dash)。
  • 此外,按照 POSIX 的规定[, 和都test作为外部实用程序(需要单独进程调用的可执行文件)存在。
    • 事实上,您需要外部实用程序版本,以便能够在“无壳”调用场景中使用[or ,例如将测试传递给or时。testfind -execxargs
    • 虽然该[实用程序可以想象为实用程序的符号链接来实现 test(只要知道test它是如何调用的,并]在调用时强制关闭[),但实际上它们通常(总是?)单独的可执行文件(在 Linux 和 macOS / BSD 上是这样) ,例如;在 Linux 上,它们的内容不同,而在 macOS / BSD 上,它们的内容是相同的(它们是同一文件的副本)。