Skip to content

Instantly share code, notes, and snippets.

@greymd
Last active May 30, 2023 16:10
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save greymd/c780d25ef4ec826dd04dffd6d2696b4f to your computer and use it in GitHub Desktop.
Save greymd/c780d25ef4ec826dd04dffd6d2696b4f to your computer and use it in GitHub Desktop.
teip v2.0 でヤバい機能がついた件

teip v2.0 でヤバい機能がついた件

teip コマンド に関するお話

まとめ

  1. teip v2.0 に新機能:マッチオフロード機能
  • テープの穴をあける行の指定に外部コマンドが使えるようになった
  1. あるコマンドの抽出範囲のみを、範囲外は残したまま、別のコマンドで編集可能になる
  • 例1: grep で抽出した範囲だけを、sed で編集
  • 例2: sed で抽出した範囲だけを、awk で編集
  • 例3: tail で抽出した範囲だけを、perl で編集
  1. コマンドの「パターンマッチ」と「編集」を切り離して利用できる時代が到来

自己紹介

およそ 2 年前 teip というコマンドを作った

  • 1 年と 10 ヶ月前 (2020/06/27) にお披露目したシェル環境の「マスキングテープ」
  • 多くの反響(大した宣伝はしてないのに ☆ 300 超えた)
    • 海外からも届く Issue / PR
  • 界隈で粛々と使われている(みんなだいすき)
    • シェル芸本でも登場

teip ってなんだっけ

※ 詳しくは以下の資料をみてね。 資料: シェル芸人に必要なのは「マスキングテープ」だったのでは - Speaker Deck

対話シェル環境の「マスキングテープ」

$ echo 100 200 300 400 | teip -f 3
100 200 [300] 400
  • 標準入力全体はマスキングテープに覆われる
  • ただし、300 の位置に穴があいている(と理解してください)

  • テープの下はいじれない → そのままの位置でキープ
  • 穴が空いたところだけ、パイプに渡して加工
$ echo 100 200 300 400 | teip -f 3 -- sed 's/.*/うんこ/g'
100 200 うんこ 400

特徴

  • 色んなアプローチで穴を開けられる

    • 正規表現、カンマ区切り、行番号指定、文字範囲指定
    • teipteip を重ねることで AND 条件で複雑に指定ができる
      • 例: teip -g AAA -- teip -f 3 -- sed 's/./@/' => "AAA" を含む行の 3 列目に穴をあけて sed で置換
  • 高パフォーマンス

    • I/O バッファリング、複数スレッド、高速に GB クラスのファイルでもさばける
    • Rust 製 メモリ安全 おれ騒然(川柳)
    • GNU 製コマンド単体より、むしろ teip を併用すると高速化するケースも

利用例

  • CSV ファイルの 3 列目だけ編集
  • 巨大なログの日付を date で変換
  • パターンマッチ機能を持たないコマンドを加工に利用できる
    • 文章の一部分の URL を短縮する、Base64 変換する、漢字だけふりがなを振る、参考文献の番号を振り直す.. etc

v2.0 での新機能

外部コマンドによるマッチオフロード機能

  • 新オプション -e (external command という意味で e)
  • teip ではなく、外部コマンドにもマスキングテープの穴を開けさせることが可能に
teip -e 'コマンド文字列' -- コマンド
  • 今までは teip しか穴の位置は制御できなかった

    • -f (フィールド指定)、 -g 正規表現、-l (行番号指定)、-c (文字範囲指定) など
  • ふるつきさんと Issue で議論した末に誕生 => GitHub Issue 12

    • ありがとうございますありがとうございます

マッチオフロード機能 -e の使い方

-e の特性1

-e には文字列としてコマンドラインを指定でき、このコマンドラインの出力結果の数字が、行番号指定に使われる。

$ echo -e "AAA\nBBB\nCCC" | teip -e 'echo 3'
AAA
BBB
[CCC]

=> 3 行目に穴があく

複数行もいける(昇順である必要あり)。

$ echo -e "AAA\nBBB\nCCC" | teip -e 'echo 1;echo 2'
[AAA]
[BBB]
CCC

