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 `ls -D /data/tftpboot/sb3???sh25t.sqfs` ; do
         break
      done
    build=${build:15}
 fi

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

缺少`]'

我该如何解决这个问题?

4

4 回答 4

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。但是,如果存在,该ls命令仍将返回,0如果不foo存在,则返回 a 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.backupfoofoomvifmv

这就是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

它还更清楚地说明了为什么它是-gtand-f带有前导破折号而不是简单地gtor f。那是因为-gt-f是 test 命令的参数!


现在您对内置 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"

引号使整个字符串成为测试命令的一个参数,这不是您想要的。您希望-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引号之外(引号也是!),我将-ato 和所有四个条件一起使用。

我也可以这样做:

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

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

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

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

[ -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与命令行的交互方式。

与其他操作系统中的对应物不同(咳咳, 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.???替换该模式。echo

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

$ 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`

该功能向您展示了在 shell 处理您的命令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参数仅采用单个文件名。因此,即使您完成了上述所有操作,您仍然可能会得到一个无法运行的程序。更糟糕的是,它会间歇性地失败,这更难调试。

解决此问题的一种方法是忘记 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.

碰巧你用这个问题中了大奖。如果你今天玩了乐透而不是写剧本,你可以告诉你的老板你对他们的真实看法,这是你的工作,然后搬到一个热带岛屿天堂,那里的文化如此原始,他们没有关于终端的词模拟器

相反,它明天回到盐矿。


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

于 2013-02-14T20:17:48.790 回答
0

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

于 2013-02-14T17:15:41.087 回答
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只会获取该列表并复制它,因此它是无操作的。

于 2013-02-14T17:32:26.953 回答
0

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

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

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

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

但最好使用

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

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

Bash 中的简单逻辑运算符

于 2013-02-14T17:17:15.310 回答