bash中是否有“ goto”语句?我知道这被认为是不好的作法,但是我需要特别的“ goto”。
goto
语句跳过用于调试大型脚本的大量代码,而不必等待一个小时来完成各种无关的任务。我当然不会goto
在生产代码中使用a ,但是对于调试我的代码,这将使我的生活变得无限轻松,并且将其删除时也将更容易发现。
bash中是否有“ goto”语句?我知道这被认为是不好的作法,但是我需要特别的“ goto”。
goto
语句跳过用于调试大型脚本的大量代码,而不必等待一个小时来完成各种无关的任务。我当然不会goto
在生产代码中使用a ,但是对于调试我的代码,这将使我的生活变得无限轻松,并且将其删除时也将更容易发现。
Answers:
不,那里没有; 有关确实存在的控制结构的信息,请参见《Bash参考手册》中的第3.2.4节“复合命令”。特别要注意的是,和的提及不如灵活,但在Bash中比某些语言更灵活,并且可以帮助您实现所需的目标。(无论您想要什么……。)break
continue
goto
break
或continue
退出最内层的循环,而Bash允许您指定要跳转的循环级别。(甚至是允许您进入break
或continue
退出任意循环的语言,大多数语言都要求将其静态表达-例如,break foo;
将脱离标记的循环foo
-而在Bash中,它是动态表达的-例如,break "$foo"
将脱离$foo
循环。 )
break LABEL_NAME;
,而不是Bash令人讨厌的break INTEGER;
。
如果您使用它跳过大型脚本的一部分进行调试(请参阅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线上发表评论,但这一定是您会记住的,我怀疑这将非常依赖于程序员。
false
始终可用。但是,如果您有不想执行的代码块,请将其注释掉。或将其删除(如果以后需要恢复,请查看源代码管理系统)。
对于某些调试或演示需求,它确实可能有用。
我发现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:
不会出现语法错误。
set -x
帮助了解发生了什么
您可以case
在bash 中使用它来模拟goto:
#!/bin/bash
case bar in
foo)
echo foo
;&
bar)
echo bar
;&
*)
echo star
;;
esac
产生:
bar
star
bash v4.0+
。但是,它不是通用的,goto
而是该case
语句的后备选项。
如果您正在测试/调试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)
似乎更有意义。
alias goto=":<<"
并cat
完全省去。
尽管其他人已经阐明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
该解决方案存在以下问题:
:
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
还有一种获得所需结果的能力:command trap
。例如,它可以用于清理目的。
goto
bash中没有。
这是一些肮脏的解决方法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语句,它们是多余的,因为随机调用不同位置只会使您的代码难以理解。
如果您的代码被多次调用,请考虑使用循环并将其工作流程更改为continue
和break
。
如果您的代码自行重复,请考虑编写函数并根据需要多次调用。
如果您的代码需要根据变量值跳到特定部分,请考虑使用 case
statement。
如果可以将长代码分成较小的部分,请考虑将其移动到单独的文件中,然后从父脚本中调用它们。
trap
作为使用exception handling
实现代码流的变化。在这种情况下,陷阱将捕获导致脚本执行的所有操作EXIT
,这是通过调用不存在的命令触发的goto
。顺便说一句:该参数goto trap
可以是任何值,goto ignored
因为是goto
导致退出的原因,并且2>/dev/null
隐藏了错误消息,指出您的脚本正在退出。
这是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..."
我发现了一种使用函数来做到这一点的方法。
说,例如,你有3种选择:A
,B
,和C
。A
并B
执行命令,但是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
goto
bash中没有(至少command not found
对我来说是如此)。为什么?有一种更好的方法可以做到这一点。