「第26回シェル芸勉強会 大阪サテライト」の記録

はじめに

12月25日(日)に開催された「第26回シェル芸勉強会 大阪サテライト」に参加しました。

朝9時45分から夜18時までひたすらsh行に打ち込む一日でした。

……シェル芸人の朝は早い。

メイン会場および他サテライト会場

各会場の様子

問題ならびに模範解答

ライブストリーム(YouTube)


午前の部: シェルに関する勉強会

黒い画面と戯れよう

講師: 鳥海秀一さん

  • 黒い画面とは
    • キーボード入力と画面の文字表示のみでコンピュータの操作を行うUIのこと
    • 黒い画面怖い
      • CTRL+SCTRL+Q: コンピュータが遅かった時代、スクロールを止めて出力を見るため
      • vi: CTRL+Z……fgを使わないと戻れない
  • 攻略の方針
    • 遊び心: 遊ぶようなつもりで
    • 分割統治: キーボードと黒い画面
  • キーボードとは
    • 指で意思をコンピュータに伝える
    • 指は人間の部位の中では数が多く、器用で便利
    • 使いこなすには努力が必要
      • キーバインドを覚える
      • アプリケーション毎に異なる
      • Unixではvi風とemacs風の2つが有名
  • bashのキーバインド
    • 端末ドライバ
      • キーボードとOS間のデータフローを処理
      • sttyで設定の読込/変更
      • stty -aで端末設定を確認
    • Readlineライブラリ
      • テキストベースのインターフェイス用にGNUで開発されたライブラリ
      • bashのコマンドライン編集インターフェイス
      • viモードとemacsモードの両方にデフォルトの編集機能を提供
      • モード確認にはset -o
  • emacsモードのキーバインド
    • 移動コマンド
      • CTRL+B: 後方に1文字移動
      • CTRL+F: 前方に1文字移動
      • ESC+B: 後方に1ワード移動
      • ESC+F: 前方に1ワード移動
      • CTRL+A: コマンドラインの先頭に移動
      • CTRL+E: コマンドラインの末尾に移動
    • 削除、取出コマンド
      • BACKSPACE: 後方に1文字削除
      • CTRL+D: 前方に1文字削除
      • CTRL+W: 後方に1ワード削除
      • ESC+D: 前方に1ワード削除
      • CTRL+K: コマンドラインの末尾まで削除
      • CTRL+U: コマンドラインの先頭まで削除
      • CTRL+Y: 最後に削除したものを取り出す
    • 履歴コマンド
      • CTRL+P: 前の行へ移動
      • CTRL+N: 次の行へ移動
      • CTRL+R: 後方検索を実行
      • CTRL+S: 前方検索を実行
      • ESC+<: 履歴ファイルの先頭行に移動
      • ESC+>: 履歴ファイルの最終行に移動
    • 補完コマンド
      • TAB: テキストの標準的な補完を試みる
      • ESC+?: 補完候補の一覧を表示
      • ESC+/: ファイル名としての補完を試みる
      • ESC+~: ユーザ名としての補完を試みる
      • ESC+@: ホスト名としての補完を試みる
      • ESC+|: コマンドとしての補完を試みる
    • その他のコマンド
      • CTRL+J: RETURNと同じ
      • CTRL+L: 画面を消去
      • CTRL+V: 次の文字を逐語的に挿入
      • CTRL+[: ESCと同じ
      • ESC+U: ポイント後のワードを大文字にする
      • ESC+L: ポイント後のワードを小文字にする
  • viモードのキーバインド
    • 編集コマンド(入力モード)
      • BACKSPACE: 前の文字を削除
      • CTRL+H: 前の文字を削除
      • CTRL+W: 前のワードを削除
      • CTRL+U: コマンドの先頭まで削除
      • ESC: 制御モードに移る
      • CTRL+[: 制御モードに移る
    • 移動コマンド(制御モード)
      • h: 左に1文字移動
      • l: 右に1文字移動
      • w: 右に1ワード移動
      • b: 左に1ワード移動
      • 0: コマンドラインの先頭へ移動
      • $: コマンドラインの末尾へ移動
    • 入力モードへの移行コマンド(制御モード)
      • i: 現在の文字の前にテキストを挿入
      • a: 現在の文字の後にテキストを挿入
      • I: コマンドラインの先頭にテキストを挿入
      • A: コマンドラインの末尾にテキストを挿入
      • R: 既存のテキストを上書き
    • 削除コマンド(制御モード)
      • x(dl): 現在の文字を削除
      • X(dh): 現在の前の文字を削除
      • db: 後方に1ワード削除
      • dw: 前方に1ワード削除
      • D(d$): コマンドラインの末尾まで削除
      • d0: コマンドラインの先頭まで削除
    • 取出コマンド(制御モード)
      • p: 最後に削除したものを現在の文字の後に挿入
      • P: 最後に削除したものを現在の文字の前に挿入
      • CTRL+Y: 最後に削除したものを現在の文字の前に挿入
    • 履歴コマンド(制御モード)
      • k(-): 後方に1行移動
      • j(+): 前方に1行移動
      • /string: 後方に__string__を検索
      • ?string: 前方に__string__を検索
      • n: 前回と同じ方向に検索を繰り返す
      • N: 前回と逆の方向に検索を繰り返す
    • 文字検索コマンド(制御モード)
      • fx: 次の__x__へ移動
      • Fx: 前の__x__へ移動
      • tx: 次の__x__の1つ前に移動
      • Tx: 前の__x__の1つ後に移動
      • ;: 最後の文字検索を繰り返す
      • ,: 最後の文字検索を逆方向に繰り返す
    • 補完コマンド(制御モード)
      • \: 補完を試みる
      • *: 補完を試みる
      • =: 補完候補の一覧を表示
    • その他のコマンド(制御モード)
      • ~: 現文字の大文字と小文字を入れ替える
      • _: 前のコマンドの最後のワードを付け足す
      • #: コマンドラインの前に__#__(コメント記号)を付け、それを履歴ファイルに書き込む
  • bashのキーバインドの覚え方
    • 何度も繰り返して実行してみる
    • bind -pや、bind -Pの結果を検索
  • bashのキーバインドを変えてみよう
    1. bind '"a": self-insert'
    2. bind '"a": '
    3. bind '"a": "b"'
    4. bind '"a": clear-screen'

休憩

昼食の時間を利用して、T.Motooka(@t_motooka)さんによるミニ勉強会が行われました。

  1. バイナリーチラリー」を使ったバイナリイントロクイズ
  2. エクシェル芸の予習: xlsxファイルのファイルフォーマットについて

午後の部: シェル芸勉強会

  • いずれの問題も、Debian 8「jessie」の32ビットPC版で解答しています。
  • 使用OSによっては、sortを実行する言語環境をLANG=Cにする必要があるかも知れません。

A1: MS Officeファイルの中を見る

$ ls *.{xlsx,docx,pptx} | xargs -I@ bash -c "unzip -q @ -d @-DIR; ls @-DIR/* >> FILES.TXT; (cd @-DIR; zip -qr ../@ .); rm -fR @-DIR"; cat FILES.TXT
20141019OSC_LT.pptx-DIR/[Content_Types].xml

20141019OSC_LT.pptx-DIR/_rels:

20141019OSC_LT.pptx-DIR/docProps:
(...略...)
slideMasters
slides
tableStyles.xml
theme
viewProps.xml

この問題の解き方は、

  1. lsでxlsx・docx・pptxファイル名を取得
  2. xargs -I 置換文字列で各ファイルごとに次のコマンド群をbashに実行させる
    1. unzip [圧縮ファイル] -d [展開先ディレクトリ]で、各ファイルを「ファイル名-DIR」に展開
    2. lsの出力をFILES.TXTに保存
    3. zip -r [圧縮ファイル] [圧縮するディレクトリ]で元のファイルに戻す
    4. rm -fRで後片付け
  3. catFILES.TXTを出力

という手順になっています。

なお、zip実行時には-qオプションを付けて、不要な出力を抑えています。

ちなみに、手順2.2.ls @-DIR/*tree @-DIRに置き換えると、より分かりやすいファイルリストが出来上がります。

$ ls *.{xlsx,docx,pptx} | xargs -I@ bash -c "unzip -q @ -d @-DIR; tree @-DIR >> FILES.TXT; (cd @-DIR; zip -qr ../@ .); rm -fR @-DIR"; cat FILES.TXT
20141019OSC_LT.pptx-DIR
├── [Content_Types].xml
├── _rels
├── docProps
│   ├── app.xml
(...略...)
    ├── theme
    │   └── theme1.xml
    └── viewProps.xml

12 directories, 37 files

A2: スライド内の単語「危険」の出現回数を求める

訂正: 当日の解答(1/2)当日の解答(2/2)に誤りがありましたので、次の通り訂正します。

$ unzip -q 20141019OSC_LT.pptx -d tmp; grep -lr '危険' tmp/ppt/slides | xargs -I@ sed -i 's/\(危険\)/\n\1\n/g' @; grep -cr '危険' tmp/ppt/slides | awk -F: '$2!=0{sum+=$2;print}END{print "合計: "sum}'; rm -fR tmp
tmp/ppt/slides/slide5.xml:1
tmp/ppt/slides/slide1.xml:1
tmp/ppt/slides/slide8.xml:1
tmp/ppt/slides/slide28.xml:2
tmp/ppt/slides/slide12.xml:1
tmp/ppt/slides/slide6.xml:1
tmp/ppt/slides/slide15.xml:1
tmp/ppt/slides/slide4.xml:1
tmp/ppt/slides/slide19.xml:1
tmp/ppt/slides/slide20.xml:1
tmp/ppt/slides/slide30.xml:1
tmp/ppt/slides/slide33.xml:5
合計: 17

スライドのデータファイルは[展開先]/ppt/slidesディレクトリに格納されています。

この問題の解き方は、

  1. unzip [圧縮ファイル] -d [展開先ディレクトリ]tmpディレクトリに展開
  2. grep -lr パターン [ディレクトリ]tmp/ppt/slidesディレクトリにある、行に「危険」という単語を含むファイル名を得る
  3. 2.で得たファイル名リストについて、grep -cはマッチの個数を行単位でチェックするため、sedで「危険」の前後に改行を挿入する
  4. grep -cr パターン [ディレクトリ]tmp/ppt/slidesディレクトリにある各ファイルについて、パターン「危険」にマッチする行数(=個数)を得る
  5. awkで第2フィールドの合計(=出現回数)を求め、出力
  6. rm -fRで後片付け

という手順になっています。

しかし、単純に出現回数を得るのであれば、grep

-o, –only-matching
    マッチする行のマッチした部分だけを (それが空文字列でなければ) 表示します。
    マッチした各文字列は、それぞれ別の行に書き出します。

というオプションを利用した

$ unzip -q 20141019OSC_LT.pptx -d tmp; grep -or '危険' tmp/ppt/slides | wc -l; rm -fR tmp
17

で十分でしょう。

A3: スライドから画像を抽出し、ZIPファイルに圧縮

$ unzip -q 20141019OSC_LT.pptx -d tmp; (cd tmp/ppt; zip -q ../../media.zip media/*.*); rm -fR tmp

スライド内の画像ファイルは[展開先]/ppt/mediaディレクトリに格納されていますので、このディレクトリごとzipで圧縮すればOKです。

A4: スライドのスクレイピング

$ unzip -p 20141019OSC_LT.pptx ppt/slides/slide7.xml | grep -Po '<a:p>.+?</a:p>' | sed 's/<[^<]\+>//g' | sed '/^$/d'
戦果(?)
初日だけで見知らぬ方のマシン3台轟沈
その他自爆者多数
Docker上で試したらホストマシン沈黙の報告
自分の本がサイト経由で1冊だけ売れた
フォロワーが1人減った
2014/10/19
OSC Tokyo/Fall 2014
7

この問題の解き方は、

  1. unzip -p [圧縮ファイル] [特定のファイル]で、スライド7ページ目のXMLデータを出力
  2. 正規表現で最短量指定子の「+?」が使えるように-P(--perl-regexp)オプションを付けたgrep -oで、パラグラフ部分(a:p要素)を抽出
  3. sedでXMLタグと空行を削除

という手順になっています。

A5: ワークシートをSSV(スペース区切り)形式で出力――その1

$ unoconv -d spreadsheet -f csv --stdout graph.xlsx | tr ',' ' '
0 -5
0.5 -7.375
1 -14
1.5 -24.125
2 -37
(...略...)
9 -86
9.5 -50.125
10 -5
10.5 50.125
11 116

unoconvを使えば簡単に解けますが、それだけでは面白みに欠けますのでA4の手法を応用した

$ unzip -p graph.xlsx xl/worksheets/sheet1.xml | grep -Po '<v>.+?</v>' | sed 's/<[^<]\+>//g' | xargs -n2
0 -5
0.5 -7.375
1 -14
1.5 -24.125
2 -37
(...略...)
9 -86
9.5 -50.125
10 -5
10.5 50.125
11 116
  1. unzip -pでワークシートのXMLデータを出力
  2. grep -Poで各セルの値(v要素)を抽出
  3. sedでXMLタグを除去
  4. xargs -n2で1行につき2ヶの値を出力

のような普通(?)のシェル芸も別解として挙げておきます。

A6: ワークシートをSSV(スペース区切り)形式で出力――その2

$ unoconv -d spreadsheet -f csv --stdout hanshin.xlsx | tr ',' ' '
 06月01日 07月10日
1 真弓 真弓
2 弘田 北村
3 バース バース
4 掛布 掛布
5 岡田 佐野
6 佐野 木戸
7 平田 平田
8 木戸 永尾
9 ゲイル 池田

A5と同様、unoconvを使えば簡単ですが、これを普通(?)のシェル芸で解くと一気に難しくなります。

最終的なシェル芸は、

$ awk 'NR<=15{T[$1]=$2}NR==16{print $0}NR>=17{print $1,T[$2],T[$3]}' <(unzip -p hanshin.xlsx xl/sharedStrings.xml | grep -Po '<si>.+?</si>' | sed 's/<si><t>//;s/<\/t>.*//' | awk '{print NR-1,$1}') <(unzip -p hanshin.xlsx xl/worksheets/sheet1.xml | grep -Po '<row.+?</row>' | sed 's/<[^<]\+>/ /g;s/ \+/ /g')
 42522 42561
1 真弓 真弓
2 弘田 北村
3 バース バース
4 掛布 掛布
5 岡田 佐野
6 佐野 木戸
7 平田 平田
8 木戸 永尾
9 ゲイル 池田

になりますが、手順が非常に複雑なため、大きく3つの部分に分解して説明します。

  1. 文字列置換用テーブルの生成

     $ unzip -p hanshin.xlsx xl/sharedStrings.xml | grep -Po '<si>.+?</si>' | sed 's/<si><t>//;s/<\/t>.*//' | awk '{print NR-1,$1}' > TABLE
     $ cat TABLE
     0 真弓
     1 弘田
     2 バース
     3 掛布
     4 岡田
     5 佐野
     6 平田
     7 木戸
     8 ゲイル
     9 北村
     10 掛布
     11 佐野
     12 木戸
     13 永尾
     14 池田
    

    ワークシートで表示されるそれぞれの文字列は、xl/sharedStrings.xmlsi要素の子要素であるt要素に格納されています。

    そして、ワークシートxl/worksheets/sheet1.xmlの方では、<c r="B2" t="s"><v>0</v></c>のように、c要素の子要素であるv要素の値(0から始まるインデックス番号)に置き換えられています。

    手順については、A5の別解と同じくA4の手法を応用して、最後のawk '{print NR-1,$1}'の部分でインデックス番号を生成しています。

  2. ワークシートのデータの抽出

     $ unzip -p hanshin.xlsx xl/worksheets/sheet1.xml | grep -Po '<row.+?</row>' | sed 's/<[^<]\+>/ /g;s/ \+/ /g' > SHEET
     $ cat SHEET
      42522 42561
      1 0 0
      2 1 9
      3 2 2
      4 3 10
      5 4 11
      6 5 12
      7 6 6
      8 7 13
      9 8 14
    

    ここでもA4の手法を応用して各セルのデータを抽出しています。

  3. ワークシートのデータ置換と出力

     $ awk 'NR<=15{T[$1]=$2}NR==16{print $0}NR>=17{print $1,T[$2],T[$3]}' TABLE SHEET
      42522 42561
     1 真弓 真弓
     2 弘田 北村
     3 バース バース
     4 掛布 掛布
     5 岡田 佐野
     6 佐野 木戸
     7 平田 平田
     8 木戸 永尾
     9 ゲイル 池田
    

    1.で生成したTABLEと、2.で生成したSHEETを順にawkに読み込ませます。

    TABLEの部分に相当する1〜15行目では、第1フィールドをキー、第2フィールドを値とした連想配列Tに置換用テーブルを格納しています。

    そして、SHEETの部分に相当する16行目以降では、ヘッダとなる16行目を除いて「第1フィールド 連想配列Tの要素 連想配列Tの要素」というレイアウトで出力を行っています。

これら3つのシェル芸を、プロセス置換(<( ))を使って1つにまとめたものが先程の

awk 'NR<=15{T[$1]=$2}NR==16{print $0}NR>=17{print $1,T[$2],T[$3]}' <(unzip -p hanshin.xlsx xl/sharedStrings.xml | grep -Po '<si>.+?</si>' | sed 's/<si><t>//;s/<\/t>.*//' | awk '{print NR-1,$1}') <(unzip -p hanshin.xlsx xl/worksheets/sheet1.xml | grep -Po '<row.+?</row>' | sed 's/<[^<]\+>/ /g;s/ \+/ /g')

となります。

A7: 文字のはめ込み――その1

$ unzip -q certificate.docx -d tmp && sed -i 's/WINNER/シェル芸人/g;s/TODAY/'"$(date +%F)"'/g' tmp/word/document.xml; (cd tmp; zip -qr ../シェル芸人.docx .) && rm -fR tmp
$ docx2txt シェル芸人.docx - | uniq
優秀賞
2016-12-25
PUBPOSITION PUBNAME
あなたは優秀な成績を修めましたのでここに表彰します
シェル芸人 殿

この問題の解き方は、

  1. unzip [圧縮ファイル] -d [展開先ディレクトリ]certificate.docxtmpディレクトリに展開
  2. sed -itmp/word/document.xml内の「WINNER」と「TODAY」をそれぞれ置換し、上書き保存
  3. zip -r [圧縮ファイル] [圧縮するディレクトリ]シェル芸人.docxを生成
  4. rm -fRで後片付け

という手順になっています。

A8: 文字のはめ込み――その2

$ cat list.txt | xargs -I@ bash -c "unzip -q certificate.docx -d tmp && sed -i 's/WINNER/@/g;s/TODAY/'"$(date +%F)"'/g' tmp/word/document.xml; (cd tmp; zip -qr ../@.docx .) && rm -fR tmp"
$ cat list.txt | xargs -I@ bash -c "docx2txt @.docx - | uniq"
優秀賞
2016-12-25
PUBPOSITION PUBNAME
あなたは優秀な成績を修めましたのでここに表彰します
シェル芸おじさん 殿
優秀賞
2016-12-25
PUBPOSITION PUBNAME
あなたは優秀な成績を修めましたのでここに表彰します
シェル芸野郎 殿
優秀賞
2016-12-25
PUBPOSITION PUBNAME
あなたは優秀な成績を修めましたのでここに表彰します
変態シェル芸豚野郎 殿

A7の応用編です。

cat list.txtで得た名前のリストを、xargs -I 置換文字列A7のシェル芸をbashに読み込ませて実行させています。


LT大会

Excelでもコマンド実行がしたい

発表者: nmrmsys(@nmrmsys)さん

  • ExSQell」の紹介
    • Excelのシート上にあるシェルコマンドやSQL文を実行し、その出力をシート上で利用できる
    • 機能強化: リダイレクト機能が加わる
    • 機能強化: SSHを使ったリモートコマンド実行機能が加わる
    • 機能強化: sudoが利用可能になる

Hyper(TM)でエキサイティングなシェル芸ライフ

発表者: くんすと(@kunst1080)さん

  • Hyper」の紹介
    • Electron製のターミナルエミュレータ
    • クロスプラットフォーム
    • プラグイン機能あり
    • 注意点: 動作が不安定かつ重め・日本語には未対応

FUSEで繋がる世界

発表者: so(@3socha)さん

  • FUSE」とは
    • 仮想ファイルシステム作成用インターフェイス
    • Plan 9由来
    • 利用例: procfs・sshfs・s3fs/goofy・NTFS-3G・GmailFSなど
    • ユーザ空間で動作するため、安全で拡張性も高い
  • Googleカレンダー用カレンダーFSを実装してみる
  • ファイルシステムでズンドコキヨシ - Qiitaの紹介

漢字バナー芸

発表者: MSR(@msr386)さん

  • 「バナー芸」とは
    • 文字を目立たせるシェル芸
    • 代表的コマンド: banner・figlettoilet
  • toiletで漢字が使えるようにする
    • フォントデータの形式
    • 漢字のフォントデータを自動生成する方法

追加: バナー芸

発表者: 社畜(9か月)@ぬるり(@null_ref_eng)さん

echo "___\\\\(^o^)/___オワタ___" | sed ':a;p;s/\(.\)\(.*\)/\2\1/;/^a/!b a' | xargs -I{} toilet -F border --gay {}

おわりに

bashのキーバインドといえば、今までemacsモードの移動コマンド程度しか使っておらず、キーボード入力時に結構ムダな動作をしている自覚があったので、午前の部の講義で 紹介されたキーバインド一覧をせめて半分程度でも覚えてみようかと思います。

そういうことでライブストリームからキーバインドを全て書き出してみましたので、あくまでも自分用に作ったものではありますがチートシート代わりに使っていただければ幸いです。

午後のエクシェル芸では、大阪サテライト会場の参加者全員が

  • 「Software Design 2017年1月号」を購入している
  • MS Officeのファイルが、XMLファイルや画像ファイルなどをzipで圧縮したものであることを知っている

という恐るべき事実が判明したのが印象的でした。

最後に、

  • 大阪サテライトの主催をされている、くんすと(@kunst1080)さん、T.Motooka(@t_motooka)さん
  • 大阪サテライト会場を提供してくださっている、フェンリル株式会社 大阪本社
  • 午前の部の講師を務めてくださった、鳥海秀一さん
  • Ryuichi Ueda(@ryuichiueda)さんをはじめとする、メイン会場スタッフの皆さん

に改めてお礼を申し上げます。ありがとうございました。

……エクシェル芸はムズいが役に立つ。

Written on December 31, 2016