1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

シェルスクリプトを書くときの初手のおさらい

Posted at

はじめに

最近、シェルスクリプトを書く機会があり、どういう初手をとるのがいいのか、自分なりに整理し直しました。

まず先頭に書くもの

もはや、何番煎じかわからないネタですが、あらためて。
このあたりの記事がよくまとまっています。

結論

# !/usr/bin/env bash
set -euo pipefail

shebang

# !/usr/bin/env bash

bashのバスが、OSなどによって微妙にちがうので、これが一番、そういったものに影響を受けないようです。
作ったシェルスクリプトを、docker container上で動かすなどであれば、portabilityが高い方がいい、というのが、最近の落とし所のようです。

エラーハンドリング

set -euo pipefail

エラー時に即時停止

仕事でシェルスクリプトを書く場合、なんらかのバッチ処理のためのことが多いです。
その場合、想定したファイルが存在しないなど、途中でエラーになった場合は、その時点で処理を止めたいです。
(その状態で後続の処理が続くと、意図しない結果がより広がるからです)

下記を追記することで、

set -e

未定義変数をエラー扱い

変数名のtypoや、値のセットをし忘れた場合に、それを早く検知したいです。
運用上というより、開発時のこういったミスを浮き彫りにするためですね。

# !/usr/bin/env bash
set -e 

echo "start"

echo $TEST

echo "end"
$ ./sample.sh 
start

end

上記のように、未定義変数を使うと、空の値として扱われます。
変数の使い方次第では、そのまま後続処理がエラーコードを出せずに続いてしまう可能性があります。

この設定で、そういった場合をエラーにできるようになります。

set -u

下記のように、未定義変数を使った箇所で止まるようになります。

$ ./sample.sh  
start
./sample.sh: line 7: TEST: unbound variable

パイプしているコマンドにどれかが失敗すれば、エラー扱い

パイプのexit codeは、最後のものになります。
その前が失敗していても、最後のコマンドがエラー扱いにならない場合は、exit codeは0になってしまいます。

$ false | false | false | true
$ echo $?
0

$ true | true | true | false 
$ echo $? 
1  

パイプしているコマンドのどれかでエラーになったら、止めた場合は以下の設定をいれます

set -o pipefail

下記で試してみます。

# !/usr/bin/env bash
set -euo pipefail

echo "start"

true | false | true | true

echo "end"

想定どおりの挙動ですね。

$ ./sample.sh 
start

オプション引数の取り扱い

出力先のディレクトリなど、「実際の運用では、とりうる値は1パターンだが、テスト・開発をしやすくするために、値をかえやすくしたい」というものがあります。こういうときは、「引数なしのときは、本番用の値、引数を渡したらそれを使う」ということで、引数の有無やその値で制御できると楽です。

シェルスクリプトの引数のparseのやり方を色々見つけたのですが、なるべくすくない知識と単純な発想でできないかと、思案した結果がこちらです。結局は、自分の要求にあわせた自前での解析が、過不足のない実装になりそうです。

# !/usr/bin/env bash
set -euo pipefail

source_dir=/mnt/nfs001/input
output_dir=/mnt/nfs001/output

for ((i=1; i<$#; i++))
do
  j=$((i+1))
  arg=${!i}
  case "${arg}" in
   "--source-dir" ) source_dir=${!j} ;;
   "--output-dir" ) output_dir=${!j} ;;
  esac
done

echo "source_dir: ${source_dir}"
echo "output_dir: ${output_dir}"

  • スクリプト内で、変数の初期値をセット
  • 引数を順番にチェック
  • --XXXに該当したら、その次の引数を対応する変数にセット

という方針です。オプション引数を適正に渡したら、変数の値が書き換えられるのが確認できます。

$ ./sample.sh 
source_dir: /mnt/nfs001/input
output_dir: /mnt/nfs001/output

$ ./sample.sh --source-dir /tmp/src                      
source_dir: /tmp/src
output_dir: /mnt/nfs001/output

$ ./sample.sh --output-dir /tmp/dst
source_dir: /mnt/nfs001/input
output_dir: /tmp/dst

$ ./sample.sh --source-dir /tmp/src --output-dir /tmp/dst
source_dir: /tmp/src
output_dir: /tmp/dst

$ ./sample.sh aaa bbb ccc ddd      
source_dir: /mnt/nfs001/input
output_dir: /mnt/nfs001/output

i番目、i+1番目の引数の取得するために、bashのindirection機能 (${!i} と、変数の前にエクスクラメーションをいれる書き方)を使っているのが、やや強引なところです。

必須の引数は、最初の方に渡してもらうとすれば、この方法と共存できます。

# !/usr/bin/env bash
set -euo pipefail

keyword=$1
status=$2

source_dir=/mnt/nfs001/input
output_dir=/mnt/nfs001/output

for ((i=3; i<$#; i++))
do
  j=$((i+1))
  arg=${!i}
  case "${arg}" in
   "--source-dir" ) source_dir=${!j} ;;
   "--output-dir" ) output_dir=${!j} ;;
  esac
done

echo "positional arguments"

echo "keyword: ${keyword}"
echo "status: ${status}"

echo " --- options --- "

echo "source_dir: ${source_dir}"
echo "output_dir: ${output_dir}"
$ ./sample.sh                
./sample.sh: line 4: $1: unbound variable

$ ./sample.sh aaa
./sample.sh: line 5: $2: unbound variable


$ ./sample.sh aaa bbb
positional arguments
keyword: aaa
status: bbb
 --- options --- 
source_dir: /mnt/nfs001/input
output_dir: /mnt/nfs001/output

$ ./sample.sh aaa bbb --source-dir /tmp/src --output-dir /tmp/dst
positional arguments
keyword: aaa
status: bbb
 --- options --- 
source_dir: /tmp/src
output_dir: /tmp/dst

これは、必須引数や、オプション引数の順番に制約をつけているので、実装を簡略化できています。
もっと渡す引数の順番に自由度をもたせたないなら、結構がんばった解析が必要です。

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?