Technically Impossible

Lets look at the weak link in your statement. Anything "Technically Impossible" basically means we haven't figured out how yet.

42 SILICON VALLEY Piscine 2017 Day01

42 Tokyoというエンジニア養成機関が話題だ。私は42 Tokyoを受験しないのだが、選抜方法とその内容に関心があり調べていた。シリコンバレー校の資料がGitHubで公開されている。私が参照している資料は2017年のものだ。

コーディングを通じて何かが試され、判定される試験がPiscineとするならば、そのDay 00~01は、その作業環境を学習、理解し、整えることが試されているように感じられる。

特に演習と直接関連する話題について、それらが既知の人にとって、それを実践するのは実に容易なことだろう。しかし、その実践したことを初心者、特に何の知見もない人に説明し、理解させようとすると、どうだろうか。
プログラムのような問題に対するアプローチ、ロジックというのは説明しようがあるものの、自然に習得、習熟したような知見ほど、相手に説明し、理解させるのは難しい。極端な例ではあるのだが、近い事例を挙げるとすれば、次のようなことだ。

  • 高齢者のような、PCを利用したことのないユーザーに、マウスのダブルリックを説明し、理解させ、「できる」ようにする。
  • 高齢者のような、スマートフォンを利用したことのないユーザに、タップと長押しの違いを説明し、理解させ、「できる」ようにする。

Day 00~01の内容を見ると、特に既知の受験者ほど、実践は容易だが、説明は「厄介」に感じられる話題が多く含まれているように感じられた。
一方、何の知見もない人は、これらを自分で「調べる」ところから始めなければならない。調べるには、調べるべき場所と調べ方、を知っておく必要がある。本題となるプログラミングを始める前から「試される」わけだ。
一からC言語の学習を始めるよりも、このような特定のテーマにまとめきれない、「初歩にして基礎にあらず」よりもさらに手前、に該当しそうな事柄というのは、自習しづらいものだ。
最終的に挫折するとしても、C言語という本題に到達する前、Day 00~01での離脱は無念なことだろう。この投稿が、予習の支援になればと思う。

ちなみに、ここで紹介する情報が東京校のPiscineに通用する保証はなく、内容の正しさを保証するものでもないことを申し添えておく。

雑感

Day 00に引き続き、Day 01のExerciseでもシェル操作が取り上げられている。Day 00では、シェル操作でも特に、調べるだけで片が付く話題が中心だったのに対して、Day 01では考える要素が多分に盛り込まれている。いわゆる「シェル芸」に通じるものがある*1

Day 02から始まるC言語の演習に臨むに際し、

  1. 調べること
  2. 考えること
  3. プログラムすること(調べること、考えることを含む)

という段階を経験させる、試験としての工夫が感じられる。

余談:Exercise 09について

Exercise 09は、その解釈によって大きく印象が異なる課題だ。CTF (Capture The Flag)のような暗号解読問題と解釈すれば納得感もあろうが、端的に「悪問」と解釈することもできる。その解釈の違いは、課題の要求に対して提供される情報量に由来するだろう。

情報が少なすぎる、それが理不尽と感じられるならば悪問と解釈されるだろうし、CTF的に解釈できれば、情報不足は感じるとしても、そこに理不尽さは感じないだろう。そして、この解釈の違いは、42とPiscineに対する印象だけでなく、IT業界に対する印象にも通じるものがある。つまり、要求に対する適切な情報が提供されないことが日常なのだ。

例えば、プロジェクトで求められる成果物、開発して欲しいシステムに求められる要件だ。目的が分からない、分かっていないことがあるし、そもそも説明すべき人物が、それらを理解していない、知らないことすら珍しいことではない。
だから、このような事態*2が生じるのも、もはや日常だ。

そのようなことを個人レベルで追体験する点についてだけでなく、次の点から、Exercise 09は試験問題として良問であるともいえる。それは戦略の問題だ。
入学試験をはじめとして、基準を越えさえすればよい試験は多い。合格のために、必ずしも満点を取る必要はない。後続の問題、自分にとってより有利な問題に取り組むため、難しすぎる問題などを意図的に捨てることもある。
まさにExercise 09のような問題が、そのような取捨選択の余地、受験者の戦略が試される問題だ。

考えてみると、世の中で言う試験において、あえて可能な限りの高得点を目指す必要のある試験というのは少ない。思いつくのは、学科の定期試験くらいのものだ。絶対評価で成績が決まるため、All Aを目指すならば、自ずと全クラスで80点以上を確保しなくてはならない。
評価基準が不明とはいえ、少なくともPiscineはそのような試験ではないだろう。

