【问题标题】:Why do I get "missing `]'" from this bash script to list files?为什么我从这个 bash 脚本中得到“缺少`]'”来列出文件?
【发布时间】:2013-01-30 13:47:32
【问题描述】:

我有以下代码

#!/bin/bash
 if [   "-f /data/sb3???sh25t.sqfs" && "! -f /data/sb3??????sh25t.sqfs"  && "! -f /data/sb3????????sh25t.sqfs" && "! -f /data/sb3???????sh25t.sqfs" ]  ;then
      select build in `ls -D /data/tftpboot/sb3???sh25t.sqfs` ; do
         break
      done
    build=${build:15}
 fi

当我运行这个脚本时,它给了我一个错误:

缺少`]'

我该如何解决这个问题?

【问题讨论】:

  • 你能用语言描述你想要做什么吗?如果我们知道它应该做什么,而不是尝试修复您当前的操作方式,我们也许可以提供更好的解决方案。
  • 我的数据文件夹中有以下文件,它们有不同的长度 1-ab393_ef25t.txt 2-ab393_v2_ef25t.txt 3-ab393_V2_ef25t.txt 4-ab393_eng_ef25t.txt 5-ab393_ENG_ef25t.txt 6-ab393_abcd_ef25t. txt 7-ab393_prod_ef25t.txt 这是我想做的事情: if [ -f 1 && ! -f 2 && ! -f 3 && ! -f 4] 如果 [ -f 1 && -f 2 && ! -f 3 && ! -f 4 ] 然后只列出 1 和 2(小写和大写)

标签: bash


【解决方案1】:

使用-a 而不是&&,并且不要在"! -f /filename" 等表达式周围添加双引号。另请注意,如果有多个文件匹配该模式,-f /data/sb3????????sh25t.sqfs 将不起作用。

【讨论】:

  • 是因为答案中提到的原因还是其他原因?
【解决方案2】:

你也有&&在单[] 这是不允许的

要么使用[[]],要么像这样取出&&

[ -f file1 ] && [ -f file2 ]

由于您使用的是 glob,因此不能使用以下语法:

不过最好用

[[ -f file1 && -f file2 ]]

请注意,后者仅适用于bash 更多信息在这里:

Simple logical operators in Bash

【讨论】:

  • 请注意,他使用 glob 作为文件名,在 [[ ]] 中没有扩展
  • @thatotherguy 好点。但是他仍然可以在[之外使用&&的第一种语法]
  • 我的数据文件夹中有以下文件,它们的长度不同 1-ab393_ef25t.txt 2-ab393_v2_ef25t.txt 3-ab393_V2_ef25t.txt 4-ab393_eng_ef25t.txt 5-ab393_ENG_ef25t.txt 6-ab393_abcd_ef25t. txt 7-ab393_prod_ef25t.txt 这是我想做的事情: if [ -f 1 && ! -f 2 && ! -f 3 && ! -f 4] 如果 [ -f 1 && -f 2 && ! -f 3 && ! -f 4 ] 然后只列出 1 和 2(小写和大写)
【解决方案3】:

你的代码最好写成

#!/bin/bash
if [   -f /data/sb3???sh25t.sqfs ] &&
   [ ! -f /data/sb3??????sh25t.sqfs ] &&
   [ ! -f /data/sb3???????sh25t.sqfs ] &&
   [ ! -f /data/sb3????????sh25t.sqfs ]; then
   select build in /data/tftpboot/sb3???sh25t.sqfs ; do
       break
   done
   build="${build:15}"
fi

(我重新排序了 f 测试,以便更容易看到您正在测试的文件名中的模式。)

