17歳からのプログラミング入門。

情報系の門の先で起きたことをたまにメモ。

echo 学生セキュリティ勉強会 | sed 's/.*/sed計算機/'

前置き

9/23(土)、秋葉原カスペルスキーさんで『第一回 学生セキュリティ勉強会』というイベントに参加してきました。

参加者は40人程度、部屋は満員で、主催者も驚いていたようでした、し、いるやんもびっくりでした。

本会の内容は、セキュリティに関する話ということで言わないように釘を刺されているので触れられません、ごめんなさい。 言える範囲としては「SQLインジェクションXSS等の基本的なWeb脆弱性の体験と解説」(イベントページより)ということで、全く攻撃を行ったことがない初心者にとっては(脆弱性に対する考え方も含めての)学びがある良いイベントだったのではないかと思ったのでした。

懇親会(本会)

17:30くらいから懇親会が始まりました。時間は1時間くらいということで、カスペルスキーさんのご厚意のピザを頬張りながら交流を楽しみました。

ただまあ、人が集まるとLTしたくなるのが芸人の性というもので、何故かセキュリティ勉強会でシェル芸とsedの発表をしてしまいました。 発表では不明瞭な点もありましたので、以下にまとめなおして振り返りとさせていただきます。

シェル芸とは?

  • 「マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。」
    • 重要な(繰り返し使いそうな)操作ならソースコードに残しましょう、そうするべきだなーとシェル芸しながらつくづく思います・・・。
    • 人々は楽しみでこれをやっているのです。
  • シェル芸勉強会というのがあります。

シェル芸の例題紹介

  • contents.texについて、「\begin{figure}と\end{figure}」で囲まれた部分を全て抽出してください。(シェル芸勉強会 vol.28 Q1より)
    • sedというコマンドの検索機能を使うと、汎用プログラミング言語で解くより遥かに簡単に解けます。
    • 解答は公開されているので、いるやんの解答を書いておきます。
    • sed -n '/\\begin{figure}/,/\\end{figure}/p'
    • 「いらないところは表示しない、 “\begin{figure}” を見つけて、 “\end{figure}” が見つかるまでの範囲を表示して」と読みます。
  • 他にも、grepを使うとさくっと解けるvol.30 Q1を紹介しました。

(GNU) sedについて

  • sedはエディタです。(Stream EDitorの略、manにもそう書いてある)
  • リッチなエディタを立ち上げなくても、コマンド"ライン"上で検索・置換・追加(これはほとんど使われない)といった操作ができます。
  • sedでは数値の計算が行えません。
    • awkとかを使うのが一般的かなーって。

sedで足し算作った(本題)

LISPっぽい足し算を行ってみました。

echo '(+ 4 7)' | sed -E 's/\(\+ ([0-9]) ([0-9])\)/\1\2/;y/123456789/abcdefghi/;s/.$/\U&/;tadd;:add;s/(.)X/1\10/;s/(.?)0(.?)/\L\1\2/;tendadd;y/abcdefghiABCDEFGHI/0abcdefghBCDEFGHIX/;badd;:endadd;y/abcdefghi/123456789/'

これ1行をbashに貼り付けてもらうと11と表示されます。 実際、 (+ 1 1) -> 2 のような変換を100個くらい書けばできるんですが、それじゃ味気ないよね、と。

プログラムの解説

1行で書くのは読みにくいだけなので、主にセミコロンごとに改行して1行ずつ読み下していきましょう。 nlコマンドを使って行番号を振ってみました。

     1   echo '(+ 4 7)' |
     2  sed -E '
     3  s/\(\+ ([0-9]) ([0-9])\)/\1\2/;
     4  y/123456789/abcdefghi/;
     5  s/.$/\U&/;
     6  tadd;
     7  :add;
     8  s/(.)X/1\10/;
     9  s/(.?)0(.?)/\L\1\2/;
    10  tendadd;
    11  y/abcdefghiABCDEFGHI/0abcdefghBCDEFGHIX/;
    12  badd;
    13  :endadd;
    14  y/abcdefghi/123456789/
    15  '

