【问题标题】:Is there a "goto" statement in bash?bash 中是否有“goto”语句?
【发布时间】:2012-03-09 18:26:47
【问题描述】:

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

【问题讨论】:

  • 不,bash 中没有 goto(至少它对我来说是 command not found)。为什么?有可能有更好的方法来做到这一点。
  • 他可能有他的理由。我发现这个问题是因为我想要一个 goto 语句跳过大量用于调试大型脚本的代码,而无需等待一个小时来完成各种不相关的任务。我当然不会在生产代码中使用goto,但是为了调试我的代码,它会让我的生活变得无比轻松,并且在删除它时更容易被发现。
  • @delnan 但是没有 goto 会使一些事情变得更复杂。确实有用例。
  • 我厌倦了这个 goto 神话! goto没有错!你写的所有东西最终都会变成 goto。在汇编程序中,只有 goto。在高级编程语言中使用 goto 的一个很好的理由是,例如以一种干净易读的方式跳出嵌套循环。
  • “避免 goto”是一个很好的规则。像任何规则一样,它应该分三个阶段学习。第一:遵守规则,直到它成为第二天性。第二,学会理解规则的原因。第三,在全面了解如何遵守规则和规则的原因的基础上,学习规则的良好例外。避免跳过上面的步骤,这就像使用“goto”。 ;-)

标签: linux bash shell goto


【解决方案1】:

如果您使用它来跳过大型脚本的一部分进行调试(请参阅 Karl Nicoll 的评论),那么如果 false 可能是一个不错的选择(不确定“false”是否始终可用,对我来说它位于 /bin /假):

# ... 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 行的注释,但它必须是你会记住的东西,我怀疑这将非常依赖于程序员。

【讨论】:

  • 是的,false 始终可用。但是,如果您有一段不想执行的代码,只需将其注释掉即可。或者删除它(如果您以后需要恢复它,请查看您的源代码控制系统)。
  • 如果代码块太长而无法一次繁琐地注释掉一行,请参阅这些技巧。 stackoverflow.com/questions/947897/… 但是,这些也不能帮助文本编辑器匹配从头到尾,所以它们并没有太大的改进。
  • "if false" 通常比注释掉代码要好得多,因为它确保了包含的代码仍然是合法代码。注释掉代码最好的借口是当它确实需要被删除,但有一些东西需要记住——所以它只是一个注释,不再是“代码”。
  • 我使用注释'#if false;'。这样我就可以搜索它并找到调试删除部分的开头和结尾。
  • 这个答案不应该被赞成,因为它不会以任何方式回应 OP 的问题。
【解决方案2】:

不,没有;有关确实存在的控制结构的信息,请参阅§3.2.4 "Compound Commands" in the Bash Reference Manual。特别要注意breakcontinue 的提及,它们不如goto 灵活,但在Bash 中比在某些语言中更灵活,并且可以帮助您实现您想要的。 (无论你想要什么......)

【讨论】:

  • 您能否详细说明“在 Bash 中比在某些语言中更灵活”?
  • @user239558:某些语言只允许您从最内层循环中使用breakcontinue,而 Bash 允许您指定要跳转的循环级别。 (即使是允许你从任意循环中breakcontinue 的语言,大多数都要求静态表达——例如,break foo; 将跳出标记为foo 的循环——而在 Bash 中它是动态表达——例如,break "$foo" 将跳出$foo 循环。)
  • 我建议定义具有所需功能的函数并从代码的其他部分调用它们。
  • @Sapphire_Brick:是的,我在你回复的评论中提到了这一点。
  • @ruakh 哦。。我的错。
【解决方案3】:

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

我发现 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 看起来像汇编语言的任务越来越接近完成。” - 哇。只是,哇。
  • 我唯一要改变的是让标签以: start: 开头,这样它们就不会出现语法错误。
  • 如果你这样做会更好: cmd=$(sed -n "/#$label:/{:a;n;p;ba};" $0 | grep -v ':$ ') 标签开头为:#start: => 这将防止脚本错误
  • 嗯,确实很可能是我的错误。我有编辑帖子。谢谢。
  • set -x 有助于了解正在发生的事情
【解决方案4】:

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

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

产生:

bar
star

【讨论】:

  • 请注意,这需要bash v4.0+。但是,它不是通用的goto,而是case 语句的替代选项。
  • 我认为这应该是答案。我确实需要 go to 以支持从给定指令恢复执行脚本。这是,除了语义,goto,语义和句法糖之外的所有方面都很可爱,但不是绝对必要的。很好的解决方案,IMO。
  • @nathang,是否是 the 答案取决于您的案例是否恰好与 OP 询问的一般案例的子集相吻合。不幸的是,这个问题询问的是一般情况,使得这个答案太窄而不能正确。 (这个问题是否应该关闭因为这个原因过于宽泛是一个不同的讨论)。
  • goto 不仅仅是选择。 goto 功能意味着能够根据某些条件跳转到某个地方,甚至可以创建一个像流一样的循环......
