【Linux】シェルスクリプトの基本構文まとめ

# シェルスクリプトの基本構文
シェルスクリプトの基本的な構文を後から見返す用でまとめました！

## コマンド、コメントの書き方
- **複数行のコマンド**: 順番に何行でもコマンドを記述可能。
- **1 行で複数コマンド**: セミコロン (`;`) でコマンドを区切ることで、1 行に複数のコマンドを記述可能。
  - 例: `echo "Hello"; echo "World"`
- **複数行にまたがるコマンド**: バックスラッシュ (`\`) を使って改行できる。
  - 例: `echo "Hello" \`
- **コメント**: `#` を使ってコメントを記述。
  - 例: `# これはコメントです`

## シェルスクリプトの変数

- **変数の代入**: 変数に値を代入する際、`=` の前後にスペースは入れない。変数名の前に `$` は不要。
  - 例: `変数名=値`
- **変数の参照**: `$変数名` で参照。
  - 例: `echo $変数名`
- **文字列の連結**: `{}` で変数を囲むことで他の文字と連結できる。
  - 例: `echo "${HOGE}_HOGE"`
- **変数名に使える文字**: アルファベット、数値、アンダースコア（`_`）。先頭に数値は使えない。

## クォーティング（引用符）

- **シングルクォート (`''`)**: 変数や特殊文字もそのまま文字列として扱われる。
  - 例: `'This is $var'` → `This is $var`（変数展開されない）
- **ダブルクォート (`""`)**: 変数展開やコマンド置換が有効。
  - 例: `"This is $var"` → `This is 変数の値`

## コマンド置換

- **コマンドの実行結果を変数に代入**: `$()` を使って、コマンドの実行結果を取得できる。
  - 例: `filename=$(ls)` → `filename` に `ls` コマンドの結果を代入。
- **ダブルクォート内でのコマンド置換**: ダブルクォート内でもコマンド置換が使える。
  - 例: `"Result: $(date)"` → `Result: 日付`
    仕組みとしては$()で囲むことで結果を標準出力に置き換えている

## 位置パラメータで引数を扱う

### 実行例:

```bash
./<シェルスクリプト名> arg1 arg2 arg3
```

- **`$0`**: スクリプト名。
- **`$1, $2, ...`**: 各引数を参照。
- **`$#`**: 引数の数を取得。

### 引数全体の参照

- **`$@`**: 引数を個別に展開（各引数が独立した文字列として扱われる）
- **`$*`**: 引数を 1 つの文字列として展開（スペースで結合）

### 実行例

```bash
./<シェルスクリプト名> hoge1 hoge2 hoge3
```

```bash
#!/bin/bash
./some_command "$@"
```

- **`"$@"`** を使うと、引数が個別の文字列としてコマンドに渡される。

## 制御構造
シェルの条件分岐や繰り返し処理のことを制御構造と呼ぶ

## if

```bash
if [ 条件式 ]; then
  処理
elif
  処理
else
  処理
fi
```

#### 注意点

- 条件式の後の`;`は省略できない
- []の前後にスペースを入れないとエラーになる
  - `if[条件式]` if と[]の間にスペースがないため ✖️
  - `if ["hoge" = "hoge"]`hoge などの文字列と[]の間にスペースがないため ✖️

#### shell の if の仕様

shell では if の後には必ずコマンドを置かなければならない
`[`はシェルの組み込みのコマンド
`[`の後ろに引数をとる。最後に`]`という引数をとることになっている

```bash
if [ "hoge" = "foo" ]; then
```

上記で言うと
第１引数: "hoge"
第２引数: =
第３引数: "foo"
第４引数: ]
ということになる
前後にスペースが必要だったのは、引数だったため

#### コマンドと終了ステータス

ls や grep などのコマンドは終了時に終了ステータスを返す
この値は`$?`というシェル変数から参照できる
基本的に正常時に`0`
エラー時には`0`以外を返す

#### if と終了ステータス

`[`コマンドは引数に記載された条件式を判定し、真なら 0、それ以外なら 0 以外を終了ステータスとして返す
`[`はコマンドの終了ステータスを確認するため、`[`以外のコマンドを渡し、そのコマンドの実行結果を見ることもできる
ちなみに終了ステータスのみ確認したいケース
例えば`grep`コマンドなどを`[`の引数にした場合は、デフォルトでは grep の結果が出力される。
出力結果は不要で合致するデータがあるかどうかの判定のみに使いたい場合は`-q`オプションを仕様すれば、終了ステータスだけ返すことができる。

