42Tokyoの課題で、GITのコマンドなどでよく使う「|(パイプ)」の機能を再現してみました。
この課題、やることはそんなに多くないのに、めちゃくちゃ時間かかります。
まず、使う関数を正確に把握しないと、エラーハンドリングがわかりません。
もう一点、シンプルな書き方だと、システムがクラッシュする恐れがあります。(後述します)
そもそも「|(パイプ)」ってどんな記号か
パイプは「前後のコマンドを同時に実行」させます。
$ < fileA grep "ABC" | wc -m
例えば上記のようにコマンドを入力すると、下記の挙動になります。
- grep “文字列” = “文字列”を抽出する
- wc -m = 文字数をカウントする
つまり「fileAからABCを抽出して、文字数をカウントしてね」と命令できます。
なので、今回はコマンドを同時進行しつつ、エラーを想定して対応するのが目標です。
まずは標準入力と標準出力を把握
正直初見では下記の意味がわかりませんでした。
$ < fileA
これはリダイレクトと言って、入力先をfileAにしてあげることができます。
自分はこの時点で標準入力と標準出力が頭に入っていなかったので、それを調べるためにまた時間を浪費しました。でも先人方が残している記事もたくさんあるので、ここでは割愛します。
初心者向け!Linux操作技術~標準入出力とリダイレクト~
一言で言うと、大体のPCは標準入力はキーボードで、標準出力と標準エラー出力はモニターってことでしょうか。
この標準入出力を、コマンドラインで指定したファイルに繋げる必要があります。
Linuxのパイプをちょっとだけ理解する -Qiita
関数を把握
使った関数は以下の通り。
- pipe関数 : FDの入口と出口を作ってくれる
- fork関数 : プロセスを複製して、それぞれ別の動きをさせることができる
- wait関数 : プロセス複製後、子プロセスが終了するまで親プロセスを待機させることができる
- open関数 : fileをオープンさせてFDを生成する
- close関数 : fdの入口出口をそれぞれ閉じることができる
- access関数: ファイルにアクセスできるかチェックする
- dup2関数 : new fdをold fdに向けてくれる(道を無理やり曲げて方向を変えるイメージ)
作ったプログラムはGuthubに掲載しました。
ついでに、.shでテスターを作る方法を教えてもらい、作ってみました(こちら)。
どんな動きをするプログラムなのか
プログラムの挙動は下記のとおりです。
1.コマンドライン引数を読み込み
int main(int argc, char **argv, char **envp)
- argcで引数の個数
- argvで引数の文字列
- envpで環境変数
を、取得します。
argvとenvpは配列に格納されてます。
argv[0]とすると、文字列の一番目を呼び出すことができます。
envp(環境変数)というのは、PCのuser nameだったり、膨大なファイルパスなどが入っています。
この環境変数のファイルパスの中には、コマンドの挙動が書いてある場所も格納されています。
コマンドが入力されると、コマンドの中身が書いてあるファイルを読んで、マシンが実行してくれます。
コマンドの実行プログラムが格納されているのは1箇所ではありません。/bin/ だったり、/usr/bin だったりします。
実際にその場所にコマンドの情報があるか、チェックしなければなりません。
2.Fork関数でプログラムを二手に分ける
Fork関数でプログラムを二股に分けることで、やりたい作業を同時に行うことができます。
プログラムは親と子に分かれ、親でコマンド1、子でコマンド2の解析をさせます。
・・・と思うじゃないですか。これが冒頭で言っていた落とし穴です。
ここで素直にコマンドの処理を並列させると、どちらかのコマンドでオーバーフローした時にエラーも吐かれずに処理が止まり、処理待ちのままプログラムが永遠に終わらないという事態に陥ります。
そんなの初心者にわかるか。もうこの課題、意味がわからなくて数ヶ月溶かしました。向いてないと切実に思いました。
そんな課題を突破していった先輩にめちゃくちゃ聞きまくりました。インターン中で忙しそうなのにすみません、ありがとうございました!!!!
正解に近い動き
基本は子プロセスの中でforkを繰り返して、親プロセスはwaiteさせておくこと。
そしてparent で複製した先の parentで、あえてfdの入り口を「標準出力からpipeで作った入口」に向けてあげることみたいです。
意味わからないですよね。 私も久しぶりに記事を推敲してて、意味わかりません。 (2023/01/23)
先輩曰く、親プロセスでpipeの入り口を開けておいてあげる
↓
子プロセスの読み込みがいっぱいになってしまっても、pipeに流れ出て、別ファイルに書き込める
ということらしいです。
2ヶ月以上かけていろんなパターンで試行錯誤したものの、結局は先輩方が先に書いたコードに割と近くなってしまいました。まだ基礎が乏しいからですね。
でもロジックをきちんと理解できたのと、人のコードをこんなに気合い入れて読むことが少なかったので、コードを読む練習にりました。本当に勉強になりました。