好吧,给自己拿一罐 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.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
and-f
带有前导破折号而不是简单地gt
or 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
引号之外(引号也是!
),我将-a
to 和所有四个条件一起使用。
我也可以这样做:
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/[
命令一样。