### `test`コマンドと`[`の違い

- `test`コマンドと`[`は同じ機能を持っているが、`[`は最後に`]`の引数が必要。
  - 例：`[ -n "hoge" ]` は `test -n "hoge"` と同じ。

---

### 文字列の比較演算子

| 演算子 | 意味           | 例                       |
| ------ | -------------- | ------------------------ |
| `=`    | 等しい         | `[ "$str1" = "$str2" ]`  |
| `!=`   | 等しくない     | `[ "$str1" != "$str2" ]` |
| `-n`   | 空文字ではない | `[ -n "$str" ]`          |
| `-z`   | 空文字である   | `[ -z "$str" ]`          |

---

### 数値の比較演算子（整数のみ）

| 演算子 | 意味       | 例                        |
| ------ | ---------- | ------------------------- |
| `-eq`  | 等しい     | `[ "$num1" -eq "$num2" ]` |
| `-ne`  | 等しくない | `[ "$num1" -ne "$num2" ]` |
| `-lt`  | 小さい     | `[ "$num1" -lt "$num2" ]` |
| `-le`  | 以下       | `[ "$num1" -le "$num2" ]` |

---

### ファイル属性の評価

| 演算子            | 意味                      | 例                        |
| ----------------- | ------------------------- | ------------------------- |
| `-e`              | ファイルが存在する        | `[ -e "file" ]`           |
| `-d`              | ディレクトリである        | `[ -d "dir" ]`            |
| `-h`              | シンボリックリンクである  | `[ -h "link" ]`           |
| `-L`              | シンボリックリンクである  | `[ -L "link" ]`           |
| `-f`              | 通常ファイルである        | `[ -f "file" ]`           |
| `-r`              | 読み取り可能である        | `[ -r "file" ]`           |
| `-w`              | 書き込み可能である        | `[ -w "file" ]`           |
| `-x`              | 実行可能である            | `[ -x "file" ]`           |
| `file1 -nt file2` | file1 が file2 より新しい | `[ "file1" -nt "file2" ]` |
| `file1 -ot file2` | file1 が file2 より古い   | `[ "file1" -ot "file2" ]` |

---

### 演算子の結合

| 演算子 | 意味                     | 例                         |
| ------ | ------------------------ | -------------------------- |
| `-a`   | AND (条件 1 かつ条件 2)  | `[ 条件1 -a 条件2 ]`       |
| `-o`   | OR (条件 1 または条件 2) | `[ 条件1 -o 条件2 ]`       |
| `!`    | NOT (条件式を否定)       | `[ ! 条件式 ]`             |
| `()`   | 条件式のグループ化       | `[ \( 条件1 -a 条件2 \) ]` |

- `()`は bash のメタ文字なので、エスケープが必要です。

---

### 終了ステータスを用いた構文

- `&&`: コマンドが成功した場合に次のコマンドを実行します。
  - 例：`[ -e "file" ] && echo "ファイルが存在します"`
- `||`: コマンドが失敗した場合に次のコマンドを実行します。
  - 例：`[ -e "file" ] || echo "ファイルが存在しません"`

---

### シェルスクリプトの終了ステータス

- シェルスクリプトでは、最後に実行されたコマンドの終了ステータスが返されます。
- 明示的に終了ステータスを返す場合は、`exit`コマンドを使用します。

  ```bash
  exit <終了ステータス>
  ```

## for
```bash
for 変数名 in リスト
do
  繰り返し処理
done
```

- `for`ループは、指定したリストの各要素に対して繰り返し処理を行う。

### `seq`コマンドを使った数値列の出力

`seq`コマンドを使うことで、指定した範囲の数値を生成し、連番処理を行うことができる。例えば、1 から 5 までの数値でループ処理を行う場合の例：

```bash
for i in $(seq 1 5)
do
  echo "数値: $i"
done
```

- `seq 1 5`は、1 から 5 までの数値を生成する。
- 各数値が変数`i`に代入され、`echo`コマンドで表示される。

### コマンドライン引数に対する処理

全てのコマンドライン引数に対して繰り返し処理を行うには、`$@`を使用する。これにより、すべての引数がリストとして展開され、各引数に対してループ処理を行う。

例：

```bash
for arg in "$@"
do
  echo "引数: $arg"
done
```

- `$@`はすべてのコマンドライン引数をリストとして展開する。
- 明示的に`$@`を記載することで、混乱を防ぎ、明確なコードになる。