不要引用-f 运算符(但 习惯于引用操作数)并将每个测试放在单独的test/[ 命令中。无需在您的select 命令中运行ls,因为模式扩展已经生成了可供选择的文件列表; ls 只会获取该列表并复制它,所以它是无操作的。

【讨论】:

  • 如果 /data/sb3???sh25t.sqfs 确实是一个 glob 而不是某种形式的代码混淆,它就不会用双引号展开。
  • 好点。考虑到它在select 语句中的使用,它很可能是一个全局变量。
  • 我的数据文件夹中有以下文件,它们的长度不同 1-ab393_ef25t.txt 2-ab393_v2_ef25t.txt 3-ab393_V2_ef25t.txt 4-ab393_eng_ef25t.txt 5-ab393_ENG_ef25t.txt 6-ab393_abcd_ef25t. txt 7-ab393_prod_ef25t.txt 这是我想做的事情: if [ -f 1 && ! -f 2 && ! -f 3 && ! -f 4] 如果 [ -f 1 && -f 2 && ! -f 3 && ! -f 4 ] 然后只列出 1 和 2(小写和大写)
【解决方案4】:

好的,给自己买一罐Jolt Cola 然后坐下。这需要一段时间...

Unix shell 中的if 语句接受一个命令(任何命令),处理该命令,并执行 if 语句的 then 子句命令返回退出代码为零。如果该命令返回一个非零值,它将执行 else 子句(如果存在)。

例如:

if ls foo 2>&1 > /dev/null
then
   echo "File 'foo' exists"
else
   echo "No Foo For you!"
fi

上面将运行ls foo 命令。输出将通过2>&1 > /dev/null 删除,因此如果foo 存在,您将看不到列表,如果foo 不存在,您将不会收到错误消息。但是,如果foo 存在,ls 命令仍将返回0,如果不存在则返回1

这使您可以做各种整洁的事情:

if mv foo foo.backup
then
    echo "Renamed `foo` directory to `foo.backup`"
else
    echo "BIG HORKING ERROR: Can't rename 'foo' to 'foo.backup'"
    exit 2  #I need to stop my program. This is a big problem.
fi

在上面的代码中,我尝试将我的目录foo 重命名为foo.backup,然后再放入一个新的foo 目录。也许目录foo 不存在,所以mv 命令失败。也许我没有重命名目录的权限。不管是什么,if 语句让我可以测试我的mv 命令是否成功。

这就是if命令所做的全部:它执行一个命令,根据返回值是否为零,它可能会或可能不会执行then子句。


但是,如果我想测试某个特定条件是否存在怎么办?例如:

  • 是否有一个名为foo 的目录?
  • 我的环境变量$value的值是否大于3?

如果有某种命令可以让我测试这些东西不是很好吗?嘿,如果我的测试为真则该命令返回零值,如果我的测试为假则返回非零值,我可以将其合并到if 语句中!

幸运的是,Unix 中有一个名为 test 的命令正是这样做的:

if test -d foo
then
    echo "Directory foo exists"
fi

if test $value -gt 3
then
    echo "\$value is bigger than 3"
fi

恰好 Unix 命令test 有一个到[ 命令的硬链接

$ cd /bin
$ ls -li test [
54008404 -rwxr-xr-x  2 root  wheel  18576 Jul 25  2012 [
54008404 -rwxr-xr-x  2 root  wheel  18576 Jul 25  2012 test

第一列是inode 号码。由于两者的 inode 编号相同,[ 命令只是到test 命令的硬链接。

现在,事情变得更容易理解了。 [1 实际上是一个 Unix 命令!这就是为什么我需要在方括号和我正在测试的东西之间留一个空格:

# Invalid Syntax: I need spaces between the test and the braces:
if [$value -gt 3]

# Valid Syntax
if [ $value -gt 3 ]

# Actually the same command as above
if test value -gt 3

这也更清楚地说明了为什么它是带有前导破折号的-gt-f,而不仅仅是gtf。那是因为-gt-f 是测试命令的参数!


现在您对内置 shell if 的工作原理以及 [ 实际上是一个 Unix 命令的工作原理有了更清晰的了解,我们可以看看您的 if 语句 看看问题是:

if [ "-f "/data/sb3???sh25t.sqfs" && "! -f /data/sb3??????sh25t.sqfs"  && \
   "! -f /data/sb3????????sh25t.sqfs" && "! -f /data/sb3???????sh25t.sqfs" ]

首先,你有:

"-f /data/sb3???sh25t.sqfs"

引号使整个字符串成为 test 命令的一个参数,这不是您想要的。您希望 -f 参数与您正在测试的文件分开。因此,您需要从引号中删除 -f,以便 test 命令将其视为参数:

if [ -f "/data/sb3???sh25t.sqfs" && ! -f "/data/sb3??????sh25t.sqfs"  && \
   ! -f "/data/sb3????????sh25t.sqfs" && ! -f "/data/sb3???????sh25t.sqfs" ]

这仍然行不通。查看test 命令的(手册页)[http://www.manpagez.com/man/1/test/]。请注意,&& 不是test 命令的有效参数。相反,您应该使用-a 来组合两个表达式_您的测试:

if [   -f "/data/sb3???sh25t.sqfs"      -a ! -f "/data/sb3??????sh25t.sqfs"  -a \
     ! -f "/data/sb3????????sh25t.sqfs" -a ! -f "/data/sb3???????sh25t.sqfs" ]
then

-f 在引号之外(! 也是如此),我将-a 与所有四个条件一起使用。

我也可以这样做:

if [ -f "/data/sb3???sh25t.sqfs" ]      && [ ! -f "/data/sb3??????sh25t.sqfs" ] && \
   ! -f "/data/sb3????????sh25t.sqfs" ] && [ ! -f "/data/sb3???????sh25t.sqfs" ]

&& 实际上是一个 bash shell 结构,称为 list 运算符。它将两个单独的命令链接在一起,并执行以下操作:

  • 运行第一个命令。
  • 如果第一个命令返回非零值,则运行第二个命令。否则,返回第一个命令的退出代码,不要运行第二个命令。

例如,看到这样的事情是很常见的:

[ -d foo ] && rm -rf foo

或(相同的东西):

test -d foo && rm -rf foo

在上述命令中,我正在测试foo 是否作为目录存在。如果它确实存在,我的测试命令会返回一个真值,然后我执行删除目录的第二个命令。和……是一样的。

if [ -d foo ]
then
   rm -rf foo
fi

因此,我的 if 语句由四个单独的命令组成。第一个[...] 执行,并查看文件/data/sb3???sh25t.sqfs 是否实际存在。如果是,则该测试为真,返回零退出代码。然后,&& 执行列表中的下一条命令。

下一个测试命令查看/data/sb3??????sh25t.sqfs 是否存在。如果该文件不存在,则测试命令返回零退出代码,并且下一个&& 列表运算符运行下一个测试命令。只有前三个test 命令为真,才会执行最后一个测试命令。如果最后一个测试命令也是一个真实的语句,那么你的 then 子句将被执行。


我希望您了解 if 现在的工作原理以及您遇到问题的原因。

遗憾的是,还有一个问题正在发生,这就是 Unix shell 的工作方式以及 globbing 与命令行的交互方式。

与其他操作系统中的对应物(cough Windows!cough)不同,Unix shell 非常智能,也非常爱管闲事。

从命令行试试这个命令:

$ echo "Print an *"   #Use quotes
Print an *

现在试试这个:

$ echo Print an *    #No quotes
Print an bin foo bar...

这会打印所有“Print an”,然后是当前目录中的所有文件。

试试这个:

$ touch foo.out foo.txt
$ echo "Do I have any foo.??? files"
Do I have any foo.??? files
$ echo Do I have any foo.??? files
Do I have any foo.out foo.txt files

再一次,没有引号,我要求它打印出来的内容发生了变化。这是因为 Unix shell 很麻烦。发生的情况是 Unix shell 看到 globbing 模式 foo.??? 并在 echo 命令之前用 all 匹配文件替换该模式有机会呼应任何东西。

您可以通过以下方式看到这种情况:

$ set -x   #Turns on `xtrace`
$ touch foo.out foo.txt
+ touch foo.out foo.txt
$ echo "Do I have any foo.??? files"
+ echo "Do I have any foo.??? files"
Do I have any foo.??? files
$ echo Do I have any foo.??? files
+ echo Do I have any foo.out foo.txt files
Do I have any foo.out foo.txt files
$ set +x   #Turns off `xtrace`

xtrace 功能向您显示您将在 shell 处理您的命令之后执行的命令。以+ 开头的行显示了您的命令将实际执行的内容。

我提出这个问题的原因是一个可能的问题。如果您碰巧有多个文件与您的模式匹配,则所有与您的模式匹配的文件都将被替换到您的测试命令中。让我们再看一遍命令:

if [ -f /data/sb3???sh25t.sqfs ] &&        [ ! -f /data/sb3??????sh25t.sqfs ] && \
   [ ! -f /data/sb3????????sh25t.sqfs ] && [ ! -f /data/sb3???????sh25t.sqfs ]

如果您有一个文件 /data/sb3aaash25t.sqfs 和一个名为 /data/sb3bbbsh25t.sqfs 的文件,则 if 语句中的第一个测试命令将如下所示:

if [-f /data/sb3aaash25t.sqfs /data/sb3bbbsh25t.sqfs ] 

这可能不起作用,因为-f 参数只接受一个文件名。因此,即使您完成了上述所有操作,您仍然可能会得到一个无法运行的程序。更糟糕的是,它会间歇性地失败,这更难调试。

解决此问题的一种方法是忘记 test 命令,并使用 ls 命令,就像您在此答案的最顶部看到的那样:

if ls /data/sb3???25t.sqfs 2>&1 > /dev/null
then
   echo "At least one file that matches pattern sb3???25t.sqfs exists"
fi

因此,您可以这样做:

if   ls /data/sb3???sh25t.sqfs       2>&1 > /dev/null && \
   ! ls /data/sb3??????sh25t.sqfs    2>&1 > /dev/null && \
   ! ls /data/sb3????????sh25t.sqfs  2>&1 > /dev/null && \
   ! ls /data/sb3???????sh25t.sqfs   2>&1 > /dev/null
then

因为ls 可以容纳多个文件。

对于冗长的解释,我很抱歉。 99% 的时间,你可以继续你的快乐的方式,而不需要了解 if 语句是如何工作的,或者 [...] 真的是一个完全独立于 if 的 Unix 命令,或者那个爱管闲事的 Unix shell 将如何绊倒你。

恰好你在这个问题上中了大奖。如果你今天玩了乐透而不是写剧本,你可以告诉你的老板你对他们的真实看法,完全是你的工作,然后搬到一个文化如此原始的热带岛屿天堂,他们对终端模拟器。

相反,明天又要回盐矿了。


1 是的,是的。我知道 [ 内置在 BASH shell 中,而不是 /bin/[ 命令。但是,[ 中内置的 shell 的行为类似于 /bin/[ 命令。

【讨论】:

  • 在浏览时发现了这个问题,停下来阅读您的惊人答案,并为此感到更聪明,这就是 SO 摇滚的原因。 :) 请继续这样回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-22
  • 1970-01-01
  • 2014-09-18
  • 1970-01-01
  • 1970-01-01
  • 2012-03-27
相关资源
最近更新 更多