web-dev-qa-db-ja.com

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

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

177
kofucii

いいえ、ありません; do が存在する制御構造体についての情報は .2.4 "複合コマンド" Bashリファレンスマニュアル を参照のこと。特に、breakcontinueは、gotoほど柔軟ではありませんが、Bashでは一部の言語よりも柔軟性があり、目的を達成するのに役立つ場合があります。 (あなたが望むものが何であれ…。)

67
ruakh

あなたがデバッグのために大きなスクリプトの一部をスキップするためにそれを使っているなら(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行のコメントですが、それはあなたが覚えているものでなければならないでしょう、私はそれがプログラマーに非常に依存すると思う。

105
Michael Rusch

それは確かにいくつかのデバッグやデモンストレーションのニーズに役立つかもしれません。

私はボブコープランドソリューション 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
41
Hubbitus

Gotoをシミュレートするためにbashでcaseを使用することができます。

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

生成します:

bar
star
26
Paul Brannan

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ステートメントの一つ。

15

他の人たちはすでに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
11
Seth McCauley

望ましい結果を達成するためのもう1つの機能があります:コマンドtrap。たとえば、クリーンアップ目的で使用できます。

6
Serge Roussak

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ステートメントは必要ありません。異なる場所へのランダムな呼び出しはコードの理解を困難にするだけなので、冗長です。

コードが何度も呼び出される場合は、ループを使用し、ワークフローを変更してcontinuebreakを使用することを検討してください。

あなたのコードがそれ自身を繰り返すならば、関数を書いてあなたが望むようにそれを何度も呼び出すことを検討してください。

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

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

4
kenorb

この解決方法 には以下の問題がありました。

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

デバッグ時にコードブロックをコメントアウトするための簡単な検索可能なジャンプ.

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

結果は→GOTO完了

2
ztalbot

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

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

たとえば、AB、およびCの3つの選択肢があるとします。 ABはコマンドを実行しますが、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
1
cutrightjm