我有以下代码
#!/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
当我运行此脚本时,它给出了一个错误:
缺少`]'
我该如何解决这个问题?
答案 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
有一个领先的破折号,而不仅仅是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" ]
这仍然无效。查看(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
更多信息:
答案 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
只会获取该列表并重现它,因此它是一个无操作。