好的,给自己买一罐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,而不仅仅是gt 或f。那是因为-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/[ 命令。