Pythonによる自作シェル講座の感想
目次
はじめに
以下講座を受講したのでその備忘録のメモ的な記事です。あまり読む価値はないと思います
自作シェルで学ぶLinuxシステムプログラミング―Pythonで150行の「シェル」を実装してLinuxのしくみを学ぼう
コマンドを実行する「シェル」を自作しながら、Linux のしくみや OS に近い領域のプログラミングを学ぼう!「シェルって何?コマンドの実行って何?標準入出力って何?ターミナルって何?」といった疑問をシェルの実装レベルで解消しよう!
ページを読み込めませんでした
講座の感想
実際に作ってみるととても簡単にシェルが出来上がってびっくりした。
「0, 1, 2にそれぞれ標準入力、標準出力、標準エラー出力がファイルディスクリプタとして割り当てられている」くらいの記憶はありましたが、実際にリダイレクトやハイプの簡易的な実装をこなうことその本質の理解に迫れたのが良かった。
アプリケーションレイヤーのプログラミングを行う際に直接役に立つことは少ないと思うが、知っておいて得しかない情報ばかりだった。
以下、講座受講中のメモ
そもそもシェルとは
シェルはユーザーが入力したコマンドを読み取り、プロガラムを実行する、特殊なプログラム。
よってシェルの中心的な処理は以下の二つに分類できる
- ユーザーの入力を読み込んで解析する
- 指定されたコマンド(プログラム)を実行する
カーネルの機能
- プロセス管理
- OSがプロセスを順番に割り当てて、複数のプロセスが同時に動いているかのように見せる(コアが一つなら同時に1つのプロセスしか動かせない)
- initプロセス → 全てのプロセスの親となるプロセス。systemd がよくinitプロセスと用いられる(dockerは一番親プロセスがinitプロセスではないので注意)
- initプロセスが sshdを起動しておいてくれるので、Bashなどのシェルが起動できる
- メモリ管理
- ファイルシステム
- etc..
⇒ シェルはコマンドを実行する機能があるが、プロセスを新しく生成することができるのはカーネルだけなので、シェルはプロセスの実行をカーネルに依頼することになるということ。
このプロセスの実行や、その他カーネルの処理を呼び出す処理を「システムコール」という。
Pythonでシステムコールする場合は、fork
, exec
という2つのシステムコールに対応したメソッドを利用する
シェルのコマンドの種類
- 内部コマンド → echo, cd, pwdなどシェル内部で実装されたコマンド。typeコマンドでそのコマンドが内部か外部かを区別できる
vscode ➜ /workspaces/udemy-crafting-shell $ type echo echo is a shell builtin
- 外部コマンド → 実行するプログラムのファイル名。プログラムの場所はPATHで指定する(フォルダが指定された場合、そのフォルダにある実行可能プログラムが全て外部コマンドとなる)。シェルはコマンドが内部コマンドでない場合、PATH一覧から該当するプログラムがないかを探す。cat, lsなどは外部コマンド
vscode ➜ /workspaces/udemy-crafting-shell $ type cat cat is /usr/bin/cat
linixシグナル
あるプロセスから別のプロセスに通信する方法の一種。
- SIGINT
ターミナルで Ctrl + C を入力すると、プロセスにSIGINTが送信される。プロセスのデフォルトの挙動として、SIGINTを受信するとプロセスの実行が終了することになっている。 - SIGTERM
- SIGKILL
カーネルが強制的にプロセスを停止するシグナル。プロセスはSIGKILLハンドラーを作成できないため、必ずプロセスを停止することができるシグナル。
kill コマンドはこのSIGKILLをプロセスに送信している - etc…
シグナルハンドリングを実装することでプロセスのデフォルトの挙動を修正することができる。
シェバン(shebang)
Linuxなどでスクリプトを実装する際、スクリプトの1行目に #!<インタプリタのパス>
と書かれている部分。
- #!/bin/bash
- #!/usr/bin/env python3
- …
Linuxにおけるシェバンの解釈
デフォルトではコマンドは最終的にexecveシステムコールに渡される
execve(”./script.sh”, [”./script.sh”], ….])
シェバンを指定すると、execveではなく、シェバンに指定されたインタプリタにコマンドが渡されるようになる
/bin/bash script.sh
標準入出力とリダイレクト、パイプ
リダイレクトやパイプの機能を使うことで、標準入力、標準出力、標準エラー出力を切り替えることができる。
プロセスから見たファイルは、0, 1, 2, 3, 4 といった数字が割り当てられてる(プロセス内でopenしたファイルに数字が払い出される)この数字をファイルディスクリプタという。
プロセスがopenしているファイルは ls -l proc/{PID}/fd
をみると確認できる。 $$
には現在のPIDが格納されているので、 ls -l proc/{PID}/fd
とすると現在のプロセスのファイルディスクリプタを確認できる。
標準入力、標準出力、標準エラーはそれぞれ、0, 1, 2のファイルディスクリプタのこと。
リダイレクト
標準出力の切り替え: >
$ pwd > pwd.output
標準エラー出力の切り替え 2>
:
$ cat nofile.txt 2> error.output
パイプ
前のコマンドの標準出力を、次のコマンドの標準入力に受け流すことができる
cat /etc/os-release | grep VERSION
/dev/pts/0 とういファイルはなんなのか?
前提として、以下ディレクトリには特殊なファイルシステムが使われている
ディレクトリ | 配置されるファイル |
---|---|
/dev | デバイスファイル |
/proc | カーネルやプロセスの情報 |
/sys | カーネルやデバイスの情報 |
※ これらの情報はストレージ上に書き込まれているわけではない。OSのカーネル内のオペレーション上の情報をファイルと同じような扱いにしているだけ。”Everything is a file” というLinuxの哲学の一つ。Linuxにおいて、「ファイルはデータを読み書きできるもの」くらい抽象的な存在である。
シェルとターミナルの違い
本来のターミナルは、コンピュータに接続して使う、ディスプレイとキーボードがくっついたハードウェアのことを指していた(この画面はいつも見てるターミナルの黒い画面のみということ)。
現在ではこの物理的なターミナルを使われることはなく、ソフトとして入っている「ターミナルエミュレータ」というソフトウェアをターミナルと呼んでいるということ。
ターミナルは起動すると /dev/pts/0 に接続され、この /dev/pts/0 を介してシェルを扱っている。
よって、ターミナルは入力装置で、実際にプログラムを実行しているのはシェルという整理ができそう。
この違いを意識すると、clearコマンドの原理を理解できる。