## case
```bash
case <文字列> in
  <パターン1>)
    処理
    ;;
  <パターン2>)
    処理
    ;;
  *)
    それ以外の場合の処理
    ;;
esac
```

- `case`文は、指定した文字列がパターンに一致するかどうかをチェックし、一致するパターンの処理を実行する。
- `*)` はデフォルトケースで、どのパターンにも一致しなかった場合に実行される。
- 各パターンの後には`;;`を付けて処理を終了させる。

### `case`文の使い方

1. **`<文字列>`**：評価したい値（変数など）を指定。
2. **`<パターン>`**：文字列と照合したい条件を指定。パターンはワイルドカードや正規表現の一部を使うことができる。
3. **`;;`**：処理の終了を示す。
4. **`*)`**：どのパターンにも一致しなかった場合に実行されるデフォルトの処理。


### 具体例

1. **曜日に応じたメッセージを表示する例**

```bash
day="月曜日"

case $day in
  "月曜日")
    echo "今日は月曜日です。"
    ;;
  "火曜日")
    echo "今日は火曜日です。"
    ;;
  *)
    echo "指定された曜日は存在しません。"
    ;;
esac
```

- `day`変数が`"月曜日"`の場合、最初のパターンが一致し、対応するメッセージが表示される
- どの曜日にも一致しない場合は、デフォルトの`*)`が実行される。

2. **数字に応じた処理を実行する例**

```bash
num=3

case $num in
  1)
    echo "数値は1です。"
    ;;
  2)
    echo "数値は2です。"
    ;;
  3)
    echo "数値は3です。"
    ;;
  *)
    echo "範囲外の数値です。"
    ;;
esac
```

- `num`が`3`のとき、対応する処理が実行される。

3. **ワイルドカードの利用**

```bash
filename="document.txt"

case $filename in
  *.txt)
    echo "これはテキストファイルです。"
    ;;
  *.jpg | *.png)
    echo "これは画像ファイルです。"
    ;;
  *)
    echo "不明なファイル形式です。"
    ;;
esac
```

- `*.txt`：拡張子が`.txt`のファイルに一致する。
- `*.jpg | *.png`：`.jpg`または`.png`の拡張子を持つファイルに一致する。

---

### `case`文のメリット

- `case`文は、複数の条件分岐が必要なときに、`if`文よりも見通しが良く、簡潔に書くことができる。
- 特に、文字列やパターンマッチに強く、ファイル拡張子や特定のコマンドライン引数の処理に便利。

## While
```bash
while <コマンド>
do
  繰り返し処理
done
```

- `while`文は、条件が真（0 を返す）である間、ループを繰り返し実行する
- `<コマンド>`が成功（終了ステータスが 0）すると、`do`のブロックが実行される。
- 条件が偽（0 以外のステータス）になるとループが終了する。

**例：1 から 5 までカウントする`while`文**

```bash
i=1
while [ $i -le 5 ]
do
  echo "カウント: $i"
  i=$((i + 1))
done
```

### 算術展開について

`$(( ... ))` を使うことで、シェルでの算術演算を簡単に実行することができる。この算術展開は、`expr`コマンドに比べて効率的で、シンプルに書ける

**算術展開の構文**

```bash
$(( 式 ))
```

**例：算術展開を使った計算**

```bash
a=5
b=3
result=$(( a + b ))
echo "結果: $result"  # 出力: 結果: 8
```

#### `expr`コマンドとの比較

- **`expr`コマンド** はシェルスクリプトで昔から使われている算術演算用のコマンド。
ただ`expr`を使うと、毎回コマンドを呼び出すため、処理が重たくなる。

  **例：`expr`を使った計算**

  ```bash
  result=$(expr 5 + 3)
  echo "結果: $result"  # 出力: 結果: 8
  ```

- そのため、**算術展開 `$(( ))`** を使用するほうが、より効率的で読みやすいコードを書ける。

---

### while 文と算術展開の組み合わせ例

`while`文で算術展開を使うことで、効率的に数値処理が可能。

**例：1 から 10 までの合計を求める`while`文**

```bash
i=1
sum=0
while [ $i -le 10 ]
do
  sum=$(( sum + i ))
  i=$(( i + 1 ))
done
echo "合計: $sum"
```

この例では、`while`文で 1 から 10 までの数を加算し、合計を出力しています。算術展開を使うことで、処理が軽量かつシンプルになる。

---

### 補足：`while`文で無限ループを作る場合

