bash中是否有一个“ goto”语句?


204

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


4
不,gotobash中没有(至少command not found对我来说是如此)。为什么?有一种更好的方法可以做到这一点。
Niklas B.

153
他可能有原因。我找到了这个问题,是因为我希望goto语句跳过用于调试大型脚本的大量代码,而不必等待一个小时来完成各种无关的任务。我当然不会goto在生产代码中使用a ,但是对于调试我的代码,这将使我的生活变得无限轻松,并且将其删除时也将更容易发现。
Karl Nicoll 2012年

30
@delnan但是没有goto会使某些事情变得更复杂。确实有用例。
glglgl

71
我讨厌这个神话!goto没错!您编写的所有内容最终都会变为goto。在汇编器中,只有goto。在高级编程语言中使用goto的一个很好的理由是,例如,以一种清晰易读的方式跳出嵌套循环。
亚历山大·祖卓

25
“避免转到”是一个很好的规则。像任何规则一样,应该分三个阶段进行学习。第一:遵循规则,直到成为第二自然。第二,学会理解规则的原因。第三,在全面了解如何遵循规则以及规则原因的基础上,学习规则的良好例外。避免跳过上面的步骤,就像使用“ goto”一样。;-)
杰夫李尔曼

Answers:


75

不,那里没有; 有关确实存在的控制结构的信息,请参见Bash参考手册》中的第3.2.4节“复合命令”。特别要注意的是,和的提及不如灵活,但在Bash中比某些语言更灵活,并且可以帮助您实现所需的目标。(无论您想要什么……。)breakcontinuegoto


10
您是否可以扩展“在Bash中比在某些语言中更灵活”?
2014年

21
@ user239558:某些语言只允许您进入breakcontinue退出最内层的循环,而Bash允许您指定要跳转的循环级别。(甚至是允许您进入breakcontinue退出任意循环的语言,大多数语言都要求将其静态表达-例如,break foo;将脱离标记的循环foo-而在Bash中,它是动态表达的-例如,break "$foo"将脱离$foo循环。 )
ruakh 2014年

我建议定义具有所需功能的功能,并从代码的其他部分进行校准。
blmayer

@ruakh实际上,很多语言都允许break LABEL_NAME;,而不是Bash令人讨厌的break INTEGER;
Sapphire_Brick

1
@Sapphire_Brick:是的,我在您要回复的评论中提到了这一点。
ruakh

123

