Linix シェルスクリプト 基礎

# はじめに

- Bash
- Zsh
- Python
- JavaScript
- PHP
- Perl
- Ruby

などの他のソフトウェアの動作を制御するために使用されるプログラミング言語を **スクリプト言語** と言う。

スクリプト言語で記述されたプログラムは **スクリプト** と呼ばれ、以下の拡張子が使用される。

- `.sh` : Bash, Zsh など
- `.py` : Python
- `.pl` : Perl
- `.rb` : Ruby
- `.js` : JavaScript

Bash、Zsh、sh で記述されたスクリプトファイルの拡張子には、慣習的に `.sh` が使用される。

また、OSの動作を制御するための Bash、 Zsh、 sh で記述されたスクリプトを特に **シェルスクリプト** と言う。Windows では、シェルスクリプトは バッチファイル と呼ばれる。

**スクリプトはインタプリタによって逐次解釈・実行されるため、実行前にコンパイルを行う必要がない**。

スクリプトを実行する場合、その実行環境には実行したいスクリプト言語に対応するインタプリタが必要となる。

シェルスクリプトのインタプリタとして機能するのが、[シェル](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E3%82%B7%E3%82%A7%E3%83%AB) である。

## シバン（shebang）/ `#!`

スクリプトファイルの先頭に記述する記号を **shebang**（シバン）と言う。

shebang によって、どのインタプリタを使用して実行するかを指定することができる。

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

...ここにスクリプトを記述する...
```

shebang に記述する言語はシェルスクリプトに限定されない。

```python:myscript.py
#!/usr/bin/env python3

...ここにPythonでスクリプトを記述...
```

以下の場合には shebang は無視される。


```console:子プロセスのシェルを起動して、子シェルをインタプリタとして機能させる
$ bash myscript.sh
```

```console:現在のシェルをインタプリタとして機能させる
$ source myscript.sh
$ . myscript.sh
```


## スクリプトファイルの実行方法

スクリプトファイルを実行する方法にはいくつかある。

これらの方法は [バイナリファイル](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E5%AE%9F%E8%A1%8C%E5%8F%AF%E8%83%BD%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%90%E3%82%A4%E3%83%8A%E3%83%AA%E5%BD%A2%E5%BC%8F) には適用できない。

## 実行権限を付与する

スクリプトファイルは [`$ chmod`](https://qiita.com/AsahinaKei/items/2fbc4e079b1c4b847240#-chmod) コマンドを使用して読み取り権限（`r`）、実行権限（`x`）を付与することによって、**実行可能ファイル** になる（バイナリファイルの実行は 実行権限 のみで良い）。

```console:myscript.shに実行権限を付与する
$ chmod +rx myscript.sh
```

[`$ bash`](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#-bash) や [`$ source`](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#-source---) の引数に渡して実行する場合には、読み取り権限（`r`）だけで実行できる。

## 実行する

実行権限を付与したファイルは、起動しているシェル上で実行することができる。

```console:スクリプトを実行する①
$ ./myscript.sh # myscript.sh がカレントディレクトリにある場合
```

スクリプトファイルが Bash によって記述されている場合、[`$ bash`](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#-bash) コマンドを使用して実行することもできる。この場合、ファイルに実行権限を付与する必要はなく **読み取り権限のみで良い**。

```console:スクリプトを実行する②
$ bash myscript.sh
```

`$ bash` コマンドと同様に [`$ source`](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#-source---) コマンドを利用する場合も、ファイルに実行権限を付与する必要はなく読み取り権限のみで良い。

```console:スクリプトを実行する③
$ source myscript.sh
```

また [`.`](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#-source---) コマンドは `$ source` コマンドと同じ機能を持つため、`$ .` を利用しても良い。

```console:スクリプトを実行する④
$ . myscript.sh
```

:::note info
テキスト形式のスクリプトは、**読み取り** 権限があれば良い。
バイナリ形式のスクリプトは、**実行** 権限が必要。 
:::


## デバッグモード

シェルスクリプトには **デバッグモード** と呼ばれる実行モードがあり、デバッグモードで実行すると、スクリプト内で実行される各コマンドが実行前に表示される。

```console:デバッグモードでスクリプトを実行する
$ bash -x スクリプト
```

スクリプトファイルの [shebang](#シバンshebang-) に `-x` を記述する方法でもデバッグモードで実行することができる。

```bash:デバッグモードでスクリプトを実行する
#!/bin/bash -x 
```

スクリプトの途中でデバッグモードへと切り替えたい場合、`$ set -x` を実行する。

```bash:スクリプト内でデバッグモードに切り替える
set -x # この行以降がデバッグモードで実行される
```

また同様に、スクリプトファイル中で元に戻すこともできる。

```bash:スクリプト内でデバッグモードを元に戻す
set +x # この行以降、デバッグモードが解除される
```

## 構文チェック

`$ set` コマンドの `-n` オプションを使用すると、スクリプトを実行せずに構文エラーがないかをチェックすることができる。

```console:構文エラーをチェックする
$ set -n スクリプトファイル
```

# 配列

`配列名[インデックス]` の形で配列を扱うことができる。インデックスは `0` が最小値。

```console:配列を宣言する①
$ 配列変数名[インデックス]=要素
```

```console:例
$ array[0]="apple"
$ array[1]="banana"
$ array[2]="orange"
```

```console:配列を宣言する②
$ 配列変数名=(要素1 要素2 要素3)
```

```console:例
$ array=(apple banana orange)
```

要素を取り出す際は [変数の展開](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E5%A4%89%E6%95%B0%E3%81%AE%E5%B1%95%E9%96%8B) を利用する。

```console:配列の要素を取得する
$ array=(apple banana orange)
$ echo ${array[0]}
apple
```

## `@` / `*`

インデックスに `@` もしくは `*` を使用すると、配列内のすべての要素を展開できる。

```console:配列内のすべての要素を展開する
$ array=(apple banana orange)
$ echo array[@]
apple banana orange
```

## スライス

```console:特定の範囲を取得する
$ array=(apple banana orange grape strawberry)
$ echo ${array[@]:0:3}
apple banana orange
```

## 配列の長さ

`#配列変数名` とすると、配列の長さ（要素数）を取得できる。

