bashに「goto」ステートメントはありますか?


205

bashに「goto」ステートメントはありますか?私はそれが悪い習慣と考えられていることを知っていますが、私は特に「後藤」が必要です。


4
いいえ、gotobashにはありません(少なくともそれはcommand not found私に言っています)。どうして?それを行うにはもっと良い方法があるでしょう。
Niklas B.

153
彼には理由があるかもしれません。goto大きなスクリプトをデバッグするために、関係のないさまざまなタスクが完了するのを1時間待たずにステートメントで多くのコードをスキップするため、この質問が見つかりました。私は確かにgoto本番コードでa を使用しませんが、私のコードをデバッグするために、それは私の人生を無限に容易にし、それを削除するようになったときにそれを見つけるのが簡単になります。
Karl Nicoll、2012

30
@delnanしかし、gotoがないと、いくつかのことが複雑になる可能性があります。確かにユースケースがあります。
glglgl 2013年

71
私はこの後藤神話にうんざりしています!gotoには何の問題もありません。書き込んだものは最終的にgotoになります。アセンブラでは、gotoしかありません。高級プログラミング言語でgotoを使用する理由は、たとえばネストされたループからクリーンで読みやすい方法でジャンプすることです。
Alexander Czutro 14

25
「後藤を避ける」は素晴らしいルールです。他のルールと同様に、3つのフェーズで学習する必要があります。最初に、それが第二の性質になるまでルールに従います。次に、ルールの理由を理解することを学びます。3番目に、ルールに従う方法とルールの理由の包括的な理解に基づいて、ルールの適切な例外を学びます。「goto」を使用するような、上記の手順をスキップしないでください。;-)
ジェフリアマン2017年

回答:


75

いいえ、ありません; ご覧に§3.2.4「化合物のコマンドを」手動バッシュリファレンス制御構造については行うが存在します。特に、breakおよびの言及に注意してくださいcontinue。これらは、ほど柔軟ではありませんgotoが、Bashでは一部の言語よりも柔軟であり、目的を達成するのに役立つ場合があります。(あなたが望むものは何でも……)


10
「一部の言語よりもBashの方が柔軟性がある」について詳しく教えてください。
user239558 14

21
@ user239558は:一部の言語では唯一にあなたを許可するbreakか、continueバッシュは、あなたがジャンプするどのように多くのループのレベルを指定することができます一方、最も内側のループから。(さらにあなたを許す言語のbreakcontinue、例えば-任意のループから、ほとんどはそれが静的に表現する必要がbreak foo;ラベルされたループから抜け出すだろうfoo- Bashの中で、それは動的に表現しています一方で-例えば、break "$foo"から抜け出すだろう$fooループ。 )
ruakh 2014

必要な機能を備えた関数を定義し、コードの他の部分から計算することをお勧めします。
blmayer

@ruakh実際、多くの言語break LABEL_NAME;では、Bashの嫌なものではなく、を許可していbreak INTEGER;ます。
Sapphire_Brick

1
@Sapphire_Brick:はい、私はあなたが返信しているコメントでそれを述べました。
ruakh

124