Exercise 01

指定されている環境変数「FT_USER」の内容は、42校舎の学習環境以外で再現することはできないため、単純にコマンド「groups」の出力を、指定通りに出力する前提で対応する。

次の条件を満たすことを求められている。これらはコマンド「tr」と、そのオプションを駆使することで対応できる。

Separated by commas without spaces tr [:blank:] ","
改行文字を削除 tr -d [:cntrl:]

コマンドは次のようになる。
gist.github.com

Exercise 02

カレントフォルダ、その配下の全フォルダに格納されている、全てのshファイルを一覧出力する。出力に際し、拡張子”.sh”を削除することを求められている。

全てのshファイルを出力する find . -name "*.sh"
拡張子".sh"を削除する sed 's/.sh//'

拡張子の削除は、拡張子に該当する文字列(.sh)を、目的の文字列(空白文字)で置換すると解釈すればよい。
コマンドは次のようになる。
gist.github.com

Exercise 03

前問の応用問題だ。カレントフォルダ配下の全ファイル、フォルダの個数を数える。カウント対象として"."も含めるよう指示されている。これは、それらすべてを一覧出力し、その行数を数えることと同じだ。

指定したカウント対象を出力する find . -type f -o -type d
出力された行数を数える wc -l

コマンドは次のようになる。
gist.github.com

Exercise 04

コマンド「ifconfig」を用いて、MACアドレスを出力することを求められている。アドレス毎に改行するよう指示されている。これを読み解くと、次のように解釈できる。

MACアドレスを出力する ifconfig -a
"ether"が含まれる行を取り出す grep 'ether'
14~31文字目がMACアドレス cut -n 14-31

コマンドは次のようになる。
gist.github.com

Exercise 05

これは特殊文字エスケープが問われている。対応方法として、次の2通りのアプローチが考えられる。

  1. 全ての特殊文字エスケープする
  2. 対象文字列を分割し、必要な箇所だけエスケープする

前者のアプローチを採用すると、目的の文字に対するエスケープ、さらにそのエスケープに対するエスケープ、と繰り返しが生じる。それは複雑さ、混乱を生じさせる原因となるだけでなく、第三者による理解の妨げにもなる。だから私は後者を採用する。

目的の文字列「"\?$*'KwaMe'*$?\"」を、次のように分割し、異なる環境変数に定義する。そして、環境変数を連結して使用することで、エスケープする必要がなくなる。

"\?$* str1='"\?$*' シングルクォートで文字列指定
'KwaMe' str2="'KwaMe'" ダブルクォートで文字列指定
*$?\" str3='*$?\"' シングルクォートで文字列指定
"\?$*'KwaMe'*$?\" $str1$str2$str3 文字列を連結

問題が要求しているのは、目的の文字列をファイル名とするファイルを作成し、その内容に「42」と記録することだ。それを実現する一連のコマンドは、次のようになる。

gist.github.com

Exercise 06

コマンド「ls」の出力を、先頭行から一行飛ばしに出力することを求められている。
コマンドは次のようになる。

gist.github.com

Exercise 07

passwdファイル*3は「:」区切り、7つのフィールドで構成されている。第1フィールドが課題で言うところの「login」(ユーザー名)、第5フィールドが課題で言うところの「comment」(コメント)だ。

passwdファイルの内容、並びに指定されている環境変数「FT_LINE」の内容は、42校舎の学習環境以外で再現することはできないため、可能な範囲で出力する前提で対応する。

この課題は、Day 00~01に収録された全ての課題の集大成だ。これまでに対応した成果を総動員する。
passwdファイルを読み込み、ユーザー名を出力するのだが、ただ目的を果たすだけでは不十分だ。最終的に、指示されている事柄に無用の対応が含まれていても、指示されている順番通りに対応しなければならない。

Rigorously follow the order indicated in the instructions.

まず「removing comments」は、第5フィールド以外を出力するものと解釈できる。

cut -d ":" -f 1,2,3,4,6,7

「every other line starting from the second line」は、2行目から一行おきに出力する。

sed -n 2~2p

「reversing each login」は、第1フィールドを取り出し、文字列を逆転させる。

cut -d":" -f 1 | rev

そして「sorted in reverse alphabetical order」、その出力をアルファベット降順に出力する。

sort -r

前述の通り、この投稿では「keeping only logins between FT_LINE1 and FT_LINE2 included」を無視する。