如果您使用它跳过大型脚本的一部分进行调试(请参阅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 ...

困难在于何时需要删除调试代码。“如果为假”结构非常简单易记,但是如何找到匹配的fi?如果您的编辑器允许您阻止缩进,则可以缩进跳过的块(然后,您需要在完成后放回去)。或在fi线上发表评论,但这一定是您会记住的,我怀疑这将非常依赖于程序员。


6
false始终可用。但是,如果您有不想执行的代码块,请将其注释掉。或将其删除(如果以后需要恢复,请查看源代码管理系统)。
基思·汤普森

1
如果代码块太长而无法一次繁琐地注释掉一行,请参阅以下技巧。 stackoverflow.com/questions/947897/… 但是,这些也不能帮助文本编辑器从头到尾进行匹配,因此它们并没有太大的改进。
卡米尔·古德塞内

4
“ if false”通常比注释掉代码好得多,因为它确保了封闭的代码继续是合法代码。注释掉代码的最佳借口是确实需要删除代码的时候,但是有些事情需要记住-因此,它只是注释,而不再是“代码”。
杰夫·李尔曼

1
我使用注释“ #if false;”。这样,我就可以搜索并找到调试删除部分的开头和结尾。
乔恩

此答案不应被否决,因为它不会以任何方式回答OP的问题。
维克多·乔拉斯

54

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

我发现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

36
“我对使bash看起来像汇编语言的追求越来越接近完成。” - 哇。只是,哇。
Brian Agnew

5
我唯一要更改的就是使它开始,这样标签就: start:不会出现语法错误。
Alexej Magura

5
如果这样做会更好:cmd = $(sed -n“ /#$ label:/ {:a; n; p; ba};” $ 0 | grep -v':$'),标签开头为:#开始=>这将防止脚本错误
ACCESS_GRANTED

2
嗯,确实很可能是我的错误。我有编辑帖子。谢谢。
Hubbitus

2
set -x帮助了解发生了什么
John Lin

30

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

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

产生:

bar
star

4
请注意,这是必需的bash v4.0+。但是,它不是通用的,goto而是该case语句的后备选项。
mklement0

3
我认为这应该是答案。我真的有需要去支持从给定指令继续执行脚本。从所有方面来说,这都是语义,goto,而且语义和语法糖是可爱的,但并非绝对必要。IMO,很好的解决方案。
森·克

1
@nathang,无论是答案取决于你的案件是否发生与一般情况下,OP问的子集啮合。不幸的是,这个问题问的是一般情况,使这个答案太狭窄了而无法正确。(由于这个原因是否应该将问题归结为过于广泛是一个不同的讨论)。
Charles Duffy 2015年

1
goto不仅仅是选择。goto功能意味着能够根据某些条件跳到某些地方,甚至形成类似水流的循环……
Sergio Abreu

19

如果您正在测试/调试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"

要将脚本恢复正常,只需使用删除任何行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"

别名通常在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"

然后,您可以export DEBUG=TRUE在运行脚本之前执行此操作。

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

每当使用任何一种goto解决方案时,您都需要小心,要跳过的代码不会设置以后要依赖的任何变量-您可能需要将这些定义移到脚本的顶部或上方。您的goto陈述。


不用考虑goto的优缺点,Laurence的解决方案就很酷。你得到我的投票。
Mogens TrasherDK

1
对我来说,这更像是一个跳转,if(false)似乎更有意义。
vesperto

2
引用goto“标签”还意味着不会对here-doc 进行扩展/评估,从而使您免于陷入一些难以发现的错误(不加引号的情况下,它可能会受到以下影响:参数扩展,命令替换和算术扩展,所有这些都可以副作用)。您也可以对此稍作调整alias goto=":<<"cat完全省去。
spuratic先生,

是的,vesperto,您可以使用“ if”语句,我已经在许多脚本中做到了这一点,但是它变得非常混乱,并且很难控制和跟踪,特别是如果您想频繁更改跳转点。
劳伦斯·伦肖

12

尽管其他人已经阐明goto了bash中没有直接等效项(并提供了最接近的替代方法,例如函数,循环和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

7

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

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

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


#!/bin/bash

# GOTO for bash, based upon https://stackoverflow.com/a/31269848/5353461
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


4

gotobash中没有。

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

#!/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正在使用异常处理来实现代码流的更改。在这种情况下,trap会捕获导致脚本退出的所有内容。该命令goto不存在,因此会引发错误,该错误通常会退出脚本。正在捕获此错误trap,并且2>/dev/null隐藏了通常会显示的错误消息。

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


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

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

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

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

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


这种形式和普通函数有什么区别?
yurenchen

2
@yurenchen -想想trap作为使用exception handling实现代码流的变化。在这种情况下,陷阱将捕获导致脚本执行的所有操作EXIT,这是通过调用不存在的命令触发的goto。顺便说一句:该参数goto trap可以是任何值,goto ignored因为是goto导致退出的原因,并且2>/dev/null隐藏了错误消息,指出您的脚本正在退出。
杰西·奇斯霍尔姆

2

这是Hubbbitus编写的Judy Schmidt脚本的一个小修正。

在脚本中放置非转义标签在计算机上是有问题的,并导致其崩溃。通过添加#来使标签转义,这很容易解决。感谢Alexej Magura和access_granted的建议。

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

start=${1:-"start"}

goto $start

#start#
echo "start"
goto bing

#boom#
echo boom
goto eof

#bang#
echo bang
goto boom

#bing#
echo bing
goto bang

#eof#
echo "the end mother-hugger..."

此复制粘贴无效=>仍然有一个Jumpto。
亚历克西斯·帕克斯

那是什么意思?什么不起作用?你可以再详细一点吗?
thebunnyrules

当然我尝试了代码!发布成功之前,我在5或6种不同的条件下进行了测试。代码如何为您失败,出现什么错误消息或代码?
thebunnyrules

随便 我想为您提供帮助,但是您确实含糊不清且缺乏沟通能力(更不用说侮辱“您测试了它”这个问题)。“您未正确粘贴是什么意思”?如果您指的是“ / $#label#:/ {:a; n; p; ba};” 部分,我非常清楚这是不同的。我将其修改为新的标签格式(在其他答案中又称为#label#与:label,在某些情况下崩溃了脚本)。您的答案是否存在某些有效的问题(例如脚本崩溃或语法错误),还是因为您认为“我不知道如何剪切和粘贴”而只是将我的答案设为-1?
thebunnyrules

1
@thebunnyrules我遇到了与您相同的问题,但为您的解决方案+1的解决方式有所不同
Fabby

2

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

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

结果是-> GOTO完成


1

我发现了一种使用函数来做到这一点的方法。

说,例如,你有3种选择:AB,和CAB执行命令,但是C会提供更多信息,并再次带您到原始提示。可以使用函数来完成。

请注意,由于containg这行function demoFunction只是设置函数,因此您需要调用demoFunction在该脚本之后,以便函数实际运行。

您可以通过编写其他多个函数并在需要GOTO在shell脚本中的其他位置调用它们来轻松地调整它。

function demoFunction {
        read -n1 -p "Pick a letter to run a command [A, B, or C for more info] " runCommand

        case $runCommand in
            a|A) printf "\n\tpwd being executed...\n" && pwd;;
            b|B) printf "\n\tls being executed...\n" && ls;;
            c|C) printf "\n\toption A runs pwd, option B runs ls\n" && demoFunction;;
        esac
}

demoFunction
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.