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

在bash中运行以下内容:

stuff=`rpm -ql <some package> | grep dasdasdfd` 

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

  if [ -f $stuff ]; then echo "whaaat"; fi 

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

 file $stuff 

只需打印文件…的使用信息

 stat $stuff 

缺less操作数…

有人可以解释为什么吗? 这是一个错误? 我做错了什么? 我只想确保包中的文件存在于fs中

你可能需要在引号中包含$stuff

 if [ -f "$stuff" ]; then 

作为一般规则,您几乎总是希望在所有使用path名的地方添加引号。

我觉得将shell脚本中的variables或想法变成“macros”更加有用,它们在首次使用时被扩展为它们的值。 这与几乎所有其他编程语言中的variables都不同。

所以如果$stuff包含hello world (注意空间),这将是相同的,如果你已经键入:

 [ -f hello world ] 

这显然是一个错误。

在这种情况下,你提到你正在处理一个不存在的文件,所以$stuff实际上是空的,这就像input:

 [ -f ] 

这实际上是有效的,但总是成功的。 这是一个晦涩难懂的test行为,从POSIX规范我们读到,如果只有一个参数(在这种情况下,参数是-f ),那么test总是成功的:

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

这可能有助于写作:

 [ $variable_that_may_or_may_not_be_defined ] 

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

 if [ -f "" ]; then 

如Carpetsmoker所说,一种select是把$stuff放在引号中。

但是,因为这是标签bash,并且因为迎合文件名空白是一个痛苦,你可以去:

 if [[ -f $stuff ]] 

[这是test的别名]相反, [[ construct]“知道”如何正确处理$stuff的内容。

Carpetsmoker的答案和DevSolar的答案都提供了正确的解决scheme和有用的背景信息:在一个案例中, [ ... ]在另一个案例中。

因为如果和何时select[[ ... ]]及其(虚拟)别名, test ... ),可能并不明显,请让我尝试一下总结:

  • 如果您的代码必须是可移植的 (符合POSIX) ,您必须使用(或test ...
    • 除非您明确希望所有的shell扩展 – 特别是分词(通过空格自动分割成多个标记)和通配符(globbing),否则就像在传递给可执行文件的参数一样 ,适用于他们。
    • [ -f "$stuff" ] # double-quoting required, if $stuff has embedded whitespace
  • 如果您知道您的代码将以bash运行,您可以使用[[ ... ]]获取更多function,减less意外
    • [[ ... ]]内的令牌在一个特殊的上下文中被parsing,在这个特殊的上下文中, 既不应用分词也不应该使用path名扩展(globbing) (尽pipe其他的扩展,例如参数扩展也会发生),所以通常不需要加倍- 引用variables引用
    • [[ -f $stuff ]] # double-quoting optional

请注意, kshzsh也支持[[ ... ]] (假设行为有细微的变化)。

有关更多背景信息,请参阅[[ ... ]]提供的附加function。


[[ ... ]]改进如下

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

  • (通常)不需要引用variables引用 (除了===~的RHS以指定文字string或子string)

    • f='some file'; [[ -f $f ]] # ok, double quotes optional
    • v='*'; [[ $v == '*' ]] # ok, double quotes optional
    • [[ ... ]]内部不应用单词拆分或path名扩展,因此可以安全地使用未加引号的引用来引用其值已embedded空格和/或值(例如*variables,这些variables通常会导致通配符。
  • 提供与= / == 匹配的string模式 ,在RHS(或至less不带引号的模式元字符)上带有一个不带引号的模式。

    • [[ abc == a* ]] && echo yes # matches; use of = instead of == works too
    • 警告 :因此,在= / ==的RHS上,如果您希望将其值作为文字对待,则必须双引号引用variables(或单引号文字)。
      • v='a*'; [[ abc == "$v" ]] # does NOT match
  • 提供=~匹配的正则expression式,在RHS(或至less不带引号的正则expression式)上带有一个没有引号的正则expression式。
    • [[ abc = ^a.+$ ]] && echo yes # matches
    • 注意 :因此,如果您希望将其值作为文字对待,则在=~的RHS上, 必须重复引用variables引用(或单引号文字)。
      • v='a.+'; [[ abc =~ ^"$v"$ ]] # does NOT match
      • 另外请注意,引号/引号的区别仅在bash 3.2中引入 – 您仍然可以使用shopt -s compat31将单引号和双引号string视为正则expression式。
    • 警告 :由=~理解的正则expression式是特定于平台的 ,所以在一个平台上工作的正则expression式可能不适用于另一个平台(这是bash行为依赖于平台的less数情况之一)。 例如,在Linux上,你可以使用\b\< / \>来进行字边界断言,而BSD / OSX只支持[[:<]][[:>]] , t支持 – 请参阅https://stackoverflow.com/a/12696899/45375
  • 提供分组和否定与()! 字符
  • 使用&&|| (布尔AND和OR)
    • [[ (3 -gt 2) && ! -f / ]] && echo yes
    • 请注意,在[[ ... ]]&&优先级高于|| – 与OUTSIDE(不同于所谓的[command-]列表操作符,它们将整个命令/命令列表结合在一起)不同,它们具有相同的优先级。
    • (虽然[test-a-o ,即使是POSIX规范,以防test注意事项,请参阅http://man.cx/test )。
  • [[ ... ]][ ... ] ,但这通常不重要。

执行说明重新[test

  • [[ a是shell 关键字 (在bashkshzsh ),允许使用不同的分析规则,如上所述。
  • 相比之下, [test 内置在所有主要POSIX类似的shell( bashkshzshdash )中。
  • 另外,正如POSIX所要求的 , [test都以外部实用程序 (需要单独进程调用的可执行文件)的forms存在。
    • 事实上,您需要外部实用程序版本,以便能够使用[或在“无shell”调用场景中testing,例如在通过testing来find -execxargs
    • 虽然[实用程序可以被想象成是作为符号连接到test实用程序(只要test知道如何被调用并强制closures),当调用为[ )时,实际上它们通常(总是)单独的可执行文件(true例如Linux和OSX / BSD;在Linux上,它们的内容不同,而在OSX / BSD上它们的内容是相同的(它们是相同文件的副本))。