为什么我从这个bash脚本中“删除`]'”列出文件?

时间:2013-02-14 17:08:24

标签: bash

我有以下代码

#!/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

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

  

缺少`]'

我该如何解决这个问题?

4 个答案:

答案 0 :(得分:5)

好的,给自己一个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没有&#,您将不会收到错误消息39; t存在。但是,如果ls存在,则0命令仍将返回foo,如果不存在则1仍然会返回。{/ p>

这可以让你做各种简洁的事情:

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" ]

这仍然无效。查看(man page)[http://www.manpagez.com/man/1/test/]查看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" ]

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

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

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

[ -d foo ] && rm -rf foo

或(同一件事):

test -d foo && rm -rf foo

在上面的命令中,我正在测试以查看foo是否作为目录存在。如果它确实存在,我的test命令返回一个true值,然后我执行第二个删除目录的命令。它与......相同。

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

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

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


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

可悲的是还有一个问题正在发生,那就是Unix shell如何工作以及globbing如何与命令行交互。

与其他操作系统中的对应物不同,(咳嗽 Windows!咳嗽),Unix shell非常聪明,而且非常有用。

从命令行尝试此命令:

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

现在试试这个:

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

这将打印所有"打印"然后是当前目录中的所有文件。

试试这个:

$ 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.???并用{strong> 所有 匹配文件替换该模式echo 1}}命令有机会回应任何东西。

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

$ 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功能会向您显示在 之后执行 的命令所执行的命令。以+开头的行显示您的命令将实际执行的内容。

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

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参数只占用一个文件名。因此,即使你做了上面提到的所有事情,你仍然可能最终得到一个非工作程序。更糟糕的是它会间歇性地失败,这更难以调试。

解决这个问题的一种方法是忘记测试命令,并使用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将如何绊倒你。

事实上你已经用这个问题击中了累积奖金。如果你今天玩过Lotto而不是写剧本,你可以告诉你的老板你对他们的看法,你的工作,以及移居到一个热带岛屿的天堂,那里的文化是如此原始,他们没有任何关于< em>终端模拟器

相反,它明天会回到盐矿。


1 是的,是的。我知道[内置于BASH shell中,并且不是/bin/[命令。但是,[内置的shell就像/bin/[命令一样。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

此外,您&&内有[ ] 这是不允许的

使用[[]]或者像这样取出&&

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

由于您使用的是globs,因此无法使用以下语法:

但最好使用

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

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

Simple logical operators in Bash

答案 3 :(得分:0)

您的代码更好地写为

#!/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/[命令中。无需在ls命令中运行select,因为模式扩展已经生成了可供选择的文件列表; ls只会获取该列表并重现它,因此它是一个无操作。