有没有“goto”;声明在bash?

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

487845 次浏览

不,没有;有关存在的控制结构的信息,请参阅3.2.4 Bash参考手册 . conf中的“复合命令”。特别要注意的是breakcontinue,它们不像goto那么灵活,但在Bash中比在某些语言中更灵活,并可能帮助您实现您想要的。(无论你想要什么……)

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

#!/bin/bash


case bar in
foo)
echo foo
;&


bar)
echo bar
;&


*)
echo star
;;
esac

生产:

bar
star

如果你使用它来跳过一个大型脚本的一部分进行调试(参见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线上的注释,但它必须是你能记住的东西,我怀疑这是非常依赖于程序员的。

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

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

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

下面是一些肮脏的解决方法,使用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将捕获导致脚本EXIT的任何内容。命令goto不存在,因此会抛出一个错误,该错误通常会退出脚本。此错误被trap捕获,并且2>/dev/null隐藏了通常会显示的错误消息

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


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

如果你的代码被多次调用,那么考虑使用loop并将其工作流更改为使用continuebreak

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

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

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

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

例如,你有3个选择:ABCA和__abc1执行一个命令,但是C给你更多的信息,并再次将你带到原始的提示符。这可以使用函数来完成。

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

你可以很容易地通过编写多个其他函数并在需要在shell脚本的另一个地方“GOTO”时调用它们来适应这一点。

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

尽管其他人已经澄清了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

如果您正在测试/调试一个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语句之上。

这是Hubbbitus对Judy Schmidt剧本的一个小修正。

在脚本中放置未转义的标签会导致机器崩溃。这很容易通过添加#来转义标签来解决。感谢Alexej Magura和access_granting的建议。

#!/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..."

这个解决方案有以下问题:

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

这是一个固定的(shell-check干净和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

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

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

结果是-> GOTO done

我的想法是创造一个像“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


为什么没有人直接使用函数呢?
BTW函数比创建一个新东西

更容易处理

我的风格:

#!/bin/bash


# Your functions
function1 ()
{
commands
}


function2 ()
{
commands
}
:
:


functionn ()
{
commands
}


# Execute 1 to n in order
for i in {1..n}
do
function$i
done


# with conditions
for i in {1..n}
do
[ condition$i ] && function$i
done


# Random order
function1
functionn
function5
:
:
function3

以上风格的例子:

#!/bin/bash


# Your functions
function1 ()
{
echo "Task 1"
}


function2 ()
{
echo "Task 2"
}


function3 ()
{
echo "Task 3"
}


function1
function3
function2

输出:

Task 1
Task 3
Task 2

缺点:

  • 有组织地编写脚本。
  • 问题少,不容易出错。
  • 你可以在已有的函数中创建函数。
  • 来回移动没有任何问题。