[string]という形式でその時点での文字列を表示することとします。 また、以後で _ は何かのパターンを表します。 _ という文字そのもののつもりではないことを認識してもらうとともに、正しいパターンの書き方は↑のコマンド列に記されていることをご確認ください。

  1. (+ 4 7) という文字列を後続のコマンドに渡します。
  2. [(+ 4 7)] 後続のコマンドはsedです。拡張正規表現(sed内部でのバックスラッシュのルールが変わります)を使用します。
  3. [47] sコマンドは s/pattern/str/ の構文で patternstr に置換します。今回は (+ _ _) の形式の文字列のアンダーバーの部分を取り出して、それを繋げた文字列に置換します。_ はいずれも1文字の数字を表します。
  4. [dg] yコマンドは逐語変換と言って、左側の文字を対応する右の文字に変換します。 1a に、 2b に、・・・と言った具合です。 4d に、 7g に対応します。
  5. [dG] 末尾の1字を大文字に変換します。
  6. [dG] 実はsedのコマンドにはC言語のようなラベルジャンプ機能があります。tコマンドは直前のtコマンド以降(なければプログラム開始以降)にsコマンドが成功していれば、指定のラベルにジャンプします。 add ラベルは7行目にあるので、6行目は単純に以前のsコマンドの成功をリセットしているだけです。
  7. [dG] :コマンドはラベルの定義です。 add というラベルを定義します。
  8. [dG] _X の形式の文字列を 1_0 という風に置換します。今回は X がないので置換に失敗して、文字列はそのままです。
  9. [dG] _0_ の形式の文字列からアンダーバーの部分を取り出して、それを繋げた文字列に置換します。今回の _ は数字でなくてもいいし、存在しなくてもいいです。ちなみに今回は 0 がないので置換失敗です。
  10. [dG] 8-9行目で置換に成功していれば endadd というラベルにジャンプします。今回はどちらも失敗しているのでそのまま素通りします。
  11. [cH] 逐語変換です。前の文字を1文字若くして、後ろの文字を1文字老かせます。
  12. [cH] bコマンドは無条件ジャンプです。アセンブリで言うところのbranchに相当するのでしょうか。7行目の add ラベルまでジャンプします。

これ以降の7-12行目の動きは3度ほど上と同じ操作を繰り返し、 [cH] から、 [bI][aX] となって7行目から説明を再開します。

  1. [aX] add ラベルにジャンプしてきました。
  2. [1a0] _X の形式の文字列を 1_0 に置換します。 _ にはaがマッチして 1a0 という文字列になります。
  3. [1a] _0_ (ただし _ はなくても良い)の形式に a0 の部分がマッチして、 a のみに置換されます。 1 はそのまま残るので、 1a となります。
  4. [1a] 8行目で置換に成功しているのでtコマンドが発動して、13行目の endadd ラベルまでジャンプします。

13行目以降は計算終了処理が続きます。

  1. [1a] endadd ラベルを定義します。飛んできました。
  2. [11] 逐語変換で、アルファベットを数字に戻します。
  3. [11] sedコマンドはここまでで、この状態の文字列として4+7の答え 11 が出力されます。お疲れ様でした。

今後の展望

  • 複数桁の足し算もやってみたいです。愚直にやればできるんですが、桁上りのスマートな解決に悩んでいます。1桁ずつ切り出して計算を行う方法はできることを確認済みです。
  • 1桁同士であれば引き算は簡単なんじゃないかと思ってます。足し算よりコード短くなりそう。
  • sedの知識の更なる拡充
  • そもそもこんなふざけた計算機はもう作りたくないです💢

おわりに

危険シェル芸は手元では絶対実行しないようにしようね! 誰が書いたかわからないシェルコマンドが落ちていても実行しちゃダメだぞ!中に何が書かれているかは確認できる範囲で確認しよう!