我知道这被认为是不良实践,但我确切地需要一个"goto"。
如果您正在使用它来跳过大型脚本的某些部分进行调试(请参见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 ...
当删除调试代码时,难度就出现了。 "if false" 结构相当直观和易记,但是如何找到匹配的 fi 呢?如果您的编辑器允许块缩进,可以对跳过的代码块进行缩进(然后在完成后将其放回原位)。或者在 fi 行上添加注释,但它必须是您会记得的内容,我认为这将非常依赖于程序员。
false
始终可用。但是如果你有一段代码不想执行,可以将其注释掉或者删除它(如果需要后期恢复,可以查看源代码控制系统)。 - Keith Thompsonfalse
命令,不会触发操作系统/bin/false。因此,由于OP正在引用Bash,即使是那些没有/bin/false的“无发行版”Docker映像,false
也始终可用。 - SOFebreak
和continue
,它们虽然不像goto
那样灵活,但在Bash中比某些语言更灵活,并且可能有助于实现您想要的内容。(无论您想要什么……)break
或 continue
命令跳出,而 Bash 则允许你指定要跳过多少层循环。即使是那些允许你从任意循环结构中使用 break
或 continue
命令的编程语言,大多数也要求在代码中静态地标明要退出的循环结构 -- 例如,break foo;
会使代码跳出名为 foo
的循环结构 -- 而在 Bash 中则是动态地表示 -- 例如,break "$foo"
将会跳出 $foo
所表示的循环结构。 - ruakhbreak LABEL_NAME;
,而不是 Bash 中令人讨厌的 break INTEGER;
。 - Sapphire_Brick对于一些调试或演示的需要,它确实可能是有用的。
我发现 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
: start:
开头,这样它们就不会成为语法错误。 - Alexej Maguracmd=$(sed -n "/#$label:/{:a;n;p;ba};" $0 | grep -v ':$')
标签应该以以下方式开始:#start: => 这将防止脚本错误
- access_grantedset -x
帮助理解正在发生的事情。 - John Lin如果你正在测试/调试一个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
语句上面。
if(false)
似乎更有意义。 - vespertoalias goto=":<<"
进行微调,并完全省略cat
。 - mr.spuraticcat <<
命令如何起作用来改进答案。 - ysap您可以在bash中使用case
命令来模拟goto:
#!/bin/bash
case bar in
foo)
echo foo
;&
bar)
echo bar
;&
*)
echo star
;;
esac
产生:
bar
star
bash v4.0+
。然而,它并不是通用的 goto
,而是用于 case
语句的一个向下选择项。 - mklement0goto
等效语句(并提供了最接近的替代方案,如函数,循环和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
这个解决方案 存在以下问题:
:
结尾的代码行;label:
当作标签来处理。下面是已经修复(干净无误的 shell-check
和符合 POSIX 标准的)版本:
#!/bin/sh
# GOTO for bash, based upon https://dev59.com/5mkw5IYBdhLWcg3wzdpE#31269848
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
有一个实现所需结果的能力:命令trap
。例如,它可用于清理目的。
在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>/dev/null
隐藏了通常会显示的错误消息。continue
和break
。case
语句。trap
看作使用异常处理
来实现代码流的改变。在这种情况下,陷阱会捕获任何导致脚本退出
的东西,这是通过调用不存在的命令goto
来触发的。顺便说一句:参数goto trap
可以是任何东西,例如goto ignored
,因为是goto
导致了退出,并且2>/dev/null
隐藏了错误消息,说明您的脚本正在退出。 - Jesse Chisholmpermission denied: /dev/nul
。 - alperA
、B
和C
。 A
和 B
执行一个命令,但是 C
提供更多信息并将您带回原始提示符。这可以使用函数来完成。function demoFunction
的行只是设置函数,因此需要在该脚本之后调用 demoFunction
,以便函数实际运行。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
goto
命令(至少对我来说会显示“未找到命令”)。为什么呢?很可能有更好的方法来完成相同的任务。 - Niklas B.goto
语句来跳过大量代码,以便在调试一个大型脚本时不必等待一小时进行各种无关的任务。我肯定不会在生产代码中使用goto
,但对于调试我的代码来说,它会让我的生活变得轻松得多,而且在删除它时更容易发现它。 - Karl Nicoll