```console:配列の長さを取得する
$ echo ${#array}
```

## 要素の追加

`+=` 演算子を使うことで配列に要素を追加できる。

```console:要素の追加
$ array=(apple banana orange)
$ array+=(grape mango)
$ echo array[@]
apple banana orange grape mango
```

## 要素の削除

特定の要素を削除するには [`$ unset`](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#-unset) を使う。

```console:特定の要素を削除する
$ unset 配列変数名[インデックス]
```

## 配列の削除

```console:配列の宣言自体を削除する
$ unset 配列変数名
```

# 条件分岐

```bash:基本構文
if [[ 条件式 ]]; then
  コマンド
elif [[ 条件式 ]]; then
  コマンド
else
  コマンド
fi
```

`;` は改行によって省略することもできる。

```bash:基本構文
if [[ 条件式 ]]
then
    処理
elif [[ 条件式 ]]
then
    処理
else
    処理
fi
```


## `$ test`

条件式を評価して、**[終了ステータス](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E7%B5%82%E4%BA%86%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9--) として** 真（`0`）または偽（`1`）を返すシェル組み込みコマンド。

全てのコマンドは、実行した後の終了ステータスを持つ。`$ test` コマンド単体としては、標準出力に実行結果を吐き出したりしない。

多くのプログラミング言語では `true` や「真」を `1` で表現するが、シェルスクリプトでは終了ステータスにおける成功「真」は `0` で表現される。

```console:基本構文
$ test 条件式
```

`$ test` には [エイリアス](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9) `[` が存在するため、以下のようにすることもできる。

```console:エイリアス [ を使用して $ test を実行する 
$ [ 条件式 ]
```

閉める `]` は引数として扱われ、条件式を終了させる役割を持つ。`[` はコマンドとして機能しているため、`[` の後ろにスペースを入れなければならない。

また **`[[ 条件式 ]]`** では `&&` や `||` など機能が豊富なため、`[ 条件式 ]` よりも `[[ 条件式 ]]` の利用が推奨されている。

```console:推奨
$ [[ 条件式 ]]
```

:::note info
`[ 条件式 ]` よりも `[[ 条件式 ]]` の利用が推奨される。
:::

`$ test` コマンドの挙動の理解には、最後に実行したコマンドの終了ステータスが格納される [`$?`](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E3%83%93%E3%83%AB%E3%83%88%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%A7%E3%83%AB%E5%A4%89%E6%95%B0) を利用すると良い。

```console:$ test で理解
$ test "test" = "test" 
$ echo $?
0
$ test "test" = "test1"
$ echo $?              
1
```

```console:$ [ で理解
$ [[ "test" == "test" ]]
$ echo $?               
0
$ [[ "test" == "test1" ]]
$ echo $?                
1
```

## 文字列の比較

```console:文字列が空である（zero）
$ test -z 文字列
```

```console:文字列が空ではない（non-zero）
$ test -n 文字列
```

```console:文字列同士が等しい
$ test 文字列1 = 文字列2
```

`[[ 文字列1 == 文字列2 ]]` の時は `==` なので注意。

```console:文字列同士が等しくない
$ test 文字列1 != 文字列2
```

以下は `[[ 条件式 ]]` でのみ使用できる。

```console:文字列同士が等しい
$ [[ 文字列1 == 文字列2 ]]
```

```console:文字列同士が等しくない
$ [[ 文字列1 != 文字列2 ]]
```

```console:ワイルドカードの使用
$ [[ 文字列 == a*ple ]]
```

```console:正規表現の使用
$ [[ 文字列 =~ 正規表現 ]]
```

## 数値の比較

```console:数値同士が等しい（equals）
$ test 数値1 -eq 数値2
```

```console:数値同士が等しくない（not equals）
$ test 数値1 -ne 数値2
```

```console:数値1 > 数値2（greater than）
$ test 数値1 -gt 数値2
```

```console:数値1 >= 数値2（greater than or equal）
$ test 数値1 -ge 数値2
```

```console:数値1 < 数値2（less than）
$ test 数値1 -lt 数値2
```

```console:数値1 =< 数値2（less than or equals） 
$ test 数値1 -le 数値2
```

## ファイルの比較、存在チェック

```console:ファイルが存在する（exists）
$ test -e ファイル 
```

```console:ファイルが通常のファイル（非ディレクトリ）として存在する（file）
$ test -f ファイル # デバイスファイルは偽
```

```console:ファイルがディレクトリとして存在する（directory）
$ test -d ディレクトリ
```

```console:ファイルのサイズが0ではなく、空ファイルではない（size）
$ test -s ファイル
```

```console:ファイルが読み取り可能ファイルとして存在する（read）
$ test -r ファイル
```

```console:ファイルが書き込み可能ファイルとして存在する（write）
$ test -w ファイル
```

```console:ファイルが実行可能ファイルとして存在する（execute）
$ test -x ファイル
```

```console:ファイルがシンボリックリンクとして存在する（link）
$ test -L ファイル
```

```console:ファイル1 の更新日時が ファイル2 よりも新しい（newer than）
$ test ファイル1 -nt ファイル2
```

```console:ファイル1 の更新日時が ファイル2 よりも古い（older than）
$ test ファイル1 -ot ファイル2
```

## 条件の組み合わせ

```console:NOT 演算子（真偽を反転させる）
$ test !条件式
```

```console:AND 演算子
$ test 条件式1 -a 条件式2
```

```console:OR 演算子
$ test 条件式1 -o 条件式2
```

```console:AND 演算子の短絡評価
$ [ 条件式1 ] && [ 条件式2 ] # 条件式1 が真の場合のみ 条件式2 が評価される
```

```console:OR 演算子の短絡評価
$ [ 条件式1 ] || [ 条件式2 ] # 条件式1 が偽の場合のみ 条件式2 が評価される
```

以下は `[[ 条件式 ]]` でのみ使用できる。

```console:AND 演算子の短絡評価
$ [[ 条件式1 && 条件式2 ]]
```

```console:OR 演算子の短絡評価
$ [[ 条件式1 || 条件式2 ]]
```

# `case` 文

```bash:基本構文
case 式 in
    値1)
        処理 ;;
    値2)
        処理 ;;
    *) # default のとき
        処理 ;;
esac
```

パターンにはワイルドカード（`*`）を使用することができる。

`esac` は `case` のスペルを反転させたもの。

評価された「式」と、「値」との比較で条件を分岐させる。

# `for` 文

```bash:基本構文
for 変数 in 要素1 要素2 要素3; do
    処理
done
```

`;` は改行によって省略することができる。

```bash:基本構文
for 変数 in 要素1 要素2 要素3
do
    処理
done
```

`要素1 要素2 要素3` の部分には [配列](#配列) を使用することができる（要素を [展開](#--) させる必要がある）。

```bash:配列を使用した for 文
for 変数 in ${配列変数[@]}
do
    処理
done
```


`Bash` ではC言語のような構文がサポートされているため、以下のように書くこともできる。

```bash:C 言語風の for 文
for ((i = 1; i <= 5; i++))
do
    処理
done
```

[`$ seq`](#seq) コマンドを利用することで範囲を指定した `for` 文を実現できる。

```bash:$ seq を利用した for 文
for i in $(seq 1 5)
do
    処理
done
```

[ブレース展開](#ブレース展開) を利用することもできる。

```bash:ブレース展開を利用した for 文
for i in {1..5}
do
    処理
done
```

## `break` / `continue`

繰り返し処理を `break;` によって抜けたり、`continue;` によって以降の処理を実行せず次の繰り返し処理へ進むこともできる。

`;` は改行によって省略することができる。

## ブレース展開
Brace Expansion

文字列のリストを展開するための機能。

```console:文字列のブレース展開
$ echo {A, B, C}
A, B, C

$ echo file_{A, B, C}.txt
file_A.txt file_B.txt file_C.txt

$ echo {A..E}
A B C D E
```

```console:連番のブレース展開
$ echo {1..5}
1 2 3 4 5

$ echo file{1..3}.txt
file1.txt file2.txt file3.txt
```

```console:増分指定
$ echo {1..10..2}
1 3 5 7 9
```

## `$ seq`

```console:1 から最後の値まで連続する数値を生成する
$ seq 最後の値
```

```console:指定した範囲内で連続する数値を生成する
$ seq 最初の値 最後の値
```

```console:増分を指定する
$ seq 最初の値 増分 最後の値
```

# `while` 文

```bash:基本構文
while [ 条件式 ]; do
    処理
done
```

`;` は改行によって省略することができる。

```bash:基本構文
while [ 条件式 ]
do
    処理
done
```

`break;` でループを抜ける無限ループを利用することもできる。

```bash:無限ループを利用した while 文
while true
do
    if [ 条件式 ]
    then
        break;
    fi
done
```



# 関数

```bash:基本構文
関数名() {
    処理
}
```

[`$ function`](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#-function) を使った定義。

```bash:$ function を使った関数定義
function 関数名() {
    処理1
    処理2
    処理3;
}
```

※ `function` で定義する場合、処理の末尾にはセミコロン `;` を記載する

## `return`

`return` を使用した場合、関数の処理を途中で終了させることができる。

```bash:処理を途中で終了させる
関数名() {
    if [ 条件式 ]; then
        return;
    fi
}
```

またスクリプトファイルで関数を実行した場合、関数内で最後に使用したコマンドの [終了ステータス](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E7%B5%82%E4%BA%86%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9--) が関数の終了ステータスになるが、これを `return` で明示的に指定することができる。

```bash:終了ステータスを明示的に指定する
関数名() {
    return 終了ステータス;
}
```

「終了ステータス」と「戻り値」は別のものである点に注意。

## 戻り値

シェルスクリプトの関数では `$ echo` を使って何らかの値を戻すことが一般的。

```bash:戻り値を返す
関数名() {
    echo 値
}
```

`$ echo` に渡された値は [標準出力](https://qiita.com/AsahinaKei/items/301b88e464c23aba231e#%E6%A8%99%E6%BA%96%E5%87%BA%E5%8A%9B--stdout) に吐き出されるため、`=` を使って変数に代入することができる。

```bash:関数の戻り値を変数に代入する
変数=関数 # 関数内で $ echo によって返した値が変数に代入される
```

また `return` で返せる値は [終了ステータス](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E7%B5%82%E4%BA%86%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9--) `0` ~ `255` だけである点に注意。終了ステータスは [ビルトインシェル変数](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E3%83%93%E3%83%AB%E3%83%88%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%A7%E3%83%AB%E5%A4%89%E6%95%B0) の `$?` で参照する。

:::note info
`return` で返せるのは **終了ステータス**。
:::


## 関数を呼び出す

```bash:関数を呼び出す
関数名
```

```bash:関数に引数を渡す
関数名 引数1 引数2 引数3
```

関数の実行時に渡した引数は、関数内で [ビルトインシェル変数](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E3%83%93%E3%83%AB%E3%83%88%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%A7%E3%83%AB%E5%A4%89%E6%95%B0) の `$1`、`$2` などで参照することができる。

```bash:関数内で渡された引数を参照する
関数名() {
    echo $1 # 1つ目に渡された引数
    echo $2 # 2つ目に渡された引数
}
```

関数の外側のスコープでは `$1`、`$2` には [コマンドライン引数](#コマンドライン引数) が格納されている。そのため、コマンドライン引数として渡した値は、関数の外からしか参照できないという点。

もしコマンドライン引数を関数内で使用したい場合、以下のように、一度関数の外で変数に移し替える必要がある。

```console:コマンドライン引数
$ bash スクリプトファイル 引数1 引数2
```

```bash:コマンドライン引数を関数内で使用する
var=$1 # 一度変数に移し替える
関数() {
    var が使える
}
```

:::note info
コマンドライン引数で渡した引数は、関数の外でしか参照できない
:::

# `$ exit`

スクリプトの実行を即座に終了する。

```console:現在のシェルセッションや、スクリプトの実行を終了させる
$ exit
```

引数に [終了ステータス](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E7%B5%82%E4%BA%86%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9--) を指定することで、呼び出し元に終了ステータスを返却することができる。

```console:終了ステータスを指定しながらスクリプトを終了する
$ exit 終了ステータス
```

`$ exit` コマンドの終了ステータスには、直前のコマンドの [$?](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E3%83%93%E3%83%AB%E3%83%88%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%A7%E3%83%AB%E5%A4%89%E6%95%B0) をそのまま引数として渡すこともできる。

```console:直前のコマンドの終了ステータスをそのまま親プロセスに返す
$ exit $?
```

https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#-exit

# コマンドライン引数

コマンド、もしくは スクリプトの実行時に指定することができる引数。

オプションもコマンドライン引数の一つ。

```console:コマンドライン引数
$ コマンド 引数1 引数2
$ スクリプトファイル 引数1 引数2
```

指定した引数は、スクリプトファイル中で `$1`、`$2`、`$3` のように参照できる（[ビルトインシェル変数](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E3%83%93%E3%83%AB%E3%83%88%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%A7%E3%83%AB%E5%A4%89%E6%95%B0)）。

## `$ getopts`

コマンドラインオプションを解析できる組み込みコマンド（`$ getopt` は [外部コマンド](https://qiita.com/AsahinaKei/items/22d9fae93cabfbf137ee#%E5%AE%9F%E8%A1%8C%E5%8F%AF%E8%83%BD%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%90%E3%82%A4%E3%83%8A%E3%83%AA%E5%BD%A2%E5%BC%8F)）。

シェルスクリプト内で **オプション引数** を実現するために利用される。

オプション引数とは以下の部分のように、**オプションが受け取る引数** のことを指す。

```console:オプション引数
$ コマンド -オプション オプション引数
```

```console:$ getopts の基本構文
$ getopts オプションの仕様 変数名
```

**`オプションの仕様`**
各オプションを 1 文字で指定する。指定した文字の後ろにコロン `:` の有無が、そのオプションが引数を取るかどうかを表す。
具体的には、引数が必要なオプションについてはコロン `:` を付ける。`:` が指定されていた場合、オプション引数は **`$OPTARG`** で取得することができる。
反対に、引数が必要ないオプションはコロン `:` をつけない。

**`変数名`**
解析したオプションの名前を格納する変数。

```bash:（例）オプション -a を作成する
while getopts "a" opt
do
    case $opt in
        a)
            処理 ;; # -a が指定された時の処理
        \?)
            処理 ;; # 無効なオプションが指定された時のエラー処理
  esac
done
```

※ 無効なオプション **`/?`**
`?` はワイルドカードでも使用されるため、`\` でエスケープしている。`?` は特殊文字などではなく単なる文字列で、慣習的に `\?` で「不明な引数」を表現している。

```bash:（例）オプション引数を受け取るオプション -a を作成する
while getopts "a:" opt; do
    case $opt in
        a)
            処理 ;; # -a のオプション引数を $OPTARG で参照できる
        \?)
            処理 ;; # 無効なオプションが指定された場合のエラー処理
    esac
done
```

# Tips

関数を複数定義するような場合、主となる処理を `main()` 関数でまとめる方法がよく使用される。

```bash:myscript.sh
main() {
    関数1
    関数2
}

関数1() {
    処理
}

関数2() {
    処理
}

main # 関数の実行
```