Skip to content

Commit 5f50748

Browse files
修正、参考文献追加
1 parent 805e150 commit 5f50748

File tree

1 file changed

+72
-31
lines changed

1 file changed

+72
-31
lines changed

nest/37-midori.md

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,29 @@ author: "midori"
1111
「え、こんなの使うの?ほんとにコマンド打っていいの?」
1212

1313
GUI(グラフィカルユーザーインターフェース)[^1]しか使ったことのない私にとって、あの黒い画面に意味の分からない文字列を打ち込み、エンターを押す瞬間は毎回緊張する体験でした。
14-
15-
エンジニアとして働き始めた後も、Google Cloud Platform(GCP)やGitを操作するとき、経験豊富なエンジニアほどCLIを好んで使っています。ジュニアの私にとっては、操作対象が目に見えず、GUIのように直感的ではないCLIは、高度で難しいものに感じられ、CLIを使いこなすエンジニアは憧れの対象でもありました。
14+
エンジニアとして働き始めた後も、Google Cloud Platform(GCP)やGitを操作するとき、経験豊富なエンジニアほどCLIを好んで使っています。ジュニアの私にとっては、操作対象が目に見えず、GUIのように直感的ではないCLIは、高度で難しいものに感じられ、CLIを使いこなすエンジニアは憧れの対象でもありました。(Vimerほんとかっこいい)
1615

1716
ところが近年、AIの普及によってCLIを補助するツールが続々と登場しています。自然言語で会話するだけで、ChatGPTやGitHub Copilotがコマンドを生成してくれたり、補完や自動化を助けてくれたりするようになりました。これにより、ターミナルは「コマンドを入力する黒い画面」から「AIと人間が協力して使う対話の場」へと進化しつつあります。結果として、ターミナルへの心理的なハードルもぐっと下がり、身近な存在になり始めています。
1817

19-
しかし、そこで立ち止まって考えたいのが「そもそもターミナルはどう動いているのか?」という基礎です。ターミナルは表面的にはシンプルですが、実は50年以上基本構造が変わっていないようです。その裏側にはOSやシェルの仕組みと密接に関わっています。この仕組みを知ることは、AIとの未来を考えるうえでも大切です
18+
しかし、そこで立ち止まって考えたいのが「そもそもターミナルはどう動いているのか?」という基礎です。ターミナルは表面的にはシンプルですが、実は50年以上基本構造が変わっていないようです。その裏側はOSやシェルの仕組みと密接に関わっています。この仕組みを知ることは、AIのような新しい技術と付き合っていく上でも重要だと考えました
2019

2120
本記事では以下を目指します:
2221

2322
1. **ターミナルの歴史を知る**
2423
2. **ターミナルの仕組みを理解する**
25-
3. **TypeScriptでシンプルな擬似実装を行い、動きを直感的に理解する**
26-
27-
---
24+
3. **TypeScriptでシンプルな擬似実装を行い、ターミナルの仕組みを実際に確認する**
2825

2926
[^1]: グラフィカルユーザーインターフェースとは、マウスやアイコンなどを用いて直感的に操作できる仕組みのことです。
3027

3128
[^2]: インターフェースとは、人と機械が情報をやり取りする窓口のことです。ここでは「AIと人間が同じCLIを通じて対話する関係」を指しています。
3229

33-
---
3430

3531
## 2. ターミナルの歴史
3632
そもそも、ターミナルとは何でしょうか。調べてみると、コンピューターの歴史的な変遷と共に「コンピュータと人をつなぐ入出力装置」として進化してきたことが分かりました。まずはその歴史を見ていきたいと思います。
3733

3834
### 2.1 パンチカードの時代
3935

40-
コンピューターが誕生したばかりの1950年代、プログラムの入力は「パンチカード」と呼ばれる厚紙に穴を開けるという方法でした。複数枚のカードを束ねてリーダーに通すと、一度に処理を実行できる仕組みです
36+
1950年代、コンピューターへのプログラム入力は「パンチカード」と呼ばれる厚紙に穴を開ける方法で行われていました。複数枚のカードを束ね、カードリーダーに通すと一度に処理が実行されます
4137