以下で、2 行ごとに穴があく。

$ echo -e 'AAA\nBBB\nCCC\nDDD\nEEE\nFFF\n' | teip -e 'seq 1 2 inf' -- sed 's/./@/g'
@@@
BBB
@@@
DDD
@@@
FFF

-e の特性2

コマンドラインの出力が多少汚くても動く

  • スペースやタブ文字が文頭に含まれていてもそれらは無視
  • 一旦数字が与えられれば、数字よりも右側に文字列があっても問題なし
  • 内部的には、^\s*([0-9]+) という表現における 1 つめのグループを数字として認識
$ echo - 'AAA\nBBB\nCCC' | teip -e 'echo "   1"'
[AAA]
BBB
CCC
$ echo - 'AAA\nBBB\nCCC' | teip -e 'echo "   2うんこうんこ"'
AAA
[BBB]
CCC

-e の特性3

-e のコマンドラインは teip 本体と同じ標準入力のコピーが与えられている

seq のような数字を出力するコマンドだけでなく、 「入力を踏まえて数字を出力する」コマンド(grepsedawkperl…)も使える。

この特性を活かしてできること

例: 以下は「"CCC"という文字列を含んだ行と、それよりあとの2行の行番号」を表示する grep のコマンドで、-n-A オプションを利用

$ printf 'AAA\nBBB\nCCC\nDDD\nEEE\nFFF\n' | grep -n -A 2 CCC
3:CCC
4-DDD
5-EEE

これを、-e に与えると、「"CCC"という文字列を含んだ行と、それよりあとの2行」に穴を開けることができる

$ printf 'AAA\nBBB\nCCC\nDDD\nEEE\nFFF\n' | teip -e 'grep -n -A 2 CCC'
AAA
BBB
[CCC]
[DDD]
[EEE]
FFF

GNU sed には = という、処理中の行番号を表示するコマンドがある。 下記は "BBB"を含んだ行から、 "EEE" を含んだ行までに穴をあける例。

$ printf 'AAA\nBBB\nCCC\nDDD\nEEE\nFFF\n' | teip -e 'sed -n "/BBB/,/EEE/="'
AAA
[BBB]
[CCC]
[DDD]
[EEE]
FFF

もちろん、同様の操作は awk でも実現可能。

$ printf 'AAA\nBBB\nCCC\nDDD\nEEE\nFFF\n' | teip -e 'awk "/BBB/,/EEE/{print NR}"'
$ printf 'AAA\nBBB\nCCC\nDDD\nEEE\nFFF\n' | perl -nle 'print $. if /BBB/../EEE/'

-enl は非常に相性が良い。 tailnl を組み合わせ、入力の末尾の3行のみに穴をあけることができる。 (末尾から N 行、という指定は他のコマンドでは難しい、ですよね?)

$ printf 'AAA\nBBB\nCCC\nDDD\nEEE\nFFF\n' | teip -e 'nl -ba | tail -n 3'
AAA
BBB
CCC
[DDD]
[EEE]
[FFF]

-e の引数は単一の文字列なので、| などの記号を使うこともできる。

利用例

通常のシェル芸では苦労する処理、実現が現実的ではない処理(諦めてスクリプトを書き始めるような処理)すら、簡単にできてしまう。

$ cat file | teip -e 'grep -C 2 うんこ' --  sed 's/./@/g'

パフォーマンス

以前から引き続き、-e もまた数 100 MB の利用もストレスがない高速動作。

ざっくり検証紹介

100 MB のファイル の「admin」を含む行とその前後 2 行に含まれる文字をすべて @ に置換する検証

107 万行ちょっとのファイルのうち、16万行、だいたい全体の 15 % くらいをマッチさせる。 適宜 admin が含まれている位置は全体的にバラけている。

$ wc -l test_secure
1078333 test_secure
$ grep -C 2  admin test_secure | wc -l
160893
$ grep admin test_secure | wc -l
66771
  • 以前同様、AWS の t3.medium 上で検証、ただし RAM ディスク上で検証

