Upgrade to Pro — share decks privately, control downloads, hide ads and more …

シェル芸人に必要なのは「マスキングテープ」だったのでは

greymd
June 27, 2020

 シェル芸人に必要なのは「マスキングテープ」だったのでは

2020/06/27 第48回シェル芸勉強会LT資料
https://www.youtube.com/watch?v=PIqx9fCSbaU&t=609s

greymd

June 27, 2020
Tweet

More Decks by greymd

Other Decks in Programming

Transcript

  1. シェル芸⼈に必要なのは
    「マスキングテープ」だったのでは
    ぐれさん

    View Slide

  2. ⾃⼰紹介 (1/3)
    ぐれさん (Twitter: @grethlen)
    シェル芸キュアエンジニア
    危険シェル芸で国外追放された(⼤嘘)
    最近良いことがあった

    View Slide

  3. ⾃⼰紹介 (2/3)
    朗報

    出典: 2020/06/21 放映「ヒーリングっど♡プリキュア」より

    View Slide

  4. ⾃⼰紹介 (3/3)
    Software Design シェル芸連載
    「シェル芸⼈からの挑戦状」の連載
    参加してました
    2020 年 4 ⽉号にて⼀旦終了

    STOPがかかったわけ
    ではないではない
    150 問以上
    ご愛読ありがとうございました!

    View Slide

  5. シェル芸漬けの2年半
    数多くの問題を解いたり考えたり
    締切に追われて苦しみながら
    シェル芸⼈たちと頭をひねった2年半..
    => ある法則に気づいた

    View Slide

  6. こういう状況ありませんか
    仕事や研究などで遭遇した困りごとをシェル芸で解決しようとするとき...

    出典: NHK健康チャンネル「発達障害って何だろう」より

    View Slide

  7. 「できるけど疲れる」問題1
    第16回春だからログ解析するぞシェル芸勉強会
    https://b.ueda.tech/?post=05644
    準備1 ⼀部抜粋
    ⽇付と時刻を次のように正規化しておきましょう。
    ###
    修正前###
    [email protected]:~/tmp/nasa$ zcat access_log.nasa.gz | head -n 1
    199.72.81.55 - - [01/Jul/1995:00:00:01 -0400] "GET /history/apollo/ HTTP/1.0" 200 6245
    ###
    修正後###
    [email protected]:~/tmp/nasa$ cat access_log | head -n 1
    19950701 000001 199.72.81.55 - - [01/Jul/1995:00:00:01 -0400] "GET /history/apollo/ HTTP/1.0" 200 6245

    360 MB のファイルです

    View Slide

  8. 問題1 解答例
    模範解答
    zcat access_log.nasa.gz | awk '{print $4,$0}' |
    sed 's/^\[//' | awk '{gsub(/[\/:]/," ",$1);print}' |
    awk '{$2=$2=="Jul"?"07":$2;$2=$2=="Aug"?"08":$2;print}' |
    sed 's;^\(..\) \(..\) \(....\) \(..\) \(..\) \(..\);\3\2\1 \4\5\6;' > access_log
    => AWK では⽉の名前(Jan, Feb...) => 数字の変換ができない
    date
    で⽇付の変換はできるのだが..
    $ echo '01/Jul/1995:00:00:01 -0400' | sed 's|/| |g;s/:/ /' | date -f- +'%Y%m%d %H%M%S'
    19950701 050001
    => 事実上使えない(後述)

    View Slide

  9. 問題1 解答例(参加者抜粋)
    while でやった⼈
    zcat access_log.nasa.gz |
    while read LINE; do echo ${LINE} |
    sed 's/[^[]*\[//; s/].*//; s/-[0-9]\{4\}//; s/Jul/07/; s/Aug/08/;' |
    tr '/:' ' ' | awk '{printf($3$2$1" "$4$5$6" ")}'; echo $LINE; done
    => 解けるが、かなり時間かかる
    Ruby でやった⼈
    zcat access_log.nasa.gz|
    ruby -EASCII-8BIT -rdate -ane '$F.unshift Date.parse($F[3]).strftime("%Y%m%d %H%M%S");puts $F*" "'
    => 解ける
    なお、ぐれさんは while
    + date
    で⾒事にマシンがホッカイロ(⽩⽬)

    View Slide

  10. 「できるけど疲れる」問題2
    ぐれさんが実際に遭遇した問題イメージ(※
    脚⾊済み)
    500 MB の CSV ファイルの4列⽬に名前がある
    これの姓と名の最初の⽂字を⼤⽂字にしたい
    1,aaa,Hoge team,yamada taro,2001
    2,bbb,Super fuga team,ueda ryuchan,1998
    3,ccc,N/A,toilet hanako,1998
    ...

    1,aaa,Hoge team,Yamada Taro,2001
    2,bbb,Super fuga team,Ueda Ryuchan,1998
    3,ccc,N/A,Toilet Hanako,1998
    ...

    View Slide

  11. 問題2 解答例
    名前単体の変換は次のでできる!でも sed
    はCSV読めないし..せや!
    $ echo yamada taro | sed 's/\b./\U&/g'
    Yamada Taro
    遅くて使い物にならない(涙)
    $ cat people.csv | while IFS=, read c1 c2 c3 c4 c5 ;do echo "$c1,$c2,$c3,$(echo "$c4" | sed 's/\b./\U&/g'),$c5" ;done
    $ cat people.csv | perl -F, -anle '{chomp($F[3]=`echo $F[3] | sed "s/\\b./\\U&/g"`); $,=","; print @F}'
    結局 Perl でゴリ押しが現実的なソリューション..
    cat people.csv | perl -F, -anle '{ $F[3] =~ s/(\b.)/\U$1/g; $,= ",";print @F}'

    View Slide

  12. これらの問題の共通点はなんだろう?
    元々の⼊⼒を残したまま⼀部分を書き換えている
    処理対象が⼤きい
    データの加⼯⾃体は古典的なコマンドでできる
    でも使えないので、結局スクリプトでゴリ押しが現実解
    => こんなの絶対おかしいよ!

    View Slide

  13. 「できるけど疲れる」原因は何か?(1)
    シェル芸の⾟い所(1):
    ⼤半のコマンドが「処理したくないデータの無視」ができない
    date
    , base64
    , factor
    , iconv
    , nkf
    , grep
    , fold
    , cut
    , ...

    View Slide

  14. 「できるけど疲れる」原因は何か?(2)
    シェル芸の⾟い所(2):
    問題が複雑になるとスクリプティング地獄
    「処理したくないデータの無視」ができるコマンドもあるが...
    awk
    , sed
    , perl
    , ruby
    , ...
    「処理対象の選別」と「データの加⼯」がセット
    ⽴派なプログラミング⾔語 => ⾔語の制約に縛られる
    ⾔語の仕様や技法・ライブラリに明るい必要がある
    => いざというときサクッとできない

    View Slide

  15. 補⾜:「処理対象の選別」と「データの加⼯」がセット
    GNU sed
    ... | sed '/AAA/,/BBB/{ s/hoge/fuga/g }'
    GNU Awk
    ... | awk '{gsub("A","B",$1);gsub("B","C",$3);print}'
    Perl
    ... | perl -nle '/^(......)(...)/; $a=$1;$b=$2; $b =~ s/./@/g; print "$a$b"'

    View Slide

  16. 「できるけど疲れる」原因は何か?(3)
    シェル芸の⾟い所(3):
    スクリプティング地獄を解消しようとするとfork地獄に陥る
    Bash の while

    sed、AWK等の内部から別のコマンドを外部コマンドとして呼び出す
    ⼤量のデータ処理には向かない

    View Slide

  17. シェル芸⼈のジレンマ
    スクリプティング地獄 ↔ fork地獄
    とてもつらい

    View Slide

  18. シェルとコマンドのアプローチを振り返る
    パイプ
    コマンド同⼠をべったりとくっつける糊
    どこを処理するかはコマンドにおまかせ
    UNIX のコマンド
    標準⼊⼒を「丸ごと」受け取って処理するコマンドが⼤半
    糊につける「マスキングテープ」があればどうだろう?
    「べったり」ではなく「⼀部分のみ」
    残りの部分は「そのまま」
    世の中の問題はシンプルになるのではないか

    View Slide

  19. teip コマンド
    シェル環境で使えるマスキングテープ
    [URL] https://github.com/greymd/teip
    macOS
    brew install greymd/tools/teip
    DEB file
    git.io/teip-1.2.0.x86_64.deb
    RPM file
    git.io/teip-1.2.0.x86_64.rpm
    Cargo(要 libclang

    cargo install teip

    View Slide

  20. teip はなにができるか
    teip <
    フィルタリングルール> -- <
    コマンド>
    <
    フィルタリングルール>
    : 標準⼊⼒のどの部分を <
    コマンド>
    に渡すか設定
    <
    コマンド>
    : 標準⼊⼒まるごとは受け取る必要はない
    <
    フィルタリングルール>
    にマッチしなかった個所
    => そのまま出⼒
    <
    フィルタリングルール>
    にマッチした個所
    場所はそのままだが <
    コマンド>
    の結果と置換される

    View Slide

  21. teip の基本的な使い⽅

    デモ

    README の Getting Started の内容を紹介

    View Slide

  22. teip で先程の問題を解く

    デモ
    問題1
    $ zcat access_log.nasa.gz |
    awk '{print $4,$0}' | sed 's/^\[//' |
    teip -f 1 sh -c "sed 's|/| |g;s/:/ /' | date -f- +'%Y%m%d %H%M%S'"
    問題2
    $ cat people.csv | teip -d, -f4 sed 's/\b./\U&/g'

    View Slide

  23. こういう問題も解ける(時間があればデモ)
    第30回危念シェル芸勉強会
    https://b.ueda.tech/?post=10134
    Q2 ⼀部抜粋
    リンクが相対パスになっているものについては頭に/files/をつけて、/から始まっているも
    のとhttpやhttpsから始まっているものはそのままにしてください。
    (..
    略..)

    ほげ


    クソブログ
    ふげ

    これはそのまま

    更新してない
    (..
    略..)

    View Slide

  24. 解答例
    sed
    が同じ⾏を書き換えてしまうのが⾟い
    $ cat url.html | sed -r 's;(img src="|a href=");&/files/;g' |
    sed -r 's;(href="|src=")/files//;\\1/;' |
    sed -r 's;(href="|src=")/files/(https://|http://);\\1\\2;g' |
    sed 's;/./;/;g'
    他の参加者の解答
    perl -ple '[email protected][fc]="(?!(?:https?://|/))(?:./)?(.*?)"@="/files/$1"@g' url.html
    sed 's,\([fc]="\)\(\./\)\?\([^/][^":]*\)",\1/files/\3",' url.html
    sed -r -e '[email protected]((href|src)=)([^/])@\1/files/\[email protected]' -e '[email protected]/files/[email protected]@g' url.html

    View Slide

  25. 解答例(teip)
    短く解こうと思えばできる
    $ cat url.html | teip -Gog '[fc]="(?!(https?|/))\K.*?(?=")' -- sed 's|[./]*|/files/|'
    => ⻤⾞正規表現が使える( -G

    テープの重ねがけ
    $ cat url.html |
    teip -og '(a href|img src)=".*?"' \
    -- teip -og '".*"' \
    -- teip -og '[^"]+' \
    -- teip -vg http \
    -- teip -vg '^/' \
    -- sed 's|[./]*|/files/|'
    => 正規表現の AND 条件(!)... 表現⼒は超強⼒

    View Slide

  26. teip のパフォーマンス
    細かくベンチマークをとりながら Rust で開発
    実⾏対象のコマンドは使い回す
    => fork しないので早い(※

    実⾏コマンドの標準⼊出⼒をプロキシ
    複数プロセス並列実⾏
    ⼀時ファイルなし
    I/O は極⼒バッファリング
    => 無駄なシステムコールを削る

    都度forkする動作も選択可能

    View Slide

  27. なぜ Rust か
    速度・並⾏性に定評があるコンパイル型⾔語
    Rust 製 Coreutils(uutils/coreutils)あり
    性能をベンチマーク
    => 本家に負けず劣らず
    => ヒーリングっどきた
    Cの資産の取り回しも悪くない
    例: ⻤⾞正規表現
    Cの共有ライブラリも⼀緒にビルド

    View Slide

  28. teip vs GNU grep
    数百100MBのファイルの⾏頭から30⽂字に
    拡張正規表現で⾊をつける謎の速さ対決

    ページキャッシュ消すのを忘れずに
    $ time grep --color=always -E '^.{30}' < test_secure | pv > /dev/null
    $ time teip -og '^.{30}' < test_secure | pv > /dev/null
    => デモ

    View Slide

  29. 注意:
    teip単体で⼗分な⽔準のI/O速度を出せる点のアピール以上の意図は無い
    ユースケースが異なるものを⽐較してもあまり意味はない
    teipはまだまだ⽣まれたばかりのソフト
    いわゆる「第⼀のシステム」※
    成熟したソフトより速度が早い状況は「あるある」

    View Slide


  30. 芳尾 桂 (翻訳) Mike Gancarz (著). UNIX という考え⽅―
    その設計思想と哲学. オーム社, 2001.

    View Slide

  31. ベンチマーク (1)
    なので、こんなベンチマークはどうでしょう?
    GNU sed vs GNU sed (+ teip)
    $ sed -r 's/<
    正規表現>/.../g' <
    ファイル
    $ teip -og '<
    正規表現>' -- sed -r 's/<
    正規表現>/.../g' <
    ファイル
    GNU Awk vs GNU Awk (+ teip)
    $ awk '{gsub("<
    正規表現>","...",$0);print}' <
    ファイル
    $ teip -og '<
    正規表現>' -- awk '{gsub("<
    正規表現>","...",$0);print}' <
    ファイル
    ※ <
    正規表現>
    はすべて同じもの

    View Slide

  32. ベンチマーク (2)
    ⼤きなファイルのIPアドレスを置換
    上: 置換前
    下: 置換後

    View Slide

  33. ベンチマーク (3)
    $ teip -og '<
    正規表現>' -- awk '{print "..."}' <
    ファイル
    $ teip -og '<
    正規表現>' -- sed -n 'i...' <
    ファイル
    teip があるから実現可能な効率的な⽅法
    よりフェアな⽐較(?)
    正規表現の実⾏は1回のみ

    View Slide

  34. 0 2 4 6 8 10
    awk
    sed
    teip + awk
    teip + sed
    teip + awk (print)
    teip + sed (i text)
    Total Runtime(sec)
    8.390
    5.393
    4.306
    3.795
    2.106
    1.836

    詳細・追試⽅法は https://github.com/greymd/teip/wiki/Benchmark

    View Slide

  35. まとめ
    シェル芸は「処理したくないデータの無視」が苦⼿
    「処理対象の選別」と「データの加⼯」が癒着してた
    複雑な問題では、スクリプティング地獄 あるいはfork地獄
    teip とは
    「⼀つのこと」(処理対象の選別)をうまくやる
    問題の解法がシンプルになる
    オマケに「処理対象の選別」と「データの加⼯」を並列化できる
    性能向上が図れる

    View Slide

  36. 今後の展望
    teip のこれから
    sed
    の /AAA/,/BBB/
    みたいなのがほしい
    sed
    みたいにN倍数の⾏数のみ、とか(ほしいです?)
    これで仕事が楽になる⼈は多い気がするので布教
    エネルギーの浪費で地球環境に悪い
    地球をお⼿当て、そう今のプリキュアのように
    ああああプリキュアが⾒たいいいいしぬんじゃぁ
    ぼやき
    まてよ、Rust 製 Coreutils があるなら、Cureutils がないとおかしいのでは?
    Rustめっちゃいい。。egzact 作り直そうか。。どうしても遅いよぉ。。

    View Slide