シェルスクリプトの特殊変数

シェルスクリプトをより柔軟かつ強力にする特殊変数について解説します。これらの変数は、コマンドライン引数、スクリプト名、プロセスIDなど、スクリプトの実行環境に関する重要な情報を提供します。

## 最初のスクリプト作成

特殊変数の使い方を説明するために、簡単なシェルスクリプトを作成しましょう。

`special_vars.sh` というファイルを作成し、以下の内容を追加します。

```shell
#!/bin/bash

echo "スクリプト名: $0"
echo "最初の引数: $1"
echo "2番目の引数: $2"
echo "すべての引数: $@"
echo "引数の数: $#"
echo "プロセスID: $$"
```

各行の動作は以下のとおりです。

* `#!/bin/bash`: shebang。システムにbashを使ってこのスクリプトを解釈するように指示します。
* `$0`: スクリプトの名前を保持する特殊変数。
* `$1` と `$2`: それぞれ1番目と2番目のコマンドライン引数を表します。
* `$@`: スクリプトに渡されたすべてのコマンドライン引数を表します。
* `$#`: コマンドライン引数の数を示します。
* `$$`: 現在のシェルのプロセスIDを提供します。

ファイルを保存した後、実行権限を付与します。

```shell
chmod +x special_vars.sh
```


## 引数付きでスクリプトを実行する

作成したスクリプトを、さまざまな引数で実行して、特殊変数の動作を確認してみましょう。

1. 引数なしでスクリプトを実行します。

```shell
./special_vars.sh
```

出力は以下のようになります。

```
スクリプト名: ./special_vars.sh
最初の引数: 
2番目の引数: 
すべての引数: 
引数の数: 0
プロセスID: 1234
```

最初の引数と2番目の引数は空で、引数が指定されていないため、引数の数は0です。

2. 引数を指定してスクリプトを実行します。

```shell
./special_vars.sh hello world
```

出力は以下のようになります。

```
スクリプト名: ./special_vars.sh
最初の引数: hello
2番目の引数: world
すべての引数: hello world
引数の数: 2
プロセスID: 1235
```

変更点は以下のとおりです。

* `$1` には "hello" が含まれるようになりました。
* `$2` には "world" が含まれるようになりました。
* `$@` はすべての引数 "hello world" を表示します。
* `$#` は、2つの引数が指定されたため、2 を表示します。

プロセスID (`$$`) は、オペレーティングシステムによって割り当てられるため、スクリプトを実行するたびに異なる場合があります。


## `$?` と `$!` について

他の重要な特殊変数として、`$?` と `$!` があります。

`exit_status.sh` というファイルを作成し、以下の内容を追加します。

```shell
#!/bin/bash

echo "正常終了するコマンドの実行:"
ls /home
echo "終了ステータス: $?"

echo "失敗するコマンドの実行:"
ls /nonexistent_directory
echo "終了ステータス: $?"

echo "バックグラウンドプロセスの実行:"
sleep 2 &
echo "最後のバックグラウンドコマンドのプロセスID: $!"
```

このスクリプトは以下の動作をします。

* `$?` は、最後に実行されたコマンドの終了ステータスを示します。通常、0は成功を意味し、0以外の値はさまざまなエラー状態を示します。
* `$!` は、最後に実行されたバックグラウンドコマンドのプロセスIDを示します。
* コマンドの末尾にある `&` は、バックグラウンドでコマンドを実行します。

ファイルを保存し、実行権限を付与した後、スクリプトを実行します。

```shell
chmod +x exit_status.sh
./exit_status.sh
```

出力は以下のようになります。

```
正常終了するコマンドの実行:
labex
終了ステータス: 0
失敗するコマンドの実行:
ls: cannot access '/nonexistent_directory': No such file or directory
終了ステータス: 2
バックグラウンドプロセスの実行:
最後のバックグラウンドコマンドのプロセスID: 1236
```


注目すべき点:

* 最初の `ls` コマンドは成功するため、`$?` は 0 です。
* 2 番目の `ls` コマンドは（ディレクトリが存在しないため）失敗するため、`$?` は 2（エラーを示す 0 以外の値）です。
* `sleep` コマンドはバックグラウンドで実行され、`$!` はそのプロセスIDを示します。


## 関数における特殊変数の使用

特殊変数は関数内でも使用できます。

`function_vars.sh` というファイルを作成し、以下の内容を追加します。

```shell
#!/bin/bash

function print_args {
  echo "関数名: $0"
  echo "最初の引数: $1"
  echo "2番目の引数: $2"
  echo "すべての引数: $@"
  echo "引数の数: $#"
}

echo "2つの引数で関数を呼び出す:"
print_args hello world

echo "4つの引数で関数を呼び出す:"
print_args one two three four
```

このスクリプトは、特殊変数を使用する関数 `print_args` を定義しています。そして、異なる数の引数でこの関数を2回呼び出します。

ファイルを保存し、実行権限を付与した後、スクリプトを実行します。

```shell
chmod +x function_vars.sh
./function_vars.sh
```

出力は以下のようになります。

```
2つの引数で関数を呼び出す:
関数名: ./function_vars.sh
最初の引数: hello
2番目の引数: world
すべての引数: hello world
引数の数: 2
4つの引数で関数を呼び出す:
関数名: ./function_vars.sh
最初の引数: one
2番目の引数: two
すべての引数: one two three four
引数の数: 4
```

注目すべき点:

* `$0` は依然としてスクリプト名を参照しており、関数名ではありません。
* `$1`、`$2`、`$@`、`$#` は、スクリプトの引数と同様に、関数の引数に対しても機能します。
* これらの変数の値は、関数が異なる引数で呼び出されるたびに変化します。


## `$@` と `$*` の違い

特殊変数 `$@` と `$*` はどちらもすべてのコマンドライン引数を表すために使用されますが、二重引用符で囲まれた場合の動作が異なります。

`at_vs_star.sh` というファイルを作成し、以下の内容を追加します。

```shell
#!/bin/bash

echo "Using $@:"
for arg in "$@"; do
  echo "Argument: $arg"
done

echo "Using $*:"
for arg in "$*"; do
  echo "Argument: $arg"
done
```

このスクリプトは、ループで使用した場合の `$@` と `$*` の違いを示しています。

ファイルを保存し、実行権限を付与した後、スペースを含む複数の引数を指定してスクリプトを実行します。

```shell
chmod +x at_vs_star.sh
./at_vs_star.sh "arg with spaces" another_arg "third arg"
```

出力は以下のようになります。


```
Using $@:
Argument: arg with spaces
Argument: another_arg
Argument: third arg
Using $*:
Argument: arg with spaces another_arg third arg
```

違いは以下のとおりです。

* `"$@"` では、各引数は個別のエンティティとして扱われます。スペースを含む引数は、単一の単位として保持されます。
* `"$*"` では、すべての引数がIFS (Internal Field Separator) 変数の最初の文字(通常はスペース)で区切られた単一の文字列に結合されます。


この違いは、スペースやその他の特殊文字を含む可能性のある引数を処理する必要がある場合に重要です。


## まとめ

その他の特殊変数に関する詳細については、bashマニュアル (`man bash`) を参照してください。