無限ループは、`while true`を使って作成できます。この方法は、特定の条件が発生するまで処理を続けたい場合に便利。

```bash
while true
do
  echo "無限ループ中..."
  sleep 1  # 1秒間の休止を入れる
done
```

## シェル関数

```bash
function 関数名 () {
    処理
}
```

- シェルスクリプト内で繰り返し使う処理は、関数としてまとめると便利。
- 関数は定義した後、いつでも呼び出すことができる。

### シェル関数の引数

関数には引数を渡すことができ、関数内ではこれらの引数を使って処理を行う。

**引数の取り扱い**

- 関数内での引数は、シェルスクリプト自体の引数と同様に、`$1`, `$2` といった形式で参照できる。
- 例えば、関数呼び出し時に渡された最初の引数は `$1`、2 番目の引数は `$2` となる。

**例：引数を使った関数**

```bash
function greet() {
    echo "こんにちは、$1さん！"
}

greet "太郎"  # 出力: こんにちは、太郎さん！
```

- この例では、関数`greet`に「太郎」を引数として渡し、関数内でそれを`$1`として参照している。

---

### 関数の終了ステータス

- シェル関数の終了ステータスは、関数内で実行された最後のコマンドの終了ステータスがデフォルトで返される。
- 明示的に終了ステータスを指定したい場合は、`return`を使用して値を返すことができる。

**例：終了ステータスを返す関数**

```bash
function add_numbers() {
    local sum=$(( $1 + $2 ))
    echo "合計は: $sum"
    return $sum  # 終了ステータスとして合計を返す
}

add_numbers 3 5  # 出力: 合計は: 8
echo $?          # 8（終了ステータスを表示）
```

- `return`は数値を返すために使われ、呼び出し元で終了ステータスとして参照できる。
- 関数内で処理が正常に終了したかを判断するために、終了ステータスを活用できる。

---

### シェル関数を使うメリット

- **コードの再利用**：同じ処理を何度も記述するのではなく、関数としてまとめて再利用可能にする。
- **可読性の向上**：関数を使うことで、スクリプト全体の構造がわかりやすくなる。

---

### 例：引数と終了ステータスを組み合わせた関数

```bash
function check_file_exists() {
    if [ -e "$1" ]; then
        echo "ファイル '$1' は存在します。"
        return 0  # 正常終了
    else
        echo "ファイル '$1' は存在しません。"
        return 1  # エラー終了
    fi
}

check_file_exists "test.txt"
if [ $? -eq 0 ]; then
    echo "処理が成功しました。"
else
    echo "エラーが発生しました。"
fi
```

- `check_file_exists`関数は、ファイルが存在するかどうかを確認し、存在すれば 0、存在しなければ 1 を終了ステータスとして返す。

## シェルスクリプトを記載するときに意識すべきこと

### 1. 標準出力と標準エラー出力は出し分ける

- **標準出力**（`stdout`）には、正常な結果を出力する。
- **標準エラー出力**（`stderr`）には、エラーメッセージや警告など、異常時のメッセージを出力する。
- エラーと通常の出力を区別することで、スクリプトの管理がしやすくなる。

**例**:

```bash
echo "正常なメッセージ"  # 標準出力
echo "エラーメッセージ" 1>&2  # 標準エラー出力
```

### 2. 必要のない出力は出さない

- 不要な出力は抑える。例えば、ファイルが見つからないといった通常動作の範囲であれば、冗長なエラーメッセージを避ける。
- スクリプトの結果がユーザーにとって有用な情報のみになるよう意識する。

### 3. ヘルプを出力できるようにする

- スクリプトに引数が渡されなかった場合や、誤った引数が渡された場合には、使い方（ヘルプ）を出力する。
- これにより、ユーザーがスクリプトの正しい使い方を理解しやすくなる。

**例**:

```bash
if [ $# -eq 0 ]; then
    echo "使い方: ./script.sh [オプション]"
    exit 1
fi
```

### 4. ヒアドキュメントを活用する

- ヒアドキュメント（Here Document）を使うことで、複数行のテキストをスクリプト内に簡単に記述し、指定した終了文字までの内容を標準出力に出力できる。
- ヘルプメッセージや説明文を整形して表示するのに便利。

**例**:

```bash
cat << EOF
使い方:
    ./script.sh [オプション]

オプション:
    -h  ヘルプを表示
EOF
```

## 参考書籍

https://www.kinokuniya.co.jp/f/dsg-01-9784815624316