bash再実装の課題振り返り

42 Tokyo の課題でbashの再実装に取り組みました。本記事ではその振り返りをします。

課題の概要

機能が制限されたbashを再実装する課題です。

具体的には、

  • Simple command
  • いくつかのbuiltin command
    • cd, echo など。env など本来builtinでないコマンドも含みます
  • 構文解釈( ; | "" ''
  • リダイレクト(< > >>
  • 環境変数の展開

を実装します。
課題は2人の生徒で取り組む必要があります。

この課題をこなすことで、以下のことが学べたと思います。

  • bashの仕様
  • 構文解析(LexerとParser)
  • プロセスの作成、任意のプログラム実行
  • パイプ、リダイレクション
  • シグナルハンドリング

成果物

f:id:nafuka11:20210329023115g:plain

github.com

取り組み方

42 Tokyoの生徒、 ToYeah さんと課題に取り組みました。

調査

まずは bash で何を実装すべきか調べました。

The Architecture of Open Source Applications というオープンソースアプリケーションの設計について書かれた本の 日本語訳 を読みました。
この本にはbashアーキテクチャについて載っていました。

本を読んだところ、bashのメイン部分の実装は以下の4工程に分けられそうでした。

  1. Lexer(単語分割)
  2. Parser(構文解析
  3. Expansion(変数展開)
  4. Command execution(コマンド実行)

開発方法

開発は GitHub 上で行いました。

コードに変更を加える場合は基本的に Issue を立てた後、Issue に紐づくPullRequest を出し、相手にレビューしていただきました。

プロジェクト管理は Jootoガントチャートを作り、進捗を確認していました。
自分の進捗はこんな感じでした。課題着手から完了まで、おおよそ37日かかったようです。

f:id:nafuka11:20210328164757p:plain

ドキュメントは HackMD を使いました。議事録を残したり、参考URLを共有していました。

実装

1. Lexer(単語分割)

Lexerでは入力文字列を構文解析しやすいよう、単語に分割します。

例えば、

echo "hello w"'orld'; cat<file|wc

という入力文字列が渡された場合、

echo
"hello w"'orld'
;
cat
<
file
|
wc

に分割します。

状態の更新

入力文字列を先頭から1文字ずつ見ていって、状態を更新していきます。

状態は以下の3つのいずれかです。

  1. 通常
  2. シングルクォートの中
  3. ダブルクォートの中

今見ている文字が ' なら状態をシングルクォートの中に、 " なら状態をダブルクォートの中に更新します。
対応するクォートが現れたら、状態を通常に戻します。

単語分割

状態が通常の時にスペース、 ; | > など、単語を分割すべき文字を見つけたら、そこで単語を分割します。

Lexerの段階では、まだ " ' は残ったままです。" ' は 3. Expansion で除去します。

2. Parser(構文解析

Parserでは、分割された単語を先頭から1つずつ見て構文解析し、抽象構文木を作ります。

抽象構文木

抽象構文木とは、構文解析した結果をプログラムで処理しやすいよう木構造にしたデータです。

構文解析はリストでも実現できるようですが、木の方が文法の追加に柔軟に対応できそうなのでこちらを採用しました。

私たちの実装では、この入力文字列は、

cat < file1 | grep a | wc -l > file2 > file3 ; echo a | grep a; echo b

以下の図のようなデータ構造になります。

f:id:nafuka11:20210328170655p:plain

木の左下から右上にたどると、入力文字列と対応するようになっています。
コマンドノードには引数のリスト、リダイレクトのリストを持つようにしています。

構文解析方法

シェルの文法に関しては POSIXの規格 に載っている「Shell Grammar Rules」を参考にしました。バッカス・ナウア記法を理解するために、Railroad Diagram Generator で可視化して文法を確認しました。

規格を見たところ、今回の課題では ;|command の順に解釈していけば良さそうでした。

  • ; の解釈では、| の解釈の後、; の解釈と | の解釈のループに入ります。; がなくなったらループを抜けます。
  • | の解釈では、 command の解釈の後、 | の解釈と command の解釈のループに入ります。 | がなくなったらループを抜けます。
  • command の解釈では、リダイレクト(< > >> )が来たら次の単語をファイル名とし、それ以外はコマンド文字列とします。|; が来たり、単語がなくなったらループを抜けます。

……と文章で書いても難しいかもしれません。先ほどの抽象構文木の図に番号を振ってみました。

f:id:nafuka11:20210328171032p:plain

番号順に木が作られます。左下の末端からコマンドの子ノードを作り、親ノードに繋げていきます。

構文解析の方法は以下のURLを参考に作成しました。

エラー処理

; の前にコマンドがない場合、|> の後に単語がない場合は構文解析エラーになります。

echo > のような入力は構文解析エラーになります。一方で > file; は許容されます。

3. Expansion(変数展開)

Expansionは、コマンド実行前に呼び出され、コマンドの引数とリダイレクトの単語を処理します。
単語に含まれる $USER のような変数を展開し、" ' の除去も行います。

変数展開によって単語が分割されるため、ここで再度Lexerを実行します。
例えば、VAR="echo hello" という変数があり、$VAR という1つの単語が与えられたら、 2つの単語 echo , hello に分割されます。

4. Command execution(コマンド実行)

抽象構文木の左下まで降りて、右上に向かってコマンドを実行していきます。

パイプ

パイプするためには、木を登って降りてコマンドノードを見ていく必要があります。今回は木を登らず、構文解析時にコマンドノードをポインタで繋ぐことにしました。

f:id:nafuka11:20210328171309p:plain

パイプの実行方法

パイプが1つの場合

a | b のような場合は、1つパイプを作って、それぞれのプロセスの標準入出力を繋ぎます。

パイプが複数の場合

a | b | c のようにパイプが複数になった場合、少し複雑になります。コマンド b を実行する時には 入力側のパイプ、出力側のパイプ、2つのパイプが必要です。

これはパイプの最初、中間、最後と3つの状態で場合分けして考えられます。
パイプが増えると中間状態のコマンドが増えます。

  1. パイプの最初
    • パイプを作成し、パイプの片側を閉じ、片側を標準出力に繋ぎます。
  2. パイプの中間
    • 1で作成したパイプの片側を閉じ、片側を標準入力に繋ぎます。
    • パイプを作成し、パイプの片側を閉じ、片側を標準出力を繋ぎます。
  3. パイプの最後
    • 2で作成したパイプの片側を閉じ、片側を標準入力に繋ぎます。

私たちは pipe_state というenumで状態を持たせました。bitで read, writeのフラグを管理しても良かったかもしれません。

パイプの処理を図にするとこんな感じになります。

f:id:nafuka11:20210328171443p:plain

注意点として、使い終わったパイプは親、子プロセスともに閉じないといけません。そうしないと、パイプがずっと入力を待ち続けてしまいます。

リダイレクト

リダイレクトはパイプの後に行います。パイプする前にファイルを開いておき、パイプ後にファイルディスクリプタを上書きします。

builtinコマンドでの注意点

パイプで繋がれていないbuiltinコマンドの場合、子プロセスでなく親プロセスで実行されます。子プロセスにしてしまうとexportなど環境変数の設定が親プロセスに反映されないためです。

親プロセスでリダイレクトすると、親の標準入力、標準出力などが書き換わり、その後のコマンド実行に影響が出ます。
このため、上書きする前にファイルディスクリプタdup してバックアップし、リダイレクトが完了したら dup2 して復元する必要があります。

工夫したこと

データ構造の可視化

デバッグ時に、抽象構文木をdotファイルに出力、単語リストを標準出力するようにしました。

画像の左側が単語リスト、右側がdotファイルです。

f:id:nafuka11:20210328171544p:plain

dotファイルは Graphviz などのツールを使うと画像を生成できます。本記事の抽象構文木の図はdotファイルを Graphviz で生成したものです。

可視化により、不具合がどこで起こっているのか突き止めやすくなったと思います。また、図示すると説明もしやすかったです。

テスターの作成

早い段階から テスター を作り、リグレッションが起きていないか、メモリリークが起きていないか確認するようにしました。

このおかげで比較的少ない不具合数(cファイル, ヘッダファイルの行数およそ4300行に対し、不具合数23)で済んだように思います。

感想

楽しかったこと

チーム開発の楽しさ

一人と違って、相手から反応があり楽しかったです。相手からどんなレビューが来るか、コードが来るか、楽しみにしていました。

チームメイトの ToYeah さんは実装力があり、レビューでは特にメモリリークについて指摘いただき、頼もしかったです。

構文解析の面白さ

資料を参考に実装したら、こんなに綺麗に作れるのかと感動しました。

大変だったこと

bashの仕様の把握

特にbuiltinコマンドはソースコードを読まないと分からない挙動が多かったです。bashは長い歴史があり、ソースコードも歴史を感じさせるものとなっています(そんなに読みやすくないという意味です)。

以下はbashの面白い挙動の一例です。

  • cdは、カレントディレクトリが存在しない場合、cdするディレクトリをPWDにjoinしてcdを試みます。

    • この挙動については ToYeah さんが詳しく解説されています。 qiita.com
  • exitは、複数の引数が与えられた場合、exitしません。そして ; 以降のコマンドは無視されます。

    • 例えば、 exit 0 0; echo hello というコマンドの場合、 echo hello は実行されません。
  • シェルがどのくらいの深さで実行されているか確認できる変数、 SHLVL は1000以上になると bash: warning: shell level (1000) too high, resetting to 1 と表示され、1にリセットされます。
    • ちなみにbash4.4以前では、SHLVL=999 の時にbashを起動すると SHLVL が空文字になるバグがあります。

終わりに

課題を見て、始めはどう実装すればいいか全く分からなかったのですが、資料を読んで協力してなんとか実装できました。

bashの仕様や構文解析、プログラムの実行など学ぶことが多く、面白かったです。

時間があれば、コマンドのHistory機能など実装してみたいと思います。

2021年1月の振り返り

42Tokyoのコミット時間

f:id:nafuka11:20210204212655p:plain

1月のコミット時間は161時間37分でした。

内訳としては、半分が課題、残り半分がレビュー、イベント、AWS Academyなどになっています。

42Tokyoでやったこと

Kubernetesの課題

2020年12月からKubernetesで複数のアプリケーションをデプロイする課題に取り組んでいます。

課題はほぼ完了して、これからレビューを受ける予定です。

今回はレビューで説明するためのスライドを作成しました。

f:id:nafuka11:20210204212914p:plain

課題の規模が大きい場合、スライドを作るようにしています。理由としては、

  • 説明に再現性を持たせるため。
    • 全て口頭だとその時々で説明が違ってしまう、説明を忘れてしまう、などの問題があるため。
  • 聴覚だけでなく、視覚、言語で理解させたいため。
    • 人によって理解しやすい情報が異なるため。

があります。

AWS Academy

AWS Academy とはAWSが提供している学生向けカリキュラムです。

42TokyoはAWS Academyに加盟しており、生徒は無料でカリキュラムを受講できます。

現在私はAWS Academy Cloud Foundationsを受講しています。これはAWSの入門にあたるコースです。

ラボという、制限付きのAWSの環境にアクセスできるのが楽しいです。マネジメントコンソール、AWS CLIなどが使えます。

Yahooのペアプロ・TDDワークショップに参加

Yahooさんが主催する42Tokyoの生徒向けのペアプロ・TDDワークショップに参加しました。

nafuka.hatenablog.com

コードゴルフのイベントに参加

内容はBit逆転、FizzBuzzでした。

感想

良かったこと

  • Togglで時間をつけ始めました。
    • 何にどれくらい時間を使っているのか可視化されて良いです。
  • スライドを以前よりも短い時間で作れました。
    • 以下の記事を参考に、スケルトンという資料の設計図を作ってみました。手戻りが少なくなり良かったです。 diamond.jp

課題

  • ブログを書くのに時間がかかりすぎてしまう。
    • 書き慣れていない、自分の中で型を持っていない、といくつか原因があると思うので解消していきたいです。

今後やること

  • 引き続きKubernetesの課題を進め、課題が終わったらシェルの再実装の課題に移りたいです。
  • Discord Botは軽微な修正しかしていないので、そろそろ何か新しいことをしたいです。

Yahooのペアプロ・TDDワークショップに参加しました

2021年1月28日にYahooさんが42Tokyoの生徒向けに主催してくださった、ペアプロ・TDDワークショップに参加しました。

本記事はその参加レポートです。

そもそもペアプロ、TDDとは

ペアプロ、TDD、どちらもXPのプラクティスの一つです。

XPとは、

Wikipedia より

エクストリーム・プログラミング、XPは、 ソフトウェア品質 を向上させ、変化する顧客の要求への対応力を高めることを目的としたソフトウェア開発プロセスである。

ペアプロは、ペアプログラミングの略で、2人のプログラマがドライバーとナビゲーターの役割に分かれ、1つの画面でプログラミングを行う開発方法です。

TDDは、テスト駆動開発(Test-Driven Development)の略で、テスト→実装→リファクタリングのサイクルを繰り返していく開発方法です。

内容

Zoom上でワークショップが行われました。このワークショップはもともと8時間の内容を2時間にぎゅっと凝縮したものとなっていました。

ワークショップでは以下の3つが行われました。

  1. XPの説明
  2. Yahoo社員のライブコーディング
  3. 生徒同士のペアプログラミング

XPの説明

XPの説明では、XPがなかった頃の課題、XPについて、5つの価値と主要プラクティス13個について知ることができました。

ライブコーディング

ライブコーディングでは、ペアプログラミングで行うお題のデモンストレーションを見ました。この時、開発サイクルの説明を交えつつライブコーディングをされていました。

開発サイクルは、

  • PMから与えられたストーリーをじゃんけん見積もり
  • 実装
  • 受け入れテスト

という流れだそうです。

ペアプログラミング

その後、生徒同士でペアプログラミングを行いました。

内容は、Node.jsとVue.jsでじゃんけんゲーム、電子書籍アプリの実装を行うことになりました。しかし、私たちのペアは時間が足りず、電子書籍アプリの実装まではたどり着くことができませんでした。

オンラインでペアプログラミングをするにあたり、Zoomの画面共有 + 遠隔操作機能を使いました。Zoomの遠隔操作機能はVSCodeと相性が悪いらしく、キー入力を受け付けなくなるトラブルがありました。それでも、2人でプログラミングをする楽しさは体験できたと感じます。

感想

ライブコーディング時に、開発サイクルの説明がなされ、実際の業務のイメージがつきました。

ペアプログラミングは、42のP2Pラーニングと親和性が高く、チームプロジェクトで使ってみたいと思いました。

TDDは、大規模なプロジェクト、長期間開発するプロジェクトで特に役に立ちそうだと思いました。 これまでのプロジェクトで、新機能を追加したものの既存機能にバグを埋め込んでしまい再度修正……ということがありました。最初にテストを書いておくと安心して機能追加できそうです。

ワークショップで学んだことを生かして生徒たちと開発していきたいと思います。

2020年振り返り

今年は一言で表すと42Tokyoとコロナな1年でした。

要約

1月に42Tokyoの入学試験Piscineを受験、新型コロナウィルスの影響で開校が延期。4月からオンラインの特別カリキュラム開始、6月から42Tokyoが開校し本来のカリキュラムを進めています。

出来事

42Tokyo

去年42Tokyoの存在を知り、1月に入学試験Piscineを受験、合格しました。

その後、新型コロナウィルスの影響で42Tokyoの開校が延期。代わりに4月から1ヶ月間特別カリキュラムがありました。特別カリキュラムについては以下の記事を参照ください。

nafuka.hatenablog.com

6月から開校し、本来のカリキュラムが始まりました。カリキュラムは基本オンラインで進めています。

カリキュラム

42Tokyoのカリキュラムでは以下の課題に取り組みました。

  • 標準Cライブラリの再実装
  • 指定ファイルディスクリプタから1行ずつ文字列を読み込む関数の実装
  • ネットワークに関するクイズ問題
  • printfの再実装
  • Wolfenstein 3Dのような、Ray castingを使ったゲームの製作
  • DockerでWebサーバ、DBなど全部入りのコンテナを立てる*1
  • 過去作成したC言語の関数をアセンブリで再実装

現在は、Kubernetesで各種サービスを立ち上げる課題、bashの再実装に着手しています。

イベント

42Tokyoでは時々イベントが行われます。運営側が主催することもあれば、生徒が主催することもあります。以下のようなことをしています。

コードゴルフのお題はBrainf*ckの実装や以前の課題などです。終わった後、皆で実装を見せ合うのが勉強になります。

ラジオ体操は、毎日2-3回、決まった時間に皆とやっています。体操をするだけでなく、定期的に雑談や進捗を報告する場になっていて、モチベーション維持に役立っています。

オンラインで変わったこと

  1. 文章を書く機会が増え、積極的にコミュニティへアウトプットをするようになりました。

    オンラインだと暗黙知が形成されにくいこともあり、なるべく課題で学んだこと、躓いたことは文章に残すよう心がけています。

  2. レビューで説明するため様々なツールを使うようになりました。

    Googleスライド、HackMD、Notion、A Web Whiteboardなど。オフラインの時は紙に書いて説明していました。

42Tokyoに入ってみて

  • 多様な生徒に出会うことができました。
    • 42Tokyoは学歴や職業などに関わらず、人を受け入れています。それまで交流関係が狭かったのですが、42Tokyoに入学してから様々な生徒と出会い、刺激を得ています。
  • 生徒の役に立つツールを作るのが楽しいです。
    • 多数の生徒が在籍しているので、ツールを作るとフィードバックが得られ嬉しいです。
  • データを分析する楽しさに気づきました。
    • 42ではAPIが提供されており、生徒たちが成績などのデータにアクセスできます。他国のキャンパスのデータも取得できます。
    • データから予測を立て提示すると人を動かしやすく、楽しいです。

その他

42のカリキュラム外で作ったもの

  1. AtCoderのスコアをSlackに投稿するツール

    nafuka.hatenablog.com

  2. Discord Bot

    42TokyoのDiscordサーバでBotを稼動させています。

    nafuka.hatenablog.com

    42Tokyo用のBot以外に、ロール・チャンネル管理Botも作成して、時々使っています。

    f:id:nafuka11:20201231185640p:plain:h400

  3. 生徒の進捗で表示するGoogleスプレッドシート

    日別で各課題に合格(validated)、提出(submitted)、登録(subscribed)した人数を表示してみたり

    f:id:nafuka11:20200727171817p:plain

    入学時期別で成績分布をグラフにしてみたりしています(画像はダミーデータです)。

    f:id:nafuka11:20201231194442p:plain

    データの取得にはGASやPythonを使っています。

スマートホームはじめました

Google Home Miniを家族に貰ってから、Nature Remo mini、Switch Botカーテンを買って部屋で使っています。

Nature Remo miniと連携した音声でのリモコン操作は、リモコンを探す必要がなく快適です。Switch Botカーテンは、朝カーテンを自動で開けてくれるので目覚めがよくなりました。

読んだ本

他、読みきってはないですが、『人を動かす 文庫版』が集団の中で上手くやっていく方法が書いてあり勉強になりました。立ち回りが上手い方はこの本に書かれていることを実践されてるように思います。

来年の抱負

  • 42Tokyoの除籍システム、通称ブラックホールに吸い込まれることなく、来年中に基礎的な学習を終え、インターンに参加できるレベルに到達したいです。
  • 42Tokyoに入って、便利なツールを作ったり、データを集め眺めて分析のようなことをするのが好きと気づきました。今の環境でできることをやっていきたいです。

来年もよろしくお願いします。

*1:Dockerの課題は、本来は各プロセスを別コンテナにするべきでしょうが、分離された環境で環境構築の練習をするのが目的なのかなと思います。