シェル芸勉強会 第35回 に参加しました!
今回は東京会場での参加です。
usptomo.doorkeeper.jp
あわせて、LTにて 続・パイプを通るPDF
という題でしゃべったりもしたので、その内容も解説しておきたいと思います。
続・パイプを通るPDF
LTでしゃべった時のスライドです(川柳・ちょっと字余り)
speakerdeck.com
※スライドの9ページには微妙に間違いがあります。。
意図
PDFファイルに対して、何らかのちょっとした加工(線を引いたり)を、ワンライナーで実現することを目的にしています。
ワンライナーでPDFを吐く
のならば、おそらくPostScriptを生成して、それをGhostScriptでPDFに変換する、といったアプローチも取ることができるでしょうし、その方が簡単でしょう。
最終的なワンライナー
convert xc:none -page A4 pdf:-| gs -q -sOutputFile=- -sDEVICE=pdfwrite -c '<< /EndPage {exch pop 2 lt { newpath 200 300 moveto 10 200 rlineto closepath 0.8 0.2 0.1 setrgbcolor stroke true}{false} ifelse} bind >> setpagedevice' -| pdftk - output - uncompress
簡単に解説
convert
(ImageMagick)- オプション
xc:none
は、空っぽの画像を生成することを示します。詳しくは前回(2016年)の記事へ パイプを通るPDF(実験中) 〜 #シェル芸 の新たなる境地へ - 職業プログラマの休日出勤 - このコマンドは、通常は出力をファイルとしていて、出力ファイル形式は出力ファイル名の拡張子から判断しています。標準出力に吐く場合は形式がわからなくなってしまうので、
pdf:-
によって、PDF形式でstdoutに吐くことを示しています。
- オプション
gs
(GhostScript)-sOutputFile=-
出力をstdoutとします-sDEVICE=pdfwrite
出力形式をPDFとします。省略するとPostScriptで吐きます。gsはもともとPostScriptの処理用のプログラムですので。-c
PostScriptのコマンドを与えていることを示しますnewpath
パスの開始200 300 moveto
ペンの移動:座標平面の原点(左下原点です!)からx右方向に200、y上方向に300移動します。10 200 rlineto
線分をパスとする:現在のペンの位置から、相対的にx右方向に10、y上方向に200移動した先までの線分をパスとします。closepath
幾何学的に「閉じた」パスを作成するために、現在のペンの位置からパスの開始位置までに戻る線分をパスとして加えます。ここでは不要なのですが、「閉じた」パスになっていると色々便利なのでよく使います。0.8 0.2 0.1 setrgbcolor
RGBで色を指定します。左から赤、緑、青の順で、0 0 0
なら黒、1 1 1
なら白、0.8 0.2 0.1
なら赤っぽい色、になります。stroke
上記で描いたパスで、実際に線を描きます。- 上記以外のPostScript部分:説明するとそれだけで記事1つ書けちゃいますので省略。説明みたい人は、このStackOverflowへ:jpeg - Is it possible in Ghostscript to add watermark to every page in PDF - Stack Overflow
pdftk
- これによって、PDFの描画部分の
FlateDecode
での圧縮を伸長しています。PDFのグラフィックコマンド群が human-readable (ほんとか?www)になります。
- これによって、PDFの描画部分の
環境構築例
上記のワンライナーを走らせる際の環境構築例を示します。(Dockerの場合)
今後の展望
ご想像の通り、PostScript部分は yes
なりsed
なりawk
なり、お好きな方法で構築することができるでしょう。あとは、ちょっとしたPostScriptの文法やら何やらを覚えれば自由自在にPDFを作れそうですね! ちなみに、非ASCII文字を出すハードルは相当高いので、幾何学模様を出す程度に留めておいた方が幸せかもしれません。
問題と思考の過程
シェル芸勉強会の本編で出された問題と、それに対する回答や思考の過程を紹介しておきます。
問題文 【問題のみ】jus共催 第35回またまためでたいシェル芸勉強会 | 上田ブログ
Q1
取り組んだアプローチは、次のようなものでした。
curl parrot.live
が画面をクリアする際に使っている制御文字を特定し、- その制御文字を検出したら wait が入るような何かを仕込みつつ
- ファイルに保存したデータを読み出す
まずは制御文字を割り出すために、次のような操作をやりました。
curl parrot.live | od -x | head -n 100 | sed -e 's/[ ][2-7][0-9a-f]//g' -e 's/[ ][2-7][0-9a-f]//g'
この結果、次のようなものが得られます。
0001460 0a20 0001500 0001520 0001540 0a 0001560 0001600 0001620 0001640 0a20 1b 1b6d 1b4a 1b 0001660 0001700 0001720 0001740 0a20 0001760
ここで、0a
は皆さんご存知、Line Feed(改行文字)です。20とか6dとかは、消し損ねた通常の文字ですね。
これで、どうやら「ページ」が変わるタイミングで使われている制御文字が0x1bらしいということがわかります。これはEscapeの制御文字ですので、次の文字との組み合わせによって特殊な効果を生みます。
では、sed
で消す前の文字を見ると、
(前略) 0001640 0a20 5b1b 3933 1b6d 325b 1b4a 485b 5b1b 0001660 3133 206d 2020 2020 2020 2020 2020 2020 (後略)
endianに気をつけながらこの辺のバイト列を上手く検出できれば、冒頭に掲げた理論で突っ走るだけです!
となったところで時間切れ。
他の皆さんは他の方法で解かれてたので、そちらの方が真っ当な解法だと思います。
Q2
これで回答しました
cat herohero | nkf -Z | sed -e 's/\([0-9]*\)/\1 /' | awk 'BEGIN{l=0;}{for(i=l+1;i<$1;i++){print""}l=$1;print $2;}'
awkのくだりは、古典的なプログラミング言語な感じがしますが、まあこれもいいでしょう(たぶん)
Q3
これで回答しました
cat data | sort | uniq -c | awk '{a[$2]=a[$2] " " $3 ":" $1}END{for(i in a)print i a[i]}'
ここで a[$2]=a[$2] " " $3 ":" $1
の文字列結合の時点で挟み込んでいる空白文字が、キモですね。上手く使えた感があります。
Q5
orz
Q6
これで回答しました
seq 1 100000 | factor | grep -v ' [3-9][0-9]' | grep -v ' 2[4-9]' | grep -v ' [0-9][0-9][0-9]' | head -n 1985 | sed 's/:.*//'
ひたすら grep -v
で条件対象外のものを除去する作戦です。
Q7
これで回答しました
seq 1 100 | factor | tr -d ':' | awk 'BEGIN{a=0}$1==$2&&a<10{a++;print substr("HelloWorld",a,1)}$1!=$2{print "x"}' | xargs | tr -d ' '
手元の環境では日本語の入力ができなかったので、ASCII文字だけです。出題の意図から考えれば、print "x"
はランダムさが必要です。
Q8
orz
書籍紹介コーナー
この本を読めば、$ cat hoge.pdf
とかしちゃっても戸惑うことは減るはずです。
- 作者: John Whitington,村上雅章
- 出版社/メーカー: オライリージャパン
- 発売日: 2012/05/25
- メディア: 単行本(ソフトカバー)
- クリック: 166回
- この商品を含むブログ (9件) を見る
こちらの記事でも紹介してます => さぁ、PDF手書きの世界へ。 - 職業プログラマの休日出勤