为什么即使$ 1不是1,也总是选择'if [$ 1 =“ 1”]'分支?


10

我有一个名为“ teleport.sh”的shell脚本,如下所示:

if [ $1="1" ];
    then
    shift
        mv "$@" ~/lab/Sun
elif [ $1="2" ];
    then
    shift
        mv "$@" ~/lab/Moon
elif [ $1="3" ];
    then
    shift
        mv "$@" ~/lab/Earth
fi

当我执行时:

sh teleport.sh 2 testfile

testfile被移到了~/lab/Sun目录,这让我很困惑,因为我没有将1或1传递给该脚本。

怎么了


1
+1用于实验室太阳月亮地球传送。但是,您应始终用双引号引起来的扩展名($var,,$(cmd)甚至`cmd`[ $(cmd)应该首选])。还有一些边缘情况下,你不会报价,但总是这样做不会伤害。
nyuszika7h 2014年

@ nyuszika7h,双引号不应该表示“ $ var”和“ $ cmd”吗?您上面提到的圆括号有什么好处?
2014年

$(cmd)命令替换,(大多数情况下)与相同`cmd`。参见mywiki.wooledge.org/CommandSubstitutionmywiki.wooledge.org/BashFAQ/082
nyuszika7h 2014年

Answers:


19

使用空格可以解决您的问题。

if [ "$1" = 1 ];
    then
    shift
        mv "$@" ~/lab/Sun
elif [ "$1" = 2 ];
    then
    shift
        mv "$@" ~/lab/Moon
elif [ "$1" = 3 ];
    then
    shift
        mv "$@" ~/lab/Earth
fi

尽管这样更整洁:

#!/bin/bash

action=$1
shift
files=("$@")
case $action in  
  1) mv -- "${files[@]}" ~/lab/Sun     ;;
  2) mv -- "${files[@]}" ~/lab/Moon    ;;
  3) mv -- "${files[@]}" ~/lab/Earth   ;;
esac

3
是的,空格是必需的,但是我想知道为什么原始$1="1"语法完全“起作用”(产生真实结果)。[/ test内建函数实际上是如何解释该表达式的?
echristopherson 2014年

5
@echristopherson没有空格,test只能看到一个参数(“ l值”)。没有其他参数(“运算符”和“ r值”),没有什么可测试的,因此test只需说“好吧,是的,您给了我一些东西,而且必须虚假地正确”。
主教

2
$1="1"$1通过级联=1
jdh8

使用案例时,如何设置在不满足任何条件时执行的动作?
2014年

11

第一个明显的事情是你应该的参数之间提供空间[test[[

if [ "$1" = 1 ];

在Bash中时,[[ ]]建议使用using ,因为它不会对条件表达式(如单词拆分和路径名扩展)执行不必要的操作。也不需要用双引号引起来。==也可以使用更具可读性的运算符。

if [[ $1 == 1 ]];

补充说明:如果第二操作数还包含变量,报价是必要的,因为它可能会受到图案匹配,如果它包含可识别的字符,例如*?[]等。如果扩展通配符或模式匹配与启用shopt -s extglob,其它形式的喜欢@()!()等也将被识别为模式。请参阅模式匹配

有了这样运营商<>我曾经遇到过,其中没有引用造成不同的结果,第二个参数的错误它可能仍然是必要的。

至于第一个操作数,则不适用。

还要考虑以下更简单的变化:

case "$1" in
1)
    mv -- "${@:2}" ~/lab/Sun
    ;;
2)
    mv -- "${@:2}" ~/lab/Moon
    ;;
3)
    mv -- "${@:2}" ~/lab/Earth
    ;;
esac

或浓缩:

case "$1" in
1) mv -- "${@:2}" ~/lab/Sun ;;
2) mv -- "${@:2}" ~/lab/Moon ;;
3) mv -- "${@:2}" ~/lab/Earth ;;
esac

"${@:2}"是子字符串扩展或数组成员扩展的形式,其中2偏移量。这使扩展从第二个值开始。这样我们可能不需要使用shift

所添加的选项--可防止mv将以破折号(-)开头的文件名识别为无效选项。


你不应该在每种情况下都休息吗?
Archemar 2014年

2
@Archemar:不,没有陷阱(与许多其他语言相反)。
2014年

2
@Mat,如果您使用;&代替;;(仅限ksh,bash,zsh),则会遇到麻烦。但是那break仍然不能阻止失败,break只是打破循环。
斯特凡Chazelas

那不是命令的参数,if而是[命令的参数。
斯特凡Chazelas

1
更正:仅不需要在左手 双引号[[ $foo == $bar ]]将执行模式匹配,但[[ $foo == "$bar" ]]不会。
nyuszika7h 2014年

7

要回答为什么会发生的问题,这种行为[也称为test在POSIX文件

在下面的列表中,$ 1,$ 2,$ 3和$ 4表示要测试的参数:

[...]

1个论点:

如果$ 1不为空,则退出true(0); 否则,退出false。

您向其传递了1个参数,2=1该参数不为null,因此test成功退出。

至于其他职位(和shellcheck)指出,如果你想比较平等,你反而会必须通过3个参数2=1


3
从某种意义上讲,这是回答所问问题的唯一答案(而不是给出一个或多个可以完成OP想要完成的任务的菜谱),并且Shell可能足够神秘,知道为什么要这样做是有用的。
dmckee ---前主持人小猫,

1

我只想推荐一种便携式但更整洁的选择。Bash 不是通用的(如果不需要通用,为什么还要编写Shell脚本?)

#! /bin/sh
action="$1"
shift
case "$action" in
    1) dest=Sun   ;;
    2) dest=Moon  ;;
    3) dest=Earth ;;
    *) echo "Unrecognized action code '$action' (must be 1, 2, or 3)" >&2; exit 1 ;;
esac
mv -- "$@" ~/lab/"$dest"

(注意要学究:是的,我知道周围的报价$actioncase "$action" in线是不必要的,但我觉得这是最好把它们放在那里,无论如何,让未来的读者不必记住这一点。)


1
“ bash不是通用的(如果不需要通用,为什么还要编写shell脚本?)” –这似乎意味着bash脚本没有用例。
Ruslan 2014年

@Ruslan是的,这是我考虑的意见。/bin/sh如果需要,编写可移植的脚本;否则,请使用比shell糟糕的脚本语言编写。实际上,与Bash相比,基本Perl解释器可能出现在传统专有环境和精简嵌入式环境中。
zwol 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.