シェルスクリプトはプログラミング言語なのか?C言語と比べて考えてみた
この記事でわかること
- シェルのif文は「コマンドの成否」を見ているだけ
- シェルがプログラミング言語として成立する理由
- C言語との一番大きな違い
- 型の違いも大きい
- 言語レベルで見た位置づけ
- シェル自体もC言語で作られている
シェルスクリプトに if 文があるのを見て「これって普通のプログラミング言語と何が違うの?」と思ったことはないでしょうか。C言語と比べながら整理してみました。
シェルのif文は「コマンドの成否」を見ているだけ
シェルのif文は、コマンドの終了コード(0か非0か)を見ているだけです。
if ls /tmp; then
echo "存在する"
fi
コマンドが成功(終了コード0) なら then ブロックへ、失敗(非0) なら else へ進みます。
[ -f file.txt ] みたいな書き方も、実は test というコマンドを呼んでいます。
if [ -f file.txt ]; then
echo "ファイルある"
fi
つまりシェルの if は「コマンドの成否で分岐する構文」です。
シェルがプログラミング言語として成立する理由
プログラミング言語として成立するには、順次実行・分岐・繰り返し・変数・関数・入出力の6要素が揃えばいいとされています(チューリング完全)。
シェルはこれを全部こんな形で持っています。
| 要素 | シェルでの実装 |
|---|---|
| 順次実行 | コマンドを1行ずつ実行する |
| 分岐 | if(コマンドの終了コードで分岐) |
| 繰り返し | for while |
| 変数 | NAME=casio |
| 関数 | function foo() { ... } |
| 入出力 | パイプ | やリダイレクト > |
C言語との一番大きな違い
C言語はメモリ上のデータを扱う言語です。
int x = 10;
x = x + 5;
シェルはプロセスとファイルを扱う言語です。
cat file.txt | grep "hello" > result.txt
シェルにとって「プログラムを呼ぶ」こと自体が言語の基本操作なんですね。
型の違いも大きい
C言語は int float char と型を区別しますが、シェルは全部文字列です。
X=42 # 文字列"42"
X=42+1 # 計算されない。文字列"42+1"のまま
expr 42 + 1 # 計算したければexprコマンドを呼ぶ
計算すらコマンドに投げるのがシェルの思想です。
言語レベルで見た位置づけ
低レベル(ハードに近い)
│
├── アセンブリ
├── C言語(メモリ直接操作・コンパイル)
├── C++
├── Java / Python
└── シェルスクリプト(OSのコマンドを呼ぶだけ)
│
高レベル(抽象的)
シェルは一番高レベル寄りにいます。
シェル自体もC言語で作られている
bash も zsh も、C言語で書かれた普通の実行ファイルです。
file /bin/bash
# → /bin/bash: Mach-O 64-bit executable arm64
構造を整理するとこうなります。
ハードウェア(CPU・メモリ)
│
カーネル(OS本体、C言語)
│
シェル(C言語で作られたプログラム)
│
ls, grep, cat...(これらもCで作られたプログラム)
│
シェルスクリプト(シェルが解釈して上記を呼ぶ)
全部Cが土台にいて、その上に乗っています。
シェルスクリプトを書くとき、「Cで書かれたシェルが、Cで書かれたコマンドを、シェル言語で組み合わせている」という入れ子構造になっているわけです。
まとめ
シェルは「計算する言語」ではなく「プログラムを組み合わせる言語」です。C言語がデータを処理するのに対し、シェルはプロセスを操るのが本質。コマンドを自動化しようとしたら自然に言語の構造が必要になった、という歴史的な流れでできています。
