bash中是否有“goto”语句?我知道这被认为是不好的做法,但我需要特别“去”。


当前回答

它确实可能对一些调试或演示需求有用。

我发现Bob Copeland解决方案http://bobcopeland.com/blog/2012/10/goto-in-bash/优雅:

#!/bin/bash
# include this boilerplate
function jumpto
{
    label=$1
    cmd=$(sed -n "/$label:/{:a;n;p;ba};" $0 | grep -v ':$')
    eval "$cmd"
    exit
}

start=${1:-"start"}

jumpto $start

start:
# your script goes here...
x=100
jumpto foo

mid:
x=101
echo "This is not printed!"

foo:
x=${x:-10}
echo x is $x

结果:

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101

其他回答

还有一种能力可以达到预期的结果:命令陷阱。例如,它可以用于清理目的。

bash中没有goto。

这里有一些肮脏的工作方法,使用陷阱只向后跳转:)

#!/bin/bash -e
trap '
echo I am
sleep 1
echo here now.
' EXIT

echo foo
goto trap 2> /dev/null
echo bar

输出:

$ ./test.sh 
foo
I am
here now.

不应该以这种方式使用,而只能用于教育目的。以下是这种方法有效的原因:

Trap是使用异常处理来实现代码流中的更改。 在本例中,该陷阱将捕获导致脚本退出的任何内容。goto命令不存在,因此会抛出一个错误,该错误通常会退出脚本。此错误被trap捕获,2>/dev/null隐藏了通常显示的错误消息。

goto的这种实现显然是不可靠的,因为任何不存在的命令(或任何其他错误)都会执行相同的trap命令。特别是,您无法选择要使用哪个标签。


基本上在实际场景中,你不需要任何goto语句,它们是多余的,因为随机调用不同的地方只会让你的代码难以理解。

如果您的代码被多次调用,那么考虑使用loop并将其工作流更改为使用continue和break。

如果您的代码重复,请考虑编写函数并尽可能多次地调用它。

如果您的代码需要根据变量值跳转到特定的部分,那么可以考虑使用case语句。

如果可以将长代码分割成较小的片段,请考虑将其移动到单独的文件中,并从父脚本调用它们。

如果你使用它来跳过一个大型脚本的一部分进行调试(参见Karl Nicoll的评论),那么If false可能是一个很好的选择(不确定“false”是否总是可用,对我来说它在/bin/false中):

# ... Code I want to run here ...

if false; then

# ... Code I want to skip here ...

fi

# ... I want to resume here ...

当需要提取调试代码时,困难就出现了。“if false”结构是相当直接和容易记住的,但你如何找到匹配的fi?如果您的编辑器允许您阻止缩进,那么您可以缩进被跳过的块(然后当您完成时,您将希望将其放回)。或者fi线上的注释,但它必须是你能记住的东西,我怀疑这是非常依赖于程序员的。

一个简单的可搜索的goto,用于在调试时注释掉代码块。

GOTO=false
if ${GOTO}; then
    echo "GOTO failed"
    ...
fi # End of GOTO
echo "GOTO done"

结果是-> GOTO done

尽管其他人已经澄清了bash中没有直接的goto等价(并提供了最接近的替代方法,如函数、循环和break),但我想说明如何使用循环加break来模拟特定类型的goto语句。

我发现这种方法最有用的情况是,如果某些条件不满足,我需要返回到一段代码的开头。在下面的例子中,while循环将一直运行,直到ping停止向测试IP发送数据包。

#!/bin/bash

TestIP="8.8.8.8"

# Loop forever (until break is issued)
while true; do

    # Do a simple test for Internet connectivity
    PacketLoss=$(ping "$TestIP" -c 2 | grep -Eo "[0-9]+% packet loss" | grep -Eo "^[0-9]")

    # Exit the loop if ping is no longer dropping packets
    if [ "$PacketLoss" == 0 ]; then
        echo "Connection restored"
        break
    else
        echo "No connectivity"
    fi
done