てきとうなメモ

本の感想とか技術メモとか

set -eはどこまで有効なのか

シェルスクリプト(bash)はコマンドが失敗しても次のコマンドを実行してしまうので怖い→set -eしておけという話はよくあるが、実際どこまで有効なのか。

基本

コマンドの戻り値が0になった時にシェルを終了する

#!/bin/bash
set -e
echo "before false"
false
echo "after false"
./basic.sh 
before false
# falseで0以外を返したので、シェルが終了しechoが実行されない

if

ifの条件が0以外を返してもシェルは終了しない

#!/bin/bash
set -e
echo "before if"
if [ 1 = 0 ]; then
  echo "in if"
fi
echo "after if"
$ ./if.sh
before if
after if # ifの条件は0以外を返しているがシェルは終了していない

while,until

whileやuntilの条件が0以外を返してもシェルを終了しない

#!/bin/bash
set -e

i=0
echo "start while"
while [ $i -lt 5 ]
do
  echo $i
  i=$((i+1))
done
echo "end while"

echo "start until"
until [ $i -lt 0 ]
do
  echo $i
  i=$((i-1))
done
echo "end until"
$ ./while.sh 
start while
0
1
2
3
4
end while # i=5の時にwhileの条件が0以外を返すがシェルは終了していない
start until
5
4
3
2
1
0
end until # i=-1の時にuntilの条件が0以外を返すがシェルは終了していない

&&や||

&&や||の前のコマンドが0以外を返していてもシェルを終了しない

#!/bin/bash
set -e
echo "start and or"
rm hoge || echo hoge
rm fuga && echo fuga
echo "end and or"
$ ./andor.sh
start and or
rm: hoge: No such file or directory
hoge              # rm hogeが0以外を返していても後ろのコマンドも実行される
rm: fuga: No such file or directory
end and or    # rm fugaが0以外を返していてもシェルは終了していない

パイプライン

パイプラインの途中で0を返しても、最後まで実行される。

$ cat pipe.sh 
#!/bin/bash
set -e
echo "hoge" | grep fuga | wc -l
echo ${PIPESTATUS[*]}
./pipe.sh 
       0           # grep fugaが0以外を返していてもwc -lが実行されている
0 1 0            # パイプラインの各コマンドのステータス

シーケンス(;)

セミコロンで繋がれたコマンドの連続の場合は途中で0を返せばシェルを終了する

#!/bin/bash
set -e
echo "before seq"; rm hoge; echo "after seq";
./sequence.sh 
before seq
rm: hoge: No such file or directory
# rm hogeが0以外を返したのでechoの前にシェルが終了した

サブシェル

サブシェルもset -eが有効になる。以下の場合は、rm hogeでサブシェルが終了し、その結果(...)が0以外を返して親のシェルも終了する。

#!/bin/bash
set -e
echo "before subshell"
(rm hoge; echo "in subshell")
echo "after subshell"
$ ./subshell.sh 
before subshell
rm: hoge: No such file or directory

関数

関数の内部で0以外を返していれば、関数の途中でシェルを終了する。

#!/bin/bash

hoge() {
  false
  echo "in function"
}

set -e
echo "before function hoge"
hoge
echo "after function hoge"
$ ./function.sh 
before function hoge
# hoge内のfalseで0以外を返したのでシェルが終了した

まあman見ればよかったのだけども。

$ bash --version
GNU bash, バージョン 4.3.11(1)-release (x86_64-pc-linux-gnu)
...
$ man bash
...
-e    Exit  immediately if a pipeline (which may consist of a
        single simple command), a list, or a  compound  command
        (see  SHELL GRAMMAR above),  exits with a non-zero sta‐
        tus.  The shell does not exit if the command that fails
        is  part  of  the  command list immediately following a
        while or until keyword, part of the test following  the
        if or elif reserved words, part of any command executed
        in a && or || list except  the  command  following  the
        final && or ||, any command in a pipeline but the last,
        or if the command's return value is being inverted with
        !.  If a compound command other than a subshell returns
        a non-zero status because a command failed while -e was
        being ignored, the shell does not exit.  A trap on ERR,
        if set, is  executed  before  the  shell  exits.   This
        option  applies  to the shell environment and each sub‐
        shell environment  separately  (see  COMMAND  EXECUTION
        ENVIRONMENT  above),  and  may  cause subshells to exit
        before executing all the commands in the subshell.

mac os xbashのmanはmanが古いのか嘘が書いてあった。パイプラインやサブシェルの話は書いていない

$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin15)
Copyright (C) 2007 Free Software Foundation, Inc.
$ man bash
...
-e    Exit immediately if a simple command (see SHELL  GRAMMAR
        above) exits with a non-zero status.  The shell does not
        exit if the command that fails is part  of  the  command
        list  immediately  following  a  while or until keyword,
        part of the test in an if statement, part of a && or  ||
        list, or if the command's return value is being inverted
        via !.  A trap on ERR, if set, is  executed  before  the
        shell exits.