【解决方案5】:

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

标签是 cmets,所以如果禁用我们的 goto 不会导致语法错误(通过将 goto 设置为 ':' 无操作),但这意味着我们需要在我们的goto 声明。

无论何时使用任何类型的 goto 解决方案,您都需要小心,您跳过的代码不会设置您以后依赖的任何变量 - 您可能需要将这些定义移到您的顶部脚本,或者就在您的 goto 语句之一上方。

【讨论】:

  • 没有进入 goto 的好/坏,劳伦斯的解决方案很酷。你得到了我的投票。
  • 这更像是跳到,if(false) 似乎更有意义(对我来说)。
  • 引用 goto "label" 也意味着 here-doc 扩展/评估,这可以帮助您避免一些难以发现的错误(未引用,它会受到: 参数扩展、命令替换和算术扩展,这些都有副作用)。您也可以使用 alias goto=":&lt;&lt;" 稍微调整一下,完全不用 cat
  • 是的,vesperto,你可以使用 'if' 语句,我在很多脚本中都这样做过,但它变得非常混乱,更难控制和跟踪,尤其是如果你想要经常改变你的跳跃点。
  • 这很有趣。您可以通过解释 为什么 这有效,以及为什么 cat &lt;&lt; 命令可以解决问题来改进答案。
【解决方案6】:

虽然其他人已经澄清在 bash 中没有直接的 goto 等效项(并提供了最接近的替代方案,例如函数、循环和中断),但我想说明如何使用循环加上 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】:

    This solution 有以下问题:

    • 不加选择地删除所有以: 结尾的代码行
    • label: 一行中的任意位置视为标签

    这是一个固定的(shell-check clean 和 POSIX 兼容)版本:


    #!/bin/sh
    
    # GOTO for bash, based upon https://stackoverflow.com/a/31269848/5353461
    goto() {
      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
    

    【讨论】:

      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

        bash 中没有goto

        这是使用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&gt;/dev/null 隐藏了通常会显示的错误消息。

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


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

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

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

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

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

        【讨论】:

        • 这种形式和普通函数有什么区别?
        • @yurenchen - 将trap 视为使用exception handling 来实现代码流的更改。在这种情况下,陷阱会捕获导致脚本到达EXIT 的任何内容,这是通过调用不存在的命令goto 来触发的。顺便说一句:参数goto trap 可以是任何东西,goto ignored 因为它是导致退出的goto,而2&gt;/dev/null 隐藏了说明您的脚本正在退出的错误消息。
        • 我遇到以下错误:permission denied: /dev/nul
        【解决方案10】:

        我找到了一种使用函数的方法。

        例如,您有 3 个选择:ABCAB 执行命令,但 C 为您提供更多信息并再次将您带到原始提示。这可以使用函数来完成。

        请注意,由于包含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
        

        【讨论】:

          【解决方案11】:

          这是对 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..."
          

          【讨论】:

          • 这个复制粘贴不起作用 => 还有一个跳转。
          • 这是什么意思?什么不工作?你能说得更具体点吗?
          • 我当然试过代码!在发布全部成功之前,我在 5 或 6 种不同的条件下进行了测试。您的代码是如何失败的,什么错误消息或代码?
          • 随便什么人。我想帮助你,但你真的很模糊和不善于交流(更不用说侮辱“你测试了吗”的问题)。 “您没有正确粘贴”是什么意思?如果您指的是“/$#label#:/{:a;n;p;ba};”部分,我很清楚这是不同的。我将其修改为新的标签格式(又名 #label# vs :label 在其他答案中,在某些情况下会导致脚本崩溃)。您对我的回答是否有一些有效的问题(例如脚本崩溃或语法错误),还是因为您认为“我不知道如何剪切和粘贴”而只是 -1 我的回答?
          • @thebunnyrules 我遇到了和你一样的问题,但是solved it differently +1 为你的解决方案!
          【解决方案12】:

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

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

          结果是-> GOTO 完成

          【讨论】:

            【解决方案13】:

            我创建类似“goto”的想法是使用selectcase 并分配一个变量,然后我将其签入if 语句。不完美,但在某些情况下可能会有所帮助

            示例:

            #!/usr/bin/env bash
            
            select goto in Ubuntu Debian Quit ; do
                case $goto in
                    Ubuntu) { CHOICE="Ubuntu" ; break ; } ;;
                    Debian) { CHOICE="Debian" ; break ; } ;;
                    Quit)   { echo "Bye" ; exit ; } ;;
                    *)      { echo "Invalid selection, please try again..." ; } ;;
                esac
            done
            
            if [ "$CHOICE" == "Ubuntu" ]; then
                echo "I'm in Ubuntu"
            fi
            
            if [ "$CHOICE" == "Debian" ]; then
                echo "I'm in Debian"
            fi
            
            

            【讨论】:

              猜你喜欢
              • 2021-02-27
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-10-01
              • 2012-12-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多