検証結果

シェル芸人がすぐに思いつく愚直な方法
grep -n -C 3 admin < test_secure | awk -F '[-:]' '{print $1}' | awk NF | awk '{print $0"s/./@/g"}' | sed -f- test_secure

=> 28448 秒(7.9 時間)

ちょっと賢くしてみた(連続する行をまとめる)
grep -n -C 3 admin < test_secure | awk -F '[-:]' '{print $1}' | awk NF | awk '{ do{ for(s=e=$1; (r=getline)>0 && $1<=e+1; e=$1); print s==e ? s : s","e }while(r>0) }'| awk '{print $0"s/./@/g"}' | sed -f- test_secure

=> 469.16 秒

Python のスクリプト
teip + grep + sed
teip -e grep -n -C 3 admin -- sed s/./@/g

=> 31.68 秒

がんばったところ(小並感)

注意点

現状、メモリはモリモリ使う。

  • 本来一時ファイルを作ってやるべきような処理をキューイングで頑張っている
  • 複数スレッドで標準入力を処理するスピードに差があると、その差分はメモリ上に乗る
    • -e のコマンドのスレッドと、teip のマスキングテープを処理するスレッドに速度差があると、
  • 最悪のケース(片方のスレッドをノロマにする)など複数のケースでベンチマーク。
  • ピーク時は概ね ファイルサイズ x 3 のメモリを食う模様。
  • GB 単位の処理には注意。

メモリ使用率についてのベンチマーク詳細

--------------------------------------------------------------------------------
Command:            ./target/release/teip -e grep -n -C 3 admin -- sed s/./@/g
Massif arguments:   --time-unit=ms --massif-out-file=mem_usage_real.txt
ms_print arguments: ./mem_usage_real.txt
--------------------------------------------------------------------------------


    MB
320.5^                        #                                               
     |                        #:::                                            
     |                       @#:: ::                                          
     |                     ::@#:: ::::                                        
     |                     : @#:: :::::::                                     
     |                   ::: @#:: ::::: :::                                   
     |                   : : @#:: ::::: ::::::                                
     |                 @@: : @#:: ::::: ::::: ::::                            
     |                 @ : : @#:: ::::: ::::: : : ::                          
     |               @:@ : : @#:: ::::: ::::: : : : ::::                      
     |             @@@:@ : : @#:: ::::: ::::: : : : : : ::                    
     |             @ @:@ : : @#:: ::::: ::::: : : : : : :                     
     |           @@@ @:@ : : @#:: ::::: ::::: : : : : : : ::                  
     |           @ @ @:@ : : @#:: ::::: ::::: : : : : : : : :                 
     |          @@ @ @:@ : : @#:: ::::: ::::: : : : : : : : ::::              
     |         @@@ @ @:@ : : @#:: ::::: ::::: : : : : : : : ::::::::          
     |       @@@@@ @ @:@ : : @#:: ::::: ::::: : : : : : : : ::::::::::::::    
     |      :@ @@@ @ @:@ : : @#:: ::::: ::::: : : : : : : : ::::::::::: :::   
     |     @:@ @@@ @ @:@ : : @#:: ::::: ::::: : : : : : : : ::::::::::: ::::  
     |    :@:@ @@@ @ @:@ : : @#:: ::::: ::::: : : : : : : : ::::::::::: ::::: 
   0 +----------------------------------------------------------------------->s
     0                                                                   31.68

今後の展望

  • jq や moreutils のような存在を目指す
    • 「ビルトインじゃないけど、みんな使ってるデファクトスタンダードだよね」という立ち位置
  • きっと世の中を変える、と思うので、見守ってくだしあ。
    • 5 年、10 年スパンで粛々と。
  • 抱負
    • そろそろ Homebrew 本家に入れる
    • 各ディストロのリポジトリに入れたい(EPEL とか、snap とか)
    • 他のアーキテクチャのビルドも用意する
  • お前らのプルリクエスト待ってるぜ!!
    • もっとメモリ節約できないかなぁ
  • イースターエッグ入れたよ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment