在bash中有"goto"声明吗?

kof*_*cii 186 linux bash shell goto

在bash中有"goto"声明吗?我知道这被认为是不好的做法,但我需要特别"goto".

Mic*_*sch 116

如果你使用它来跳过部分大脚本进行调试(参见Karl Nicoll的评论),那么如果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 ...
Run Code Online (Sandbox Code Playgroud)

当需要删除调试代码时,困难就出现了."if false"构造非常简单且令人难忘,但是如何找到匹配的?如果您的编辑器允许您阻止缩进,您可以缩进跳过的块(然后您将在完成后将其放回).或者对fi行发表评论,但它必须是你会记得的东西,我怀疑它会非常依赖程序员.

  • 是的`false`始终可用.但是,如果您有一段不想执行的代码,请将其注释掉.或删除它(如果您需要稍后恢复,请查看源代码管理系统). (6认同)
  • "if false"通常远比评论代码好得多,因为它确保所附的代码仍然是合法代码.注释掉代码的最好理由是它真的需要删除,但有一些东西需要记住 - 所以它只是一个注释,而不再是"代码". (4认同)
  • 我使用评论'#if false;'。这样我就可以搜索它并找到调试删除部分的开头和结尾。 (3认同)
  • 这个答案不应该被赞成,因为它没有以任何方式回应OP的问题。 (2认同)

rua*_*akh 72

不,那里没有; 见§3.2.4"复合命令"中的Bash参考手册,了解有关控制结构信息存在.特别要注意提及breakcontinue,它们不像gotoBash 那样灵活,但在某些语言中比Bash更灵活,可以帮助你实现你想要的.(无论你想要什么......)

  • @ user239558:某些语言只允许你从最里面的循环中"break"或"continue",而Bash允许你指定要跳转的循环级别.(甚至允许你从任意循环中"断开"或"继续"的语言,大多数都需要静态表达 - 例如,`break foo;`将突破标记为`foo`的循环 - 而在Bash它是动态表达的 - 例如,'break'$ foo"`将突破`$ foo`循环.) (21认同)
  • 您是否可以扩展"在Bash中比在某些语言中更灵活"? (8认同)
  • @Sapphire_Brick:是的,我在您回复的评论中提到了这一点。 (2认同)

Hub*_*tus 49

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

我发现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
Run Code Online (Sandbox Code Playgroud)

结果是:

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101
Run Code Online (Sandbox Code Playgroud)

  • "我对使用bash看起来像汇编语言的追求越来越接近完成." - 哇.哇,哇. (32认同)
  • 我唯一要改变的是让标签以这样的方式启动`:start:`以便它们不是语法错误. (5认同)
  • 如果你这样做会更好:cmd = $(sed -n"/#$ label:/ {:a; n; p; ba};"$ 0 | grep -v':$')标签的开头为:# start:=>这可以防止脚本错误 (5认同)
  • 嗯,确实很可能是我的错误。我有编辑帖子。谢谢。 (2认同)
  • set -x有助于理解发生了什么 (2认同)

Pau*_*nan 28

您可以case在bash 中使用来模拟goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac
Run Code Online (Sandbox Code Playgroud)

生产:

bar
star
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这需要`bash v4.0 +`.然而,它不是通用的"goto",而是`case`语句的直接选项. (4认同)
  • 我认为这应该是答案.我真的需要去支持从给定指令恢复执行脚本.这是各种方式,但语义,goto,语义和句法糖都很可爱,但并非绝对必要.伟大的解决方案,IMO. (3认同)

Lau*_*haw 16

如果您正在测试/调试bash脚本,并且只想跳过前面一个或多个代码段,那么这是一个非常简单的方法,以后也很容易找到和删除(与大多数方法不同)如上所述).

#!/bin/bash

echo "Run this"

cat >/dev/null <<GOTO_1

echo "Don't run this"

GOTO_1

echo "Also run this"

cat >/dev/null <<GOTO_2

echo "Don't run this either"

GOTO_2

echo "Yet more code I want to run"
Run Code Online (Sandbox Code Playgroud)

要使脚本恢复正常,只需删除任何行GOTO.

我们还可以通过添加goto命令作为别名来美化此解决方案:

#!/bin/bash

shopt -s expand_aliases
alias goto="cat >/dev/null <<"

goto GOTO_1

echo "Don't run this"

GOTO_1

echo "Run this"

goto GOTO_2

echo "Don't run this either"

GOTO_2

echo "All done"
Run Code Online (Sandbox Code Playgroud)

别名通常不能用于bash脚本,所以我们需要shopt命令来修复它.

如果你想能够启用/禁用你goto的,我们还需要更多:

#!/bin/bash

shopt -s expand_aliases
if [ -n "$DEBUG" ] ; then
  alias goto="cat >/dev/null <<"
else
  alias goto=":"
fi

goto '#GOTO_1'

echo "Don't run this"

#GOTO1

echo "Run this"

goto '#GOTO_2'

echo "Don't run this either"

#GOTO_2

echo "All done"
Run Code Online (Sandbox Code Playgroud)

然后你可以export DEBUG=TRUE在运行脚本之前做.

标签是注释,因此如果禁用我们goto的(通过设置goto:'no-op),不会导致语法错误,但这意味着我们需要在我们的goto语句中引用它们.

无论何时使用任何类型的goto解决方案,您都需要注意,您跳过的代码不会设置您以后依赖的任何变量 - 您可能需要将这些定义移到脚本的顶部,或者只是在一个以上你的goto陈述.

  • 在不讨论 goto 的好/坏的情况下,劳伦斯的解决方案很酷。你得到了我的投票。 (3认同)
  • 引用 goto "label" 也意味着 here-doc *未* 扩展/评估,这使您免于一些难以找到的错误(未引用,它将受到:参数扩展、命令替换和算术扩展,这可以都有副作用)。你也可以用 `alias goto=":&lt;&lt;"` 稍微调整一下,完全省去 `cat`。 (2认同)

Set*_*ley 11

虽然其他人已经澄清说gotobash中没有直接的等价物(并提供了最接近的替代方法,如函数,循环和中断),但我想说明如何使用循环加上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
Run Code Online (Sandbox Code Playgroud)


Ser*_*sak 6

还有一种能够实现预期结果的能力:命令trap.例如,它可用于清理目的.


ken*_*orb 6

gotobash 中没有。

这是一些肮脏的解决方法,使用trap它只向后跳转:)

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

echo foo
goto trap 2> /dev/null
echo bar
Run Code Online (Sandbox Code Playgroud)

输出:

$ ./test.sh 
foo
I am
here now.
Run Code Online (Sandbox Code Playgroud)

这不应该以这种方式使用,而只能用于教育目的。这就是为什么它有效的原因:

trap就是利用异常处理来实现代码流程的改变。在这种情况下,trap捕获任何导致脚本退出的内容。该命令goto不存在,因此会引发错误,通常会退出脚本。此错误被 捕获trap,并且2>/dev/null隐藏通常显示的错误消息。

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


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

如果您的代码被调用多次,请考虑使用循环并将其工作流程更改为使用continueand break

如果您的代码自我重复,请考虑编写该函数并根据需要多次调用它。

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

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

  • @yurenchen - 将“陷阱”视为使用“异常处理”来实现代码流的更改。在这种情况下,陷阱捕获任何导致脚本“EXIT”的内容,这是通过调用不存在的命令“goto”触发的。顺便说一句:参数 `goto trap` 可以是任何东西,`gotoignoreed` 因为它是 `goto` 导致退出,而 `2&gt;/dev/null` 隐藏了指示脚本正在退出的错误消息。 (2认同)

Tom*_*ale 5

该解决方案存在以下问题:

  • 随意删除以a结尾的所有代码行 :
  • Treates label:上线作为标签的任何地方

这是一个固定的(shell-check干净的)版本:


#!/bin/bash

# GOTO for bash, based upon /sf/answers/2188889391/
function goto
{
 local label=$1
 cmd=$(sed -En "/^[[:space:]]*#[[:space:]]*$label:[[:space:]]*#/{:a;n;p;ba};" "$0")
 eval "$cmd"
 exit
}

start=${1:-start}
goto "$start"  # GOTO start: by default

#start:#  Comments can occur after labels
echo start
goto end

  # skip: #  Whitespace is allowed
echo this is usually skipped

# end: #
echo end
Run Code Online (Sandbox Code Playgroud)