4238
この時代のコンピューターは人間が即時に操作し、コンピューターが対話的に応答する仕組みは存在しませんでした。人間がカードを準備し、コンピューターが処理を行った後に、結果を後で紙に印刷するというサイクルで動いており、これは**バッチ処理**[^3]と呼ばれました。
4339

@@ -65,11 +61,7 @@ GUI(グラフィカルユーザーインターフェース)[^1]しか使っ
6561
UNIXの大きな特徴のひとつが、「すべてをファイルとして扱う」 という設計思想です。
6662

6763
ここでいう「ファイル」とは、単にハードディスク上の文書や画像だけではありません。
68-
キーボード
69-
ディスプレイ
70-
プリンタ
71-
通信ポート
72-
といった 入出力装置までも「特別なファイル」 として表現されました。
64+
キーボード、ディスプレイ、プリンタ、通信ポートといった 入出力装置までも「特別なファイル」 として表現されました。
7365
端末も例外ではなく、`/dev/tty` というファイルを通じて利用されます。`dev``device``tty``teletypewriter`の略です。
7466
プログラムは `/dev/tty` に書き込めば端末に文字が表示され、読み込めばユーザーのキー入力を得られる仕組みです。
7567

@@ -78,7 +70,6 @@ UNIXの大きな特徴のひとつが、「すべてをファイルとして扱
7870
### 2.3 ディスプレイ端末と制御コード
7971
1970年代後半には、紙ではなくディスプレイに文字を映すビデオ端末が普及します。代表例が1978年登場の VT100 です。
8072
VT100は制御コード(ANSIエスケープ)を導入し、アプリは文字列を出力するだけで端末がカーソル移動・色付け・画面消去を実行できるようになりました。
81-
端末ごとの差異(“方言”)はtermcap/terminfoといった抽象化レイヤが吸収し、アプリは「やりたいこと」を宣言し、ライブラリが「端末固有の制御列」に翻訳する形へ収れんしました。
8273
この仕組みは、現代の `ls --color` やプログレスバー表示にも通底しています。
8374
![By Jason Scott - Flickr: IMG_9976, CC BY 2.0, https://commons.wikimedia.org/w/index.php?curid=29457452](https://storage.googleapis.com/zenn-user-upload/5f56c5387dcf-20250908.jpg){style="width:90%;"}
8475

@@ -322,7 +313,7 @@ ls > result.txt
322313
### 4.2 実装
323314

324315

325-
| ラベル | 要素名 | これは何? | コード上の実体 | 何の代わり?(狙い) |
316+
| | 要素名 | これは何? | コード上の実体 | 何の代わり?(狙い) |
326317
| ------ | -------------------- | -------------------------- | ----------------------------------------------------------- | ---------------------------------------------------- |
327318
| **1** | **PtyPair** | 入出力の**配線アダプタ**(人↔シェルの往復) | `class PtyPair { masterIn/masterOut/slaveIn/slaveOut }` | 本物の **Pseudo TTY(PTY)** の簡易再現(`PassThrough`でメモリ内パイプ) |
328319
| **2** | **Shell** | **REPL 本体**(1行読む→解釈→実行→表示) | `class MiniShell { run(); tokenize(); }` | **bash/zsh** の最小版(まずは行単位でディスパッチのみ) |
@@ -343,10 +334,15 @@ import { setTimeout as sleepMs } from "node:timers/promises";
343334

344335
// -----------------------------
345336
// 1) 擬似端末 (PTY) ペア
337+
// - master*: 人間(端末)側
338+
// - slave*: シェル側
339+
// 2方向のデータ流れを PassThrough で再現します
346340
// -----------------------------
347341
class PtyPair {
348-
masterIn = new PassThrough(); // Terminal → masterIn → slaveIn
349-
masterOut = new PassThrough(); // slaveOut → masterOut → Terminal
342+
// Terminal → masterIn → (pipe) → slaveIn → Shell
343+
masterIn = new PassThrough();
344+
// Shell → slaveOut → (pipe) → masterOut → Terminal
345+
masterOut = new PassThrough();
350346
slaveIn = new PassThrough();
351347
slaveOut = new PassThrough();
352348

@@ -359,8 +355,11 @@ class PtyPair {
359355
}
360356

361357
// -----------------------------
362-
// 2) ユーティリティ(トークン化)
358+
// ユーティリティ(トークン化)
363359
// - クォート対応の軽量パーサ(超簡易)
360+
// - 空白で区切るが、' ' や " " で囲まれた空白は1つの単語として扱う
361+
// - 例: tokenize(`echo "hello world"`) → ["echo","hello world"]
362+
// - 厳密なシェル構文ではなく最小仕様
364363
// -----------------------------
365364
function tokenize(line: string): string[] {
366365
const tokens: string[] = [];
@@ -397,8 +396,15 @@ function tokenize(line: string): string[] {
397396

398397
// -----------------------------
399398
// 3) Builtin Commands
399+
// - ここに {名前: 関数} を追加すると、新しいコマンドが増える
400+
// - Cmd は「引数と入出力インターフェースを受け、終了コードを返す」関数
400401
// -----------------------------
401-
type IO = { stdin: Readable; stdout: Writable; stderr: Writable; prompt: () => void };
402+
type IO = {
403+
stdin: Readable; // コマンドの標準入力(今は空。将来パイプ実装で活きる)
404+
stdout: Writable; // コマンドの標準出力
405+
stderr: Writable; // コマンドの標準エラー出力
406+
prompt: () => void; // プロンプト再表示用のコールバック(必要時に呼ぶ)
407+
};
402408
type Cmd = (args: string[], io: IO) => Promise<number> | number;
403409

404410
const commands: Record<string, Cmd> = {
@@ -416,7 +422,7 @@ const commands: Record<string, Cmd> = {
416422
"Tips: パイプやリダイレクトは未対応(最小実装)",
417423
].join("\n") + "\n"
418424
);
419-
return 0;
425+
return 0; // 終了コード0 = 成功
420426
},
421427

422428
echo: async (args, io) => {
@@ -455,12 +461,18 @@ const commands: Record<string, Cmd> = {
455461
};
456462

457463
// -----------------------------
458-
// 4) シェル(REPL)
464+
// 2) シェル(REPL)
465+
// - 1行読み取って → tokenize → commands から関数を引き当てて実行
466+
// - 未定義コマンドはエラーメッセージを出して次のプロンプトへ
459467
// -----------------------------
460468
class MiniShell {
461469
private rl: readline.Interface;
462470

463-
constructor(private slaveIn: Readable, private slaveOut: Writable, private slaveErr: Writable) {
471+
constructor(
472+
private slaveIn: Readable, // シェルが読む(人間から来る)入力
473+
private slaveOut: Writable, // シェルが書く(人間へ返す)出力
474+
private slaveErr: Writable // エラー表示用
475+
) {
464476
this.rl = readline.createInterface({
465477
input: this.slaveIn,
466478
output: this.slaveOut,
@@ -473,17 +485,22 @@ class MiniShell {
473485
this.slaveOut.write("> ");
474486
}
475487

488+
// メインループ:1行ずつ処理
476489
async run() {
477490
this.prompt();
478491
for await (const line of this.rl) {
479492
const trimmed = line.trim();
480493
if (!trimmed) {
494+
// 空行は何もしないで次のプロンプト
481495
this.prompt();
482496
continue;
483497
}
498+
484499
const [cmd, ...args] = tokenize(trimmed);
485500
const impl = commands[cmd];
501+
486502
if (!impl) {
503+
// 未知コマンド
487504
this.slaveErr.write(`command not found: ${cmd}\n`);
488505
this.prompt();
489506
continue;
@@ -495,22 +512,27 @@ class MiniShell {
495512
stdin.end();
496513

497514
try {
515+
// コマンドを実行
498516
await impl(args, {
499517
stdin,
500518
stdout: this.slaveOut,
501519
stderr: this.slaveErr,
502520
prompt: () => this.prompt(),
503521
});
504-
} catch (e: any) {
505-
this.slaveErr.write(`error: ${e?.message ?? String(e)}\n`);
522+
} catch (e: unknown) {
523+
const msg = e instanceof Error ? e.message : String(e);
524+
// コマンドの実行時エラーを握りつぶさず通知
525+
this.slaveErr.write(`error: ${msg}\n`);
506526
}
507527
this.prompt();
508528
}
509529
}
510530
}
511531

512532
// -----------------------------
513-
// 5) Terminal(人間側の出入口)
533+
// 4) Terminal(人間側の出入口)
534+
// - キーボード入力を masterIn に流し、Shell の出力を stdout に表示
535+
// - Ctrl+C は「いまの行を諦めて次のプロンプトへ」という簡易挙動
514536
// -----------------------------
515537
async function main() {
516538
const pty = new PtyPair();
@@ -581,19 +603,38 @@ npx tsx mini-terminal.ts
581603

582604
> これは、最小実装なので **パイプ(`|`)やリダイレクト(`>`** は未対応です。
583605
584-
文字数と締め切りにより、ここではどのような挙動をするかあえて解説しません
606+
文字数と締め切りにより、ここではどのような挙動をするかあえて解説しません()
585607
もし良ければ、ぜひ試してみてください。
586608

587-
---
588609

589610
### 4.4 まとめ
590611
この実装からも、ターミナルは「入力と出力を結ぶ通話路」であり、シェルはその入力を解釈してプログラムへ渡す受付係として機能します。そして、Ctrl+C のような割り込みも、本質的には「特別な文字列を入力ストリームに流す仕組み」として説明できます。
591612

592613

593614
### 5. 最後に
594-
今回この記事を書いたことで、黒い画面の奥にある奥深さと歴史をあらためて感じることができました。(もう怖くないぞ!)
595-
とりわけ強く実感したのは、「シンプルであること」の凄さです。コンピュータやOSには流行り廃りがありますが、その中でターミナルは何十年も利用され続けてきました。そして現代、AI時代を迎えた今でも、その存在はむしろ再評価され、最盛を迎えています。
615+
今回この記事を書いたことで、黒い画面の奥深さを感じることができました。(もう怖くないぞ!)
616+
とりわけ強く実感したのは、「シンプルであること」の凄さです。コンピュータやOSには流行り廃りがありますが、その中でターミナルは何十年も利用され続けてきました。そしてAI時代を迎えた今でも、その存在はむしろ再評価され、最盛を迎えています。
596617

597-
これは、ターミナルが複雑な機能を詰め込んだからではありません。むしろ逆で、「文字をやり取りする」という最小限の仕組みに徹しているからこそ、どんな時代の技術とも接続でき、使い続けられるのです。
598-
シンプルであることは一見地味ですが、それこそが長く生き残る力を持ち、普遍性を与えるのだと感じました。
618+
これは、ターミナルが複雑な機能を詰め込んだからではありません。むしろ逆で、「文字をやり取りする」「全てをファイルとみなす」という最小限の仕組みに徹しているからこそ、どんな時代の技術とも接続でき、使い続けられるのです。
619+
シンプルであることは一見地味ですが、それこそが長く生き残る力を持ち、普遍性を与えるのだと感じました。今後エンジニアとして、このような普遍性があり長く愛されるサービスを作る上でも大事にしたいなと思いました。
599620
「黒い画面が怖い」あなたに、この記事を通して共感してもらえたら幸いです。
621+
622+
### 参考文献
623+
Gregory Anders, “State of the Terminal,” g.p. anders blog, March 12, 2024.
624+
URL: https://gpanders.com/blog/state-of-the-terminal/
625+
(参照日:2025年10月)
626+
627+
Linus Åkesson, “The TTY demystified,” linusakesson.net – Programming,History セクションほか.
628+
URL: https://www.linusakesson.net/programming/tty/
629+
(参照日:2025年10月)
630+
631+
LPI Learning Materials, “103.4_01: Input, Output, Storage,” LPI Learning Portal.
632+
URL: https://learning.lpi.org/en/learning-materials/101-500/103/103.4/103.4_01/
633+
(参照日:2025年10月)
634+
635+
武内 覚, 試して理解 Linux のしくみ — 実験と図解で学ぶ OS、仮想マシン、コンテナの基礎知識【増補改訂版】, 技術評論社, 2022年10月刊行
636+
https://amzn.asia/d/ceU9Fjn
637+
638+
R. Koucha, “PTY / Pseudo-Terminal (PTY, PDIP) — Redirection of Standard Input and Outputs of a Process,” Tech Corner.
639+
URL: https://www.rkoucha.fr/tech_corner/pty_pdip.html
640+
(参照日:2025年10月)

0 commit comments

Comments
 (0)