著者:今泉光之 (Twitter: @bsdhack)
Open usp Tukubai は Python により実装されているので Python が動作する環境であればインストール可能です。
公式の配布サイトからソースを取得して展開し make install するだけで Open usp Tukubai は利用可能となります。
ここでは Open usp Tukubai のコマンドを解説します。
ただし以下のように Open usp Tukubai だけでも 55 コマンドも提供されており、その全てを解説するのは無理なので、
特に有用で興味深いコマンドを厳選して紹介します。
1 2 |
$ ls -1 /opt/local/tukubai/bin | wc -l 55 |
前回紹介したcalclock、comma、getlast、ketaに続き、今回はmojihame、self、sm2の3つのコマンドを紹介し、シェルスクリプトでの再現を試みます。
但し今回は紙面と時間の都合で一部の機能やオプションなど実装できていない部分も多いです。
また、動作の把握とメイン処理の実装のみに絞ったので、エラー処理などはまったく実施していません。
そのために実際の業務などには殆ど利用できない実験的なスクリプトとなっています。
テンプレート中に埋め込まれたキーワードをデータに置換して出力します。
キーワードは %数字 の形式で指定し、データ中に出現した順番にテンプレート中の %1、%2 …と置換されます。
データ中の @ はヌル文字として扱われ空文字列に置換されますが -d オプションで @ を別の文字に変更できます。
データ中の _ はスペースに変換して出力されます。_ を出力したい場合は \_ の様に指定します。
1 2 3 4 5 6 |
$ cat < template 1st=%1 2nd=%2 3rd=%3 4th=%4 EOF $ cat < |
-l オプションは行単位での置換処理が実施されます。
入力データは行単位に読み込まれ、入力データの次の行が読み込まれるとテンプレートは先頭から再利用されます。
1 2 3 4 5 |
$ cat < template 1st=%1 2nd=%2 3rd=%3 4th=%4 EOF $ cat < |
-l オプションではラベルを指定することができます。
ラベルが指定された場合、テンプレート中でラベルの間に書かれたキーワードのみが置換の対象となります。なおラベルはテンプレート中で2回 (開始と終了) しか書くことができません。
(-h による階層構造に関しては説明が複雑なのでここでは省略します)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#!/bin/sh if [ "$1" = "-l" ] then awk 'BEGIN{ # テンプレート読み込み while(getline < "'$2'" > 0) template[++l] = $0 } { # データ処理 l = 1 str = template[l] for(i=1; i<=NF; i++){ # _ をスペースに変換 gsub("\([^\\\\]\)_", "\1 ", $i) gsub("\\\\_", "_", $i) while(1){ if(index(str, "%" i)){ # テンプレートに置換ラベルがある場合は置換して次のデータに gsub("%" i, $i, str) break } else { # テンプレートに置換ラベルがない場合は置換済みのテンプレート出力 printf("%s\n", str) str = template[++l] } } } # テンプレート出力 printf("%s\n", str) }' $3 else awk 'BEGIN{ # データ読み込み while(getline < "'$2'" > 0) for(i=1; i<=NF; i++){ data[++cnt] = $i # _ をスペースに変換 gsub("\([^\\\\]\)_", "\1 ", data[cnt]) gsub("\\\\_", "_", data[cnt]) } } { # テンプレート処理 for(i=1; i<=NF; i++){ str = $i # テンプレートに置換ラベルがある間ループ while(match(str, /%[0-9]*/)){ # 置換 n = substr(str, RSTART+1, RLENGTH-1) gsub("%" n, data[n], str) } printf("%s ", str) } printf("\n") }' $1 fi |
基本的な機能は全て awk(1) の組み込み関数で実装しました。
通常モードでは、 awk(1) の BEGIN 処理でデータを全て読み込み配列 data に格納した後で _ の置換処理を行っています。
メイン処理ではテンプレートファイルを読み込み、 match 関数を利用して %数字 のラベルを探して gsub 関数で置換しています。
行モードでは、 BEGIN 処理でテンプレートを全て読み込みハッシュ template に格納しています。
メイン処理では、データを読み込み index 関数を利用して %数字 のラベルが存在する間テンプレートを置換します。
テンプレートに数字のラベルが見付からない場合は、テンプレートの次の行をハッシュ template から取得して処理を続けます。
今回紹介した Tukubai コマンドの中では、これが一番多機能なコマンドでした。非常に複雑で多機能なので、通常モードと行モードの基本的なテンプレート置換機能しか実装しておらず、-l オプションのラベル指定、-h オプションによる階層構造テンプレートなどは実装していません。
入力データ(ファイルや標準出力)の指定されたフィールドのみを出力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ cat << EOF | self 4 5 2 01 埼玉県 01 さいたま市 RomaSaitama 01 埼玉県 02 川越市 RomaKawagoe 01 埼玉県 03 熊谷市 RomaKumagaya 02 東京都 04 新宿区 RomaShinjuku 02 東京都 06 港区 RomaMinato 04 神奈川県 13 横浜市 RomaYokohama EOF さいたま市 RomaSaitama 埼玉県 川越市 RomaKawagoe 埼玉県 熊谷市 RomaKumagaya 埼玉県 新宿区 RomaShinjuku 東京都 港区 RomaMinato 東京都 横浜市 RomaYokohama 神奈川県 |
フィールドの指定時のオプションで、開始文字と文字列長が指定できます。
例えば 4.3 とした場合は第 4 フィールドの 3 文字目から出力され、5.5.4 とした場合は第 5 フィールドの 5 文字目から 2 文字が出力されます。
マルチバイトの文字の場合、開始文字と文字列長はそれぞれ 1 文字を 2 として計算する必要があり、
マルチバイト文字を文字の中間で分割する様な指定された場合はエラーとなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ cat << EOF | self 4.3 5.5.4 2 01 埼玉県 01 さいたま市 RomaSaitama 01 埼玉県 02 川越市 RomaKawagoe 01 埼玉県 03 熊谷市 RomaKumagaya 02 東京都 04 新宿区 RomaShinjuku 02 東京都 06 港区 RomaMinato 04 神奈川県 13 横浜市 RomaYokohama EOF いたま市 Sait 埼玉県 越市 Kawa 埼玉県 谷市 Kuma 埼玉県 宿区 Shin 東京都 区 Mina 東京都 浜市 Yoko 神奈川県 |
フィールド指定の 0 は全フィールドを表します。
フィールド指定で n/m (n と m はそれぞれ整数) とすることで n 番目から m 番目のフィールドを出力できます。
フィールド指定の NF は現在行のフィールド数に置き換えられ NF-3 (最後から3番目のフィールド) や 2/NF などの利用が可能です。
-d オプションは引数で指定した文字列を区切ることができます。
1 2 |
$ self -d 1.2.3 "123456789" 234 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/bin/sh awk -v "args=$*" ' BEGIN{ # フィールド指定を配列に格納 num = split(args, field) } { # 指定されたフィールド数だけループ for(i=1; i<=num; i++){ # フィールド指定を取得 f = field[i] # NF をフィールド数に置換 sub(/NF/, NF, f) # NF-n 形式 if(match(f, /-/)){ split(f, e, "-") printf("%s ", $(e[1] - e[2])) } # m/n 形式 else if(match(f, /\//)){ n = split(f, e, "/") for(j=e[1]; j<=e[2]; j++) printf("%s ", $j) } # m.n.p 形式 else if(match(f, /\..*\./)){ split(f, e, ".") printf("%s ", substr($e[1], e[2], e[3])) } # m.n 形式 else if(match(f, /\./)){ split(f, e, ".") printf("%s ", substr($e[1], e[2])) } else printf("%s ", $f) } printf("\n") }' |
self コマンド自体が awk(1) と似ているので、多少冗長になってしまいましたが awk(1) の機能だけで殆ど実装できました。フィールド指定、NF による相対的なフィールド指定、NF からの相対位置による指定、n/m 指定などが動作しています。
ただし OS X 標準の awk(1) はマルチバイト文字には対応していないのでマルチバイト文字を扱うことができません。
Linux で標準的に利用されている GNU awk の場合はマルチバイト文字に対応していますので、今回作成した self コマンドも正しく動作しています。
(次ページに続く)