デバッグのために大きなスクリプトの一部をスキップするためにそれを使用している場合(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行のコメントですが、覚えておく必要があります。プログラマーに依存していると思います。


6
はいfalse、いつでもご利用いただけます。ただし、実行したくないコードブロックがある場合は、コメントアウトしてください。またはそれを削除します(後で回復する必要がある場合は、ソース管理システムを調べます)。
キーストンプソン

1
コードブロックが長すぎて一度に1行ずつ退屈にコメントできない場合は、これらのトリックを参照してください。 stackoverflow.com/questions/947897/… ただし、これらはテキストエディターを最初から最後まで一致させるのに役立ちません。そのため、あまり改善されていません。
Camille Goudeseune

4
「falseの場合」は、コードをコメント化するよりもはるかに優れています。囲まれたコードが引き続き正当なコードであることを保証するためです。コードをコメントアウトするための最良の言い訳は、本当に削除する必要がある場合ですが、覚えておく必要があるものがあるため、単なるコメントであり、もはや「コード」ではありません。
ジェフリアマン2017年

1
コメント「#if false;」を使用します。そうすれば、それを検索して、デバッグ削除セクションの最初と最後の両方を見つけることができます。
Jon

この回答は、OPの質問にはまったく応答しないため、賛成しないでください。
Viktor Joras

54

確かに、デバッグやデモンストレーションのニーズに役立つ場合があります。

ボブコープランドソリューションhttp://bobcopeland.com/blog/2012/10/goto-in-bash/ elegant:

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

36
「bashをアセンブリ言語のように見せるための私の探求は、これまでになく完成に近づいています。」- ワオ。ただ、すごい。
Brian Agnew 2016年

5
私が変更する唯一のことは、ラベル: start:が構文エラーではないようにラベルを開始するようにすることです。
Alexej Magura

5
次のように開始すると、cmd = $(sed -n "/#$ label:/ {:a; n; p; ba};" $ 0 | grep -v ':$')で始まります。 start:=>これにより、スクリプトエラーが防止されます
access_granted

2
うーん、確かにそれはおそらく私の間違いです。投稿を編集しています。ありがとうございました。
Hubbitus

2
set -x上で何が起こっているか理解するのに役立ちます
ジョン・林

30

casebashでgotoをシミュレートするために使用できます。

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

生成する:

bar
star

4
これにはが必要bash v4.0+です。ただし、これは汎用ではgotoなく、caseステートメントのフォールスルーオプションです。
mklement0 14

3
これが答えだと思います。与えられた命令から、スクリプトの実行の再開をサポートするために行く必要が本当にあります。これは、あらゆる意味で、セマンティック、goto、およびセマンティクスと構文糖はかわいいですが、厳密に必要というわけではありません。優れたソリューション、IMO。
nathan g 2015

1
@nathang、それ答えであるかどうかは、OPが尋ねた一般的なケースのサブセットとケースが一致するかどうかによって異なります。残念ながら、質問は一般的なケースについて尋ねているので、この答えは狭すぎて正確ではありません。(その理由のためにその質問が広すぎると閉じるべきかどうかは別の議論です)。
Charles Duffy

1
gotoは選択する以上のものです。goto機能とは、特定の条件に従ってジャンプして、フローのようなループを作成できることを意味します...
Sergio Abreu

19

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「」を無効にしても構文エラーは発生しません(goto:」にが、これは、gotoステートメントで。

あらゆる種類のgotoソリューションを使用するときは常に、ジャンプするコードが後で依存する変数を設定しないように注意する必要があります。これらの定義をスクリプトの先頭またはそのすぐ上に移動する必要がある場合があります。あなたのgoto声明の。


gotoの良し悪しを理解していなければ、Laurenceのソリューションはクールです。あなたは私の投票を得ました。
Mogens TrasherDK 2017

1
それはスキップスキップのようなものであり、if(false)(私にとっては)より理にかなっているようです。
vesperto

2
goto "label"を引用すると、ヒアドキュメント展開/評価されないため、見つけにくいバグを回避できます(引用符で囲まれていない場合、パラメータ展開、コマンド置換、および算術展開があり、これらはすべて副作用)。これを少し調整して、完全にalias goto=":<<"省くこともできcatます。
mr.spuratic

はい、vesperto、 'if'ステートメントを使用できます。私は多くのスクリプトでそれを実行しましたが、特にジャンプポイントを頻繁に変更する場合は、非常に複雑になり、制御や追跡がはるかに困難になります。
ローレンスレンショー

12

他のユーザーはすでにgotobashに直接同等のものがないことを明確にしています(そして関数、ループ、ブレークなどの最も近い代替手段を提供しています)が、loop plusを使用し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

7

このソリューションには次の問題がありました。

  • で終わるすべてのコード行を無差別に削除します :
  • 行の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

6

希望する結果を得る機能がもう1つあります。command trapです。たとえば、クリーンアップの目的で使用できます。


4

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が存在しないため、通常はスクリプトを終了するエラーがスローされます。このエラーはでキャッチされtrap2>/dev/nullは通常表示されるエラーメッセージを非表示にします。

このgotoの実装は、存在しないコマンド(またはその方法による他のエラー)が同じトラップコマンドを実行するため、明らかに信頼できません。特に、移動先のラベルを選択することはできません。


基本的に、実際のシナリオではgotoステートメントは必要ありません。異なる場所へのランダムな呼び出しはコードを理解することを難しくするだけなので、それらは冗長です。

あなたのコードが何度も呼び出された場合は、ループを使用することを考慮して使用することにそのワークフローを変更するcontinuebreak

コードがそれ自体を繰り返す場合は、関数を記述して、必要な回数だけ呼び出すことを検討してください。

コードが変数値に基づいて特定のセクションにジャンプする必要がある場合は、使用を検討してください caseステートメントのをください。

長いコードを細かく分割できる場合は、コードを個別のファイルに移動して、親スクリプトから呼び出すことを検討してください。


この形式と通常の関数の違いは何ですか?
yurenchen 2016年

2
@yurenchen- コードフローの変更を実現するためにtrapを使用exception handlingしていると考えてください。この場合、トラップはスクリプトを発生させるあらゆるものをキャッチしていますEXIT。これは、存在しないコマンドを呼び出すことによってトリガーされgotoます。ところで、EXITを引き起こすのは引数なのでgoto trap、引数は何でもかまいません。スクリプトは終了することを示すエラーメッセージを非表示にします。goto ignoredgoto2>/dev/null
Jesse Chisholm、2016

2

これは、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..."

このコピー貼り付けは機能していません=>まだジャンプ先があります。
Alexis Paques

どういう意味ですか?何がうまくいかないのですか?もっと具体的に教えていただけますか?
thebunnyrules

もちろん、コードを試しました!すべてを成功させる前に、5つまたは6つの異なる条件下でテストしました。コードはどのように失敗しましたか、どのエラーメッセージまたはコードですか?
thebunnyrules 2018

どんな男。私はあなたを助けたいと思っていますが、あなたは本当に漠然としていてコミュニケーションが取れていません(もちろん、「それをテストしましたか」という質問を侮辱することは言うまでもありません)。「正しく貼り付けなかった」とはどういう意味ですか?「/ $#label#:/ {:a; n; p; ba};」を参照している場合 一部、私はそれが異なることを非常に認識しています。私はそれを新しいラベル形式(別名#label#対:labelを他の回答で変更しましたが、場合によってはスクリプトをクラッシュさせていました)。私の回答に有効な問題がありますか(スクリプトのクラッシュや構文の誤りなど)、または「カットアンドペーストの方法がわからない」と思ったために、私の回答を-1しただけですか?
thebunnyrules 2018

1
@thebunnyrules私はあなたと同じ問題に遭遇しましたが、解決 策として
Fabby

2

デバッグ時にコードブロックをコメントアウトするための単純な検索可能なgoto。

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

結果は-> GOTO完了


1

私は関数を使用してこれを行う方法を見つけました。

:言うには、たとえば、あなたは3つの選択肢を持っているABC。コマンドAB実行しCますが、詳細情報が表示され、元のプロンプトに戻ります。これは関数を使用して行うことができます。

行containsg 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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.