bashシェルスクリプトの記述の仕方に関するメモ書き

# はじめに

bashシェルスクリプトの記述の仕方に関するメモ書きです。


## ファイル入出力やパイプライン処理を中心に
シェルが得意なのはファイル入出力やパイプライン処理なので、複雑な構文はできるだけ使わず、これらの使用を中心としたコードを書きます。


## PATH
コマンドごとにフルパスを変数に格納するなどは手間なので、必要な環境変数$PATHをexportしておきます。
それでも必要な場合はフルパスを変数に格納しておきます。


## 標準出力、標準エラー出力のリダイレクト
標準出力(1)、標準エラー出力(2)はデフォルトは画面になっています。

それ以外にエラーメッセージを定義する場合、標準エラー出力(2)にリダイレクトします。

```lang:例
echo "Error hogehoge" >&2
```

標準エラー出力をファイルに出力したい場合は、以下のように標準エラー出力(2)をファイル(hogehoge.log)へリダイレクトします。

```lang:例
echo "Error hogehoge" 2>hogehoge.log
```

標準出力と標準エラー出力をファイルに出力する場合は、以下のように標準出力(1)をファイル(hogehoge.log)へ、標準エラー出力(2)を標準出力(1)と同じ出力先(hogehoge.log)へリダイレクトします。

```lang:例
echo "Error hogehoge" >hogehoge.log 2>&1
```

or

```lang:例
echo "Error hogehoge" 1>hogehoge.log 2>&1
```


## 変数

定義する変数には「型」はなく、大文字でも小文字でも数値でも設定できるため、スクリプト全体で統一感があればなんでも良いと思います。
ただし、変数名の大文字と小文字は区別されるため例えば変数VARと変数varは別物として扱われます。

<code>export 変数名</code> とすれば環境変数として定義できます。
関数内の変数の定義には、<code>local 変数</code>とすると関数内のみで利用できるlocalの変数となるので良いです。
また、<code>typeset</code>を利用すると変数に型のようなものを持たせたり、削除や上書きされたくない変数は<code>readonly</code>を指定しておくと読み込み専用の変数として定義できます。

変数の値を参照する場合は変数名の頭に「$」を付け、参照する変数を明確にする場合は「{}」で変数名を囲むと読みやすいです。

```lang:例
VAR=hoge

ls /tmp/${VAR}.txt
```


## 予約変数

シェルにはあらかじめ定義済みの予約変数があります。必要に応じて利用します。

| 変数名 | 説明 |
|---|---|
| $$ | 実行シェルのプロセス番号(自分のPID)を格納する変数。 |
| $? | 最後に実行されたコマンドの終了ステータスを格納する変数。(コマンド成功時には 0 、失敗時には 1 、コマンドやエラーの種類によってはその他の数値もあり)|
| $! | 最後に呼び出されたバックグラウンドプロセスのプロセス番号を格納する変数。 |
| $- | シェルに与えられたフラグを格納する変数。 |
| $1 | シェルスクリプト実行時に指定された引数を格納する変数。 |
| $# | シェルスクリプト実行時に指定された引数、もしくはsetコマンド実行時に指定された全パラメータが設定される変数を格納する変数。 |
| $0 | シェルスクリプト自身のファイル名フルパス |
| $n | シェルスクリプトに与えられた引数を格納する変数。nは1,2,3…と指定できる。 |
| $@ | シェルスクリプト実行時に指定された全引数が設定される変数を格納する変数。<br /> デリミタを格納する環境変数 IFS にセットされた値で区切られて表示される。 |
| $* | シェルスクリプト実行時に指定された全引数が設定される変数を格納する変数。<br /> 出力に関してはデリミタを格納する環境変数 IFS の影響を受けない。 |
| ${@:X:Y} | 複数のパラメータのうち、X番目以降のY個のデータを取得するための変数を格納する変数。 |
| !$ | 最後に実行されたコマンドに指定していた引数を格納する変数。 |
| $PIPESTATUS | パイプで連結した各コマンドの終了ステータスが設定される変数。 |
| $LINENO | この変数を使用している行の行番号が設定される変数。  |


## シェバン(1行目)の記述

シェルスクリプトの１行目は、起動してスクリプトを読み込むためのインタプリタを指定します。

```lang:例
#!/bin/bash
```
Linux/Unixでは、ファイルの先頭2バイトが「#!」であった場合は、その後に記述されている別のコマンドを実行することになります。
上記では<code>/bin/bash</code>が呼び出されます。
<code>#!</code> のことを「シェバン」(shebang) と呼びます。

RHEL系のOSだと<code>/bin/sh</code>は<code>/bin/bash</code>のシンボリックリンクになっていますが、他のOSだと異なるかもしれないので、<code>/bin/sh</code>と指定していて、他のOSに移植する場合は注意が必要です。



