ネストされたループでシェルスクリプトを終了する


11

ネストされたループを持つシェルスクリプトがありますが、「exit」は実際にはスクリプトを終了せず、現在のループのみを終了することがわかりました。特定のエラー条件でスクリプトを完全に終了する別の方法はありますか?

許容可能なエラーがあり、多くの書き換えが必要になるため、「set -e」を使用したくありません。

現在、私はkillを使用して手動でプロセスをkillしていますが、これを行うにはもっと良い方法があるはずです。


1
「exit」が実際にスクリプトを終了しないとはどういう意味ですか?それは、ちょうど試してみてくださいbash -c 'for x in y z; do exit; done; echo "This never gets printed"'
Chris Down

そうです、通常はネストされたループから出るはずですが、exitを使用すると、スクリプトは外側のループから続行されます。スクリプトを投稿できません。
user923487 2015年

2
問題を示すスクリプトを作成して、ここに投稿できないのはなぜですか。それは私にはありそうにないようです。
Toby Speight 2015年

1
内部ループがコードのサブシェルで発生するのは事実ですか?
Toby Speight 2015年

@Tobyほとんどのスクリプトはロギングのためにサブシェルにありますが、ループとコードの残りの両方は同じサブシェルにあります。
user923487 2015年

回答:


19

あなたの問題は、それ自体がループのネストではありません。1つ以上の内部ループがサブシェルで実行されているということです。

これは機能します:

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        for j in $(seq 1 10) ; do
                echo j $j
                sleep 1
                [[ $j = 3 ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        echo "After the j loop."
done
echo "After all the loops."

出力:

i 1
j 1
j 2
j 3
I've had enough!

これはあなたが説明した問題を示しています:

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        cat /etc/passwd | while read line; do
                echo LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        echo "After the j loop."
done    
echo "After all the loops."

出力:

i 1
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
After the j loop.
i 2
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
After the j loop.
i 3
LINE root:x:0:0:root:/root:/bin/bash
(...etc...)

これが解決策です。サブシェルで実行される内部ループの戻り値をテストする必要があります。

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        cat /etc/passwd | while read line; do
                echo LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        [[ $? != 0 ]] && exit $?
        echo "After the j loop."
done
echo "After all the loops."

テストに注意してください: [[ $? != 0 ]] && exit $?

出力:

i 1
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!

編集:現在のサブシェルを確認するには、「応答」スクリプトを変更して、現在のシェルのプロセスIDを通知します。注:これはbash 4でのみ機能します。

#!/bin/bash

for i in $(seq 1 100); do
        echo pid $BASHPID i $i
        cat /etc/passwd | while read line; do
                echo pid $BASHPID LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        [[ $? != 0 ]] && echo pid $BASHPID && exit $?
        echo "After the j loop."
done
echo "After all the loops."

出力:

pid 31793 i 1
pid 31796 LINE root:x:0:0:root:/root:/bin/bash
pid 31796 LINE bin:x:1:1:bin:/bin:/sbin/nologin
pid 31796 LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
pid 31793

変数「i」と「j」は、Fortranの好意により提供されました。ごきげんよう。:-)


内部ループと外部ループの両方が同じサブシェルで実行されているため、内部ループを終了すると両方とも終了しますか?自分で問題を再現するのは難しいです。プログラムロジックのほとんどを削除するとすぐに、問題はなくなりました。とにかく、私はこれを今のところ回答としてマークします。それは何かをしなければならない可能性が高いからです。
user923487

あなたが提供したリンクを読んだ後、私は問題を見つけたと思います。外側のループでは、「cat file | while read line」を実行しています。パイピングはサブシェルを作成します。知らなかった。
user923487 2015

@ user923487-私の更新された回答を参照してください。bash 4を使用している場合は、サブシェルpidをエコー(または最新の場合はprintf)して、サブシェルにいるかどうかを確認できます。bashのバージョンを確認するbash --versionには、コマンドラインに入力します。
Mike S

非常に役立ちます。ありがとう!私は "killall scriptname.sh"がこれを解決する最も簡単な方法のようだと言いましたが。
user923487

2

以前の答えは、使用することを提案している[[ $? != 0 ]] && exit $?が、この意志はない、非常に期待されるようので、仕事を[[ $? != 0 ]]テストがリセットされ$?ている手段スクリプトが早期終了します予想通りものの、それは(期待できないとして)コード0で常に終了だろうと、ゼロに戻って。また、文字列比較テスト-neではなく、数値比較テストを使用することをお勧めします!=。したがって、私見のより良い解決策は以下を使用することです:

err=$?; [[ $err -ne 0 ]] && exit $err

実際の終了コードが正しく設定されていることを確認するためです。


1

使用できますbreak

からhelp break

Exit a FOR, WHILE or UNTIL loop.  If N is specified, break N enclosing loops.

したがって、3つの囲んでいるループを終了するには、メインのループ内に2つのネストされたループがある場合、これを使用してすべてのループを終了します。

break 3

ループの後にはさらに多くのコードがあるので、それらを抜け出すだけでは十分ではありません。
user923487 2015年

1
クールthx!例for((i=0;i<3;i++));do echo A;for((j=0;j<2;j++));do echo B;break 2;done;done
Aquarius Power

0

exit シェル全体、または現在のサブシェルを終了します:

$ bash -c 'for i in 1 2 3; do for j in 4 5 6; do echo $i; exit 1; echo $j; done; done'
1
$ echo $?
1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.