Bashに "goto"ステートメントはありますか?私は知っていますそれは悪い習慣と考えられています、しかし私は特に "後藤"が必要です。
いいえ、ありません; do が存在する制御構造体についての情報は .2.4 "複合コマンド" Bashリファレンスマニュアル を参照のこと。特に、break
とcontinue
は、goto
ほど柔軟ではありませんが、Bashでは一部の言語よりも柔軟性があり、目的を達成するのに役立つ場合があります。 (あなたが望むものが何であれ…。)
あなたがデバッグのために大きなスクリプトの一部をスキップするためにそれを使っているなら(Karl Nicollのコメントを見てください)、falseが良い選択肢であるかもしれません:
# ... Code I want to run here ...
if false; then
# ... Code I want to skip here ...
fi
# ... I want to resume here ...
デバッグコードを切り取る時が来たときに、困難が生じます。 "もしfalseなら"という構文はとても簡単で思い出に残るものですが、どのようにして一致するfiを見つけますか?エディタでインデントをブロックできる場合は、スキップしたブロックをインデントすることができます(それが終わったら元に戻すことをお勧めします)。それともfi行のコメントですが、それはあなたが覚えているものでなければならないでしょう、私はそれがプログラマーに非常に依存すると思う。
それは確かにいくつかのデバッグやデモンストレーションのニーズに役立つかもしれません。
私はボブコープランドソリューション 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
Gotoをシミュレートするためにbashでcase
を使用することができます。
#!/bin/bash
case bar in
foo)
echo foo
;&
bar)
echo bar
;&
*)
echo star
;;
esac
生成します:
bar
star
Bashスクリプトをテスト/デバッグしていて、コードの1つ以上のセクションを過ぎて先にスキップしたい場合は、これを実行するための非常に簡単な方法があります。上記の)。
#!/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
を ':
' no-opに設定することによって)goto
を無効にしても構文エラーにはなりませんが、これはgoto
ステートメントで引用符を付ける必要があることを意味します。
どんな種類のgoto
解決策を使うときはいつでも、あなたが飛び越えているコードがあなたが後で頼るどんな変数も設定しないことに注意する必要があります - あなたはそれらの定義をあなたのスクリプトの一番上、またはちょうど上に動かす必要がありますあなたのgoto
ステートメントの一つ。
他の人たちはすでにbashに直接のgoto
同等物がないことを明確にしていました(そして、関数、ループ、そしてbreakのような最も近い代替手段を提供しました)、私はループプラスbreak
を使う方法を説明したいです特定の種類のgotoステートメントをシミュレートします。
これが最も役立つと思う状況は、特定の条件が満たされない場合にコードのセクションの先頭に戻る必要があるときです。以下の例では、pingがテストIPへのパケットのドロップを停止するまで、whileループが永久に実行されます。
#!/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
望ましい結果を達成するためのもう1つの機能があります:コマンド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
は通常表示されるエラーメッセージを隠します。
Gotoのこの実装は明らかに信頼できません。存在しないコマンド(またはその方法では他のエラー)が同じtrapコマンドを実行するためです。特に、どのラベルに移動するかを選択することはできません。
実際のシナリオでは基本的に、gotoステートメントは必要ありません。異なる場所へのランダムな呼び出しはコードの理解を困難にするだけなので、冗長です。
コードが何度も呼び出される場合は、ループを使用し、ワークフローを変更してcontinue
とbreak
を使用することを検討してください。
あなたのコードがそれ自身を繰り返すならば、関数を書いてあなたが望むようにそれを何度も呼び出すことを検討してください。
コードが変数値に基づいて特定のセクションにジャンプする必要がある場合は、case
ステートメントの使用を検討してください。
長いコードを小さい部分に分割できる場合は、それを別々のファイルに移動して親スクリプトから呼び出すことを検討してください。
この解決方法 には以下の問題がありました。
:
で終わるすべてのコード行を無差別に削除します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
デバッグ時にコードブロックをコメントアウトするための簡単な検索可能なジャンプ.
GOTO=false
if ${GOTO}; then
echo "GOTO failed"
...
fi # End of GOTO
echo "GOTO done"
結果は→GOTO完了
これは、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..."
関数を使ってこれを行う方法を見つけました。
たとえば、A
、B
、およびC
の3つの選択肢があるとします。 A
とB
はコマンドを実行しますが、C
はより多くの情報を提供し、再び元のプロンプトに戻ります。これは関数を使って行うことができます。
function demoFunction
を含む行は単に関数を設定しているだけなので、関数が実際に実行されるように、そのスクリプトの後にdemoFunction
を呼び出す必要があります。
シェルスクリプトの別の場所に "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