## bash オプション


### bash -e / set -e

<code>-e</code>は実行されたコマンドの 1 つ が 0 でないステータスで終了した場合にスクリプトを終了させ、エラーシェルスクリプト実行時に実行内容が表示されるようにするオプションです。


### bash -n / set -n

スクリプト自体は実行されず、文法チェックのみ行われます。
問題がある場合はエラー出力され、問題が無い場合は何も出力されません。


### bash -u / set -u

<code>-u</code>オプションは未定義変数が参照されたときに処理を終了します。
また、<code>-</code>でなく<code>+</code>を指定すると意味が逆になります。


### bash -o pipefail / set -o pipefail

パイプでつないだ各コマンドの中で終了ステータスが0以外(正常終了以外)だった場合に、最後に0以外だったコマンドの終了ステータスが返されます。


### bash -v / set -v
<code>-v</code>はシェルスクリプト内でこれから実行されるコマンドを表示します。
これは、スクリプト内に記述されているコマンドが出力されるだけなので、 使用するときは「-x」オプションと同時に指定して、実行する内容と実行された内容を表示させるために使うことが多いです。


### bash -x / set -x
<code>-x</code>はデバッグ情報を出力します。具体的には、シェルスクリプトを実行した際に、変数への代入や実行されたコマンドなど、シェルスクリプト内で処理された内容が表示されます。



## オプション指定の仕方

### スクリプト内のシェバンに記述

スクリプト内のシェバンにオプションを指定できます。

```lang:例
#!/bin/bash -x
```
2つ以上の引数を指定できないので、<code>-o pipefail</code>といったものは指定できません。
また、<code>#!/usr/bin/env bash</code>といった指定の方法もありますが、この場合インタプリタにオプションを付与することができなくなります。


### オプションをスクリプト実行時に指定

bashコマンドを用いてオプションを指定し、引数に実行するスクリプトファイルを指定して実行します。

```lang:例
$ bash -x hogehoge.sh
```


### setコマンドによる指定

bashオプションは起動時にオプション指定することで、スクリプト全体でオプションが有効になりますが、<code>set</code>コマンドの場合、スクリプト内にオプションを記述することで、その後の処理からオプションが有効になります。<code>+</code>で指定すると<code>-</code>で指定した場合と逆の設定になります。

例えば、以下のように<code>bash -x</code>のオプションを指定すると、シェルスクリプト全体でデバッグオプションが有効になります。

```lang:例
#!/bin/bash -x
```

この場合、デバッグが有効になりますが、長いシェルスクリプトですと大量の情報が出力されてしまいます。
必要な部分のみデバッグしたい場合には、デバッグしたい処理の部分を<code>set -x</code>,<code>set +x</code>で囲んでおきます。
こうすることで囲まれた処理のみにオプションを有効にすることができます。<code>+</code>を指定しなければ、オプションが解除されることはありません。

```lang:例
#!/bin/bash


デバッグの必要ない処理

set -x #デバッグ出力開始

デバッグの必要な処理

set +x #デバッグ出力終了

デバッグの必要ない処理
```



## 実行しているシェルスクリプトのファイル名やディレクトリ名の取得

### シェルスクリプト自身のファイル名取得
シェルスクリプト自身のファイル名を取得するには以下を指定します。
スクリプト実行時のカレントディレクトリからシェルスクリプトのファイル名のみを取得できます。
予約変数の<code>$0</code>には現在実行しているシェルスクリプトのファイル名フルパスが格納されており、<code>basename</code>コマンドは引数に与えられたファイルのパスから、ディレクトリのパスのみを取り出します。

```lang:例
SCRIPT_FILE=$(basename $0)

echo $SCRIPT_FILE
```

### シェルスクリプトが設置してあるディレクトリパスの取得
シェルスクリプトが設置してあるディレクトリ名を取得するには、<code>dirname</code>を利用します。
スクリプト実行時のカレントディレクトリからシェルスクリプトが設置してあるディレクトリの相対パスのみを取得できます。
予約変数の<code>$0</code>には現在実行しているシェルスクリプトのファイル名フルパスが格納されており、<code>dirname</code>コマンドは引数に与えられたファイルのパスから、ディレクトリのパスのみを取り出します。

```lang:例
SCRIPT_DIR=$(dirname $0)

echo $SCRIPT_DIR
```



## 外部のスクリプトファイルの読み込み
外部のスクリプトを読み込む場合は<code>source</code>か<code>.</code>を利用します。
外部スクリプトファイルの内容が読み込まれます。

```lang:例
source ~/外部のスクリプトファイル名
```

または

```lang:例
. ~/外部のスクリプトファイル名
```