bashデバッグTips

* 本記載内容は以下内容を自分用にまとめたものになります
    1. [シェルスクリプトのデバッグ](http://shellscript.sunone.me/debug.html "シェルスクリプトのデバッグ")
    2. [PS4の設定でシェルスクリプトのデバッグが捗る](http://qiita.com/kawaz/items/65cdbeaa739c4e6b7776 "PS4の設定でシェルスクリプトのデバッグが捗る")
    3. [Bashの擬似シグナルを使ったデバッグ方法](http://dharry.hatenablog.com/entry/20101121/1290273723 "Bashの擬似シグナルを使ったデバッグ方法")
    4. [Shellスクリプトをデバッグ（ステップ実行）する](http://oss-lab.net/?p=730 "Shellスクリプトをデバッグ（ステップ実行）する")

---

# bashデバッグTips

## モチベーション

* デバッグしやすくしたい

## 1. Bashオプション（`bash -uvx`）

* 動作確認用スクリプト

```bash:cal_score_ave.sh
#!/bin/bash

: [DEBUG] set param
readonly SCORE_ARRAY=(100 90 80 70)
readonly COUNTS=${#SCORE_ARRAY[@]}

function cal_score_ave() {
  local _score_sum=i
  : [DEBUG] cal
  for _score in ${SCORE_ARRAY[@]}
  do
    _score_sum=$((_score_sum + _score))
  done

  score_ave=$((_score_sum / COUNTS))
  return 0
}

: [DEBUG] main
cal_score_ave
echo $score_ave
echo $param
echo "finished"

exit 0
```

```shell-session:-uvx
# bash -uvx cal_score_ave.sh
#!/bin/bash

: [DEBUG] set param
+ : '[DEBUG]' set param
readonly SCORE_ARRAY=(100 90 80 70)
+ SCORE_ARRAY=(100 90 80 70)
+ readonly SCORE_ARRAY
readonly COUNTS=${#SCORE_ARRAY[@]}
+ readonly COUNTS=4
+ COUNTS=4

function cal_score_ave() {
  local _score_sum=i
  : [DEBUG] cal
  for _score in ${SCORE_ARRAY[@]}
  do
    _score_sum=$((_score_sum + _score))
  done

  score_ave=$((_score_sum / COUNTS))
  return 0
}

: [DEBUG] main
+ : '[DEBUG]' main
cal_score_ave
+ cal_score_ave
+ local _score_sum=i
+ : '[DEBUG]' cal
+ for _score in '${SCORE_ARRAY[@]}'
cal_score_ave.sh: line 12: i: 展開されていない変数
```

|オプション|説明                                    |
|:--------|:--------------------------------------|
|-u       |未定義の変数をチェックしそこで処理を終了する|
|-v       |実行するコマンドをそのまま表示する|
|-x       |実行内容をトレースする|

### 1.1 `bash -u`

```shell-session:-u
# bash -u cal_score_ave.sh
cal_score_ave.sh: line 12: i: 展開されていない変数
```

### 1.2 `bash -v`

```shell-session:-v
# bash -v cal_score_ave.sh
#!/bin/bash

: [DEBUG] set param
readonly SCORE_ARRAY=(100 90 80 70)
readonly COUNTS=${#SCORE_ARRAY[@]}

function cal_score_ave() {
  local _score_sum=i
  : [DEBUG] cal
  for _score in ${SCORE_ARRAY[@]}
  do
    _score_sum=$((_score_sum + _score))
  done

  score_ave=$((_score_sum / COUNTS))
  return 0
}

: [DEBUG] main
cal_score_ave
echo $score_ave
85
echo $param

echo "finished"
finished

exit 0
```

### 1.3 `bash -x`

```shell-session:-x
# bash -x cal_score_age.sh
+ : '[DEBUG]' set param
+ SCORE_ARRAY=(100 90 80 70)
+ readonly SCORE_ARRAY
+ readonly COUNTS=4
+ COUNTS=4
+ : '[DEBUG]' main
+ cal_score_ave
+ local _score_sum=i
+ : '[DEBUG]' cal
+ for _score in '${SCORE_ARRAY[@]}'
+ _score_sum=100
+ for _score in '${SCORE_ARRAY[@]}'
+ _score_sum=190
+ for _score in '${SCORE_ARRAY[@]}'
+ _score_sum=270
+ for _score in '${SCORE_ARRAY[@]}'
+ _score_sum=340
+ score_ave=85
+ return 0
+ echo 85
85
+ echo

+ echo finished
finished
+ exit 0
```

## 2. ヌルコマンド（`:`）

> : [arguments]
> 　　　何もしません。
> 　　　
> 　　　このコマンドはargumentsを展開し、指定されたリダイレクトを実行する以外には何も行いません。
> 　　　終了コード 0 を返します。
>　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　man bash より引用

## 3. デバッグプロンプト（`PS4`）

> PS4
> 　　　このパラメータは PS1 と同じように展開されます。
> 　　　
> 　　　この値は実行トレース中に bash が表示する各コマンド前に出力されます。
> 　　　
> 　　　複数段の間接レベル (levels of indirection) を示すときは、
> 　　　PS4 の最初の文字が必要に応じて複数回表示されます。
> 　　　
> 　　　デフォルト値は ‘‘+ ’’ です。
>　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　man bash より引用

* PS4の変更
    * `~/.bashrc`に以下1行追加

```bash:~/.bashrc
export PS4='+ (${BASH_SOURCE}:${LINENO}): ${FUNCNAME:+$FUNCNAME(): }'
```

```shell-session
# bash -x ./cal_score_ave.sh
+ (./cal_score_ave.sh:3): : '[DEBUG]' set param
+ (./cal_score_ave.sh:4): SCORE_ARRAY=(100 90 80 70)
+ (./cal_score_ave.sh:4): readonly SCORE_ARRAY
+ (./cal_score_ave.sh:5): readonly COUNTS=4
+ (./cal_score_ave.sh:5): COUNTS=4
+ (./cal_score_ave.sh:19): : '[DEBUG]' main
+ (./cal_score_ave.sh:20): cal_score_ave
+ (./cal_score_ave.sh:8): cal_score_ave(): local _score_sum=i
+ (./cal_score_ave.sh:9): cal_score_ave(): : '[DEBUG]' cal
+ (./cal_score_ave.sh:10): cal_score_ave(): for _score in '${SCORE_ARRAY[@]}'
+ (./cal_score_ave.sh:12): cal_score_ave(): _score_sum=100
+ (./cal_score_ave.sh:10): cal_score_ave(): for _score in '${SCORE_ARRAY[@]}'
+ (./cal_score_ave.sh:12): cal_score_ave(): _score_sum=190
+ (./cal_score_ave.sh:10): cal_score_ave(): for _score in '${SCORE_ARRAY[@]}'
+ (./cal_score_ave.sh:12): cal_score_ave(): _score_sum=270
+ (./cal_score_ave.sh:10): cal_score_ave(): for _score in '${SCORE_ARRAY[@]}'
+ (./cal_score_ave.sh:12): cal_score_ave(): _score_sum=340
+ (./cal_score_ave.sh:15): cal_score_ave(): score_ave=85
+ (./cal_score_ave.sh:16): cal_score_ave(): return 0
+ (./cal_score_ave.sh:21): echo 85
85
+ (./cal_score_ave.sh:22): echo

+ (./cal_score_ave.sh:23): echo finished
finished
+ (./cal_score_ave.sh:25): exit 0
```

## 4. trapコマンド

| sigspec |                          タイミング                          |
| :------ | :----------------------------------------------------------- |
| EXIT    | シェルがスクリプトを終了した                                 |
| ERR     | コマンド、シェル関数から0以外の終了ステータスが返された      |
| DEBUG   | シェルが各コマンド、算術、シェル関数の最初のコマンドの実行前 |
| RETURN  | source または . で実行されたシェル関数/スクリプトが終了した  |


> trap [-lp] [[arg] sigspec ...]
> 　　　シェルがシグナル sigspec を受け取ると、コマンド arg が読み込まれて、実行されます。
> 　　　
> 　　　arg が存在しない (かつ sigspec が一つ指定された) 場合か、 arg が - の場合、
> 　　　指定されたシグナルは全てオリジナルの動作 (シェルの起動時に設定されていた値) にリセットされます。
> 　　　
> 　　　arg が空文字列である場合、
> 　　　それぞれの sigspec で指定されたシグナルは、 シェルとシェルが起動したコマンドから無視されます。
> 　　　
> 　　　arg なしで -p オプションが与えられた場合、 
> 　　　各 sigspec に関連付けられた trap コマンドが表示されます。
> 　　　
> 　　　引き数が全くないか、 -p だけが与えられた場合、
> 　　　trap は各シグナルに関連付けられたコマンドのリストを出力します。
> 　　　
> 　　　-l オプションを与えると、 シェルはシグナル名と対応する番号のリストを出力します。
> 　　　それぞれの sigspec は、<signal.h> で定義されているシグナル名、またはシグナル番号です。
> 　　　シグナル名は大文字小文字は区別されず、先頭の SIG は省略可能です。
> 　　　
> 　　　sigspec が EXIT (0) であれば、シェルの終了時にコマンド arg が実行されます。
> 　　　
> 　　　sigspec が DEBUG であれば、
> 　　　各々の 単純なコマンド、for コマンド、case コマンド、select コマンド、各々の算術 for コマンドの前、
> 　　　およびシェル関数の最初のコマンドの実行前 (前述の シェルの文法セクションを参照) に、
> 　　　コマンド arg が実行されます。
> 　　　
> 　　　DEBUG のトラップの影響についての詳細は組み込みコマンド shopt の extdebug オプションの説明を
> 　　　参照してください。
> 　　　
> 　　　sigspec が RETURN であれば、シェル関数の実行、
> 　　　または組み込みコマンドの . や source で実行されたスクリプトの実行が終わるたびに
> 　　　コマンド arg が実行されます。
> 　　　
> 　　　sigspec が ERR であれば、 単純なコマンドが 0 以外の終了ステータスのときに
> 　　　コマンド arg が実行されます。
> 　　　ただし、失敗したコマンドが、 while または until キーワード直後のコマンドリストに含まれる場合、
> 　　　if 文の条件に含まれる場合、 && や || のリスト中で実行するコマンドに含まれる場合、
> 　　　および、コマンドの戻り値が ! で反転されている場合には、 ERR のトラップは実行されません。
> 　　　これらは errexit オプションが従う条件と同じです。
> 　　　
> 　　　シェルに入る際に無視されるシグナルは、トラップもリセットもできません。
> 　　　無視されなかったシグナルのトラップは、 サブシェルやサブシェル環境では作られたときに
> 　　　元の値にリセットされます。
> 　　　sigspec のいずれかが不正であれば、返却ステータスは偽になります。 
> 　　　それ以外の場合には、 trap は真を返します。
>　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　man bash より引用

### 4.1 ステップ実行

* trapに渡す`sigspec`に`DEBUG`を指定することでステップ実行できる

```bash
trap 'read -p "$0($LINENO) $BASH_COMMAND"' DEBUG`
```

```bash:trap動作確認コード
#!/bin/bash

: [DEBUG] set param
readonly SCORE_ARRAY=(100 90 80 70)
readonly COUNTS=${#SCORE_ARRAY[@]}

function cal_score_ave() {
  local _score_sum=i
  : [DEBUG] cal
  for _score in ${SCORE_ARRAY[@]}
  do
    _score_sum=$((_score_sum + _score))
  done

  score_ave=$((_score_sum / COUNTS))
  return 0
}

trap 'read -p "$0($LINENO) $BASH_COMMAND"' DEBUG
: [DEBUG] main
cal_score_ave
echo $score_ave
echo $param
trap – DEBUG
echo "finished"

exit 0
```

```shell-session:実行結果
# ./cal_score_ave.sh
./cal_score_ave.sh(5) : [DEBUG] set param
./cal_score_ave.sh(6) readonly SCORE_ARRAY=(100 90 80 70)
./cal_score_ave.sh(7) readonly COUNTS=${#SCORE_ARRAY[@]}
./cal_score_ave.sh(21) : [DEBUG] main
./cal_score_ave.sh(22) cal_score_ave
./cal_score_ave.sh(23) echo $score_ave
85
./cal_score_ave.sh(24) echo $param

./cal_score_ave.sh(25) echo "finished"
finished
./cal_score_ave.sh(27) exit 0
```

### 4.2 特定の箇所のみステップ実行する

* `trap - DEBUG`で無効化する

```bash
trap 'read -p "$0($LINENO) $BASH_COMMAND"' DEBUG
# ここにステップ実行したい処理
trap - DEBUG
```

```bash:trap動作確認コード
#!/bin/bash

: [DEBUG] set param
readonly SCORE_ARRAY=(100 90 80 70)
readonly COUNTS=${#SCORE_ARRAY[@]}

function cal_score_ave() {
  local _score_sum=i
  : [DEBUG] cal
  for _score in ${SCORE_ARRAY[@]}
  do
    _score_sum=$((_score_sum + _score))
  done

  score_ave=$((_score_sum / COUNTS))
  return 0
}

trap 'read -p "$0($LINENO) $BASH_COMMAND"' DEBUG
: [DEBUG] main
cal_score_ave
echo $score_ave
echo $param
trap - DEBUG
echo "finished"

exit 0
```

```shell-session:実行結果
# ./cal_score_ave.sh
./cal_score_ave.sh(20) : [DEBUG] main
./cal_score_ave.sh(21) cal_score_ave
./cal_score_ave.sh(22) echo $score_ave
85
./cal_score_ave.sh(23) echo $param

./cal_score_ave.sh(24) trap - DEBUG
finished
```

### 4.3 エラーのみ捕捉する

* trapに渡す`sigspec`に`ERR`を渡すことでエラーのみ捕捉することができる

```bash
trap 'read -p "$0($LINENO) $BASH_COMMAND":exit 1' ERR`
```

```bash:trap動作確認コード
#!/bin/bash

trap 'read -p "$0($LINENO) $BASH_COMMAND";exit 1' ERR

: [DEBUG] set param
readonly SCORE_ARRAY=(100 90 80 70)
readonly COUNTS=${#SCORE_ARRAY[@]}

function cal_score_ave() {
  local _score_sum=i
  : [DEBUG] cal
  for _score in ${SCORE_ARRAY[@]}
  do
    _score_sum=$((_score_sum + _score))
  done

  score_ave=$((_score_sum / COUNTS))
  #return 0
  return 1
}

: [DEBUG] main
cal_score_ave
echo $score_ave
echo $param
echo "finished"

exit 0
```

```shell-session:実行結果
# ./cal_score_ave.sh
./cal_score_ave.sh(19) return 1
```


---