「they must separated by ", "」は、出力を「, 」(カンマ+半角スペース)区切りで出力するのだが、次の手順を経ている。

  1. 改行文字を「,」(カンマのみ)に変換する
  2. 「,」(カンマのみ)を、「, 」(カンマ+半角スペース)へ変換する
tr [:cntrl:] "," | sed 's/,/, /g'

最後に「the output must end with a "."」の対応だ。この対応時点では、行末尾に「, 」(カンマ+半角スペース)が残されている。この行末尾だけを「.」へ変換しなければならない。

このために次の手順を経ている。

行全体を逆転する。
行末尾の「, 」が先頭2文字へ移動する。
rev
行頭3文字目以降を出力する。
行頭3文字目以降を出力しているので、「, 」が消える。
cut -c 3-
行頭に「.」を挿入する。 sed 's/^/./'
再び行全体を逆転する。
行頭の「.」が行末尾へ移動する。
rev
改行文字を削除する。 tr -d [:cntrl:]

一連のコマンドは次のようになる。
gist.github.com

Exercise 08

Day 00, Exercise 03*4同様、LDAP環境の操作について問われている。42校舎の学習環境に依存する要素があるため、使用すべきコマンドしか特定できない。

コマンド「ldapsearch」を使用する。コマンド「grep」で目的の文字列を含むユーザーを特定し、コマンド「wc」で行数を数える。
必要ならば、コマンド「sed」、「tr」で半角スペースや改行文字を編集する。

Exercise 09

この課題も、Day 00~01に収録された全ての課題の集大成だ。これまでに対応した成果を総動員する。

この課題は、CTF (Capture The Flag)に出題されるような、ヒント無しの暗号解読問題と解釈できれば、端的に「悪問」に類する問題とも解釈できる。特に後者として解釈するのは、課題の要求に対して、次の問題文はあまりにも必要な情報を端折りすぎていることを根拠としている。

Write a command line that takes numbers from variables FT_NBR1, in ’\"?! base,
and FT_NBR2, in mrdoc base, and displays the sum of both in gtaio luSnemf base.

課題が要求していることを、私なりの解釈で問題文に仕立てるならば、次のようになるだろう。


「」に指定されている、3つの文字列がある。

  1. 「’\"?!」
  2. 「mrdoc」
  3. 「gtaio luSnemf」

これらを、各文字列の長さに基づいた進数として解釈する。

文字列1、2の定義を用いて、2つの文字列変数、FT_NBR1、2を定義する。その和を文字列3で復号すると、次のような結果を得る。

FT_NBR1=\'?"\"'\
FT_NBR2=rcrdmddd
FT_NBR1 + FT_NBR2 = Salut

FT_NBR1=\"\"!\"\"!\"\"!\"\"!\"\"!\"\"
FT_NBR2=dcrcmcmooododmrrrmorcmcrmomo
FT_NBR1 + FT_NBR2 = Segmentation fault

そのようなスクリプトを記述せよ。

まずFT_NBR1、2を定義する。「Exercise 05」で説明したように、エスケープを可能な限り避けるため、次のように定義した。

str1="\'?"
str2='"\"'
str3="'"
str4='\'
FT_NBR1=$str1$str2$str3$str4
FT_NBR2="rcrdmddd"

FT_NBR1、2は5文字の文字列なので、5進数が文字に置き換えられたものと解釈する。また「gtaio luSnemf」は13進数だ。それらは次のように定義できる。
特にFT_NBR1の対応について、ここでもエスケープを最低限にとどめるよう対応している。

FT_NBR1の数値変換 sed "s/'/0/g" | tr '\\"?!' 1234
FT_NBR2の数値変換 tr mrdoc 01234
「gtaio luSnemf」の数値変換 tr 0123456789ABC 'gtaio luSnemf'

FT_NBR1、2は、それぞれ5進数なので、その和も5進数である。「gtaio luSnemf」は13進数なので、5進数の計算を入力として、13進数の結果を出力する。その結果を「gtaio luSnemf」で復号する。

xargs echo 'obase=13; ibase=5;' | bc | tr 0123456789ABC 'gtaio luSnemf'

一連のコマンドは、次のようになるだろう。
gist.github.com

変数定義を含めて実行すると、「Salut」が出力される。

str1="\'?"
str2='"\"'
str3="'"
str4='\'
FT_NBR1=$str1$str2$str3$str4
FT_NBR2="rcrdmddd"

echo $FT_NBR1 + $FT_NBR2 | sed "s/'/0/g" | tr '\\"?!' 1234 | tr mrdoc 01234 | xargs echo 'obase=13; ibase=5;' | bc | tr 0123456789ABC 'gtaio luSnemf'