Edited at

optex であらゆる UNIX コマンドのオプションをカスタマイズする


Getopt::EX

greple コマンドからオプションハンドリングの部分を切り出して、Getopt::EX という Perl モジュールとしてリリースした。

これは、コマンドの実行前にオプションを処理して、新しいオプションや機能を実現するためのものだ。greple を本格的に使おうとすると、オプションの指定がかなり複雑になり、端末から入力するのは現実的ではない。シェルスクリプトにするという手はあるが、管理がしにくい。Getopt::EX を使うと、rc ファイルとモジュールという形でまとめることができる。


optex コマンド

optex は、この Getopt::EX の機能をすべてのコマンドで使えるようにしようというものだ。getoptex だと長いので optex にした。読み方には特にこだわっていないが、何となく自分では「オプテック」と呼んでいる。

シェルのエイリアス機能でも近いことはできるが、コマンドそのものを拡張したように見えるので、シェルスクリプトや他のコマンドから間接的に実行される場合にも有効になる点が決定的に違う。

使い方は、optex の引数としてコマンドを指定する方法と:

$ optex date

シンボリックリンクを使う方法がある:

$ ls -l ~/.optex.d/bin/date

~/.optex.d/bin/date -> ~/perl5/bin/optex*
$ date

リンクの置き場所はどこでも構わないが、デフォルトで ~/.optex.d/bin というディレクトリを想定して、リンクを管理する機能も用意してある。--ln オプションでリンクを作成し、--rm オプションで削除することができる。

$ optex --ln date

$ optex --ls -l date
date -> ~/perl5/bin/optex
$ optex --rm date
~/.optex.d/bin/date removed.
$ optex --ls -l date
date: not exist

設定ファイルは ~/.optex.d/ というディレクトリに置く。date コマンドであれば ~/.optex.d/date.rc だ。

設定ファイルの中では、次のようなことができる。


option

例えば macOS の date コマンドには -I[TIMESPEC] オプションが実装されていないが、次のような設定を ~/.optex.d/date.rc ファイルに入れておくことで実現できる。


~/.optex.d/date.rc

option -I        -Idate

option -Idate +%F
option -Iseconds +%FT%T%z
option -Iminutes +%FT%H:%M%z
option -Ihours +%FT%H%z

option --iso-8601 -I
option --iso-8601=date -Idate
option --iso-8601=seconds -Iseconds
option --iso-8601=minutes -Iminutes
option --iso-8601=hours -Ihours


こうすると、次のように実行できる。

% optex date -Iseconds

シンボリックリンクがあれば、普通のコマンドと同じように実行できる。

% date -Iseconds


define

複雑なオプションを定義するためにマクロを使うこともできる。ファイル ~/.optex.d/awk.rc に次の内容を入れておくと、awk で --vowels というオプションが使えるようになる。


~/.optex.d/awk.rc

define __delete__ /[bcdfgkmnpsrtvwyz]e( |$)/

define __match__ /ey|y[aeiou]*|[aeiou]+/
define __count_vowels__ <<EOS
{
s = tolower($0);
gsub(__delete__, " ", s);
for (count=0; match(s, __match__); count++) {
s=substr(s, RSTART + RLENGTH);
}
print count " " $0;
}
EOS
option --vowels __count_vowels__

これは、英単語の音節を調べようとしているもので、こんな風に使う。

% awk --vowels /usr/share/dict/words

これは、この記事で使ったスクリプトだ。

https://qiita.com/kaz-utashiro/items/9027eba0c1347d441bd9


expand

複雑なオプションを定義するためには expand を使う。expand は option と同じように使えるが、そのファイルの中でだけ有効になる。find コマンドのオプションは指定するのが面倒なので、こんな風にまとめておけると便利だ。

expand repository   ( -name .git -o -name .svn -o -name RCS )

expand no_dots ! -name .*
expand no_version ! -name *,v
expand no_backup ! -name *~
expand no_image ! -iname *.jpg ! -iname *.jpeg \
! -iname *.gif ! -iname *.png
expand no_archive ! -iname *.tar ! -iname *.tbz ! -iname *.tgz
expand no_pdf ! -iname *.pdf

option --clean \
repository -prune -o \
-type f \
no_dots \
no_version no_backup \
no_image \
no_archive \
no_pdf

% find . --clean -print


モジュール

optex はモジュールで拡張することができる。date コマンドの場合、モジュールは ~/.optex.d/date/ からロードする。ここに ~/.optex.d/date/default.pm というモジュールがあると自動的に実行される。

モジュールファイルには rc ファイルと同じ内容も書くことができて、この場合別に Perl のコードを書く必要はない。先の awk の例なら ~/.optex.d/awk/vowels.pm というファイルに以下の内容を入れておくと awk -Mvowels--vowels オプションが有効になる。最初の3行のおまじないは必要だが、その後は rc ファイルと同じだ。


~/.optex.d/awk/vowels.pm

package vowels;

1;
__DATA__

define __delete__ /[bcdfgkmnpsrtvwyz]e( |$)/
define __match__ /ey|y[aeiou]*|[aeiou]+/
define __count_vowels__ <<EOS
{
s = tolower($0);
gsub(__delete__, " ", s);
for (count=0; match(s, __match__); count++) {
s=substr(s, RSTART + RLENGTH);
}
print count " " $0;
}
EOS

option --vowels __count_vowels__
help --vowels count vowels


これは、普通の Perl モジュールなので、何でも書くことができる。

例えば、

package default;

$ENV{LANG} = 'C';
1;

こんな default モジュールを用意して置くと、date コマンドを実行する前に、環境変数 LANGC に設定する。なので、こんな風になる。

% /bin/date

2017年 10月22日 日曜日 18時00分00秒 JST

% date
Sun Oct 22 18:00:00 JST 2017

その他のモジュールは -M オプションで指定する。

~/.optex.d/es.pm というモジュールは -Mes オプションで指定する。

package es;

$ENV{LANG} = 'es_ES';
1;

% date -Mes
domingo, 22 de octubre de 2017, 18:00:00 JST


サンプル

サンプルのモジュールがいくつか同梱してある。


-Mutil::filter

util::filter モジュールは、入出力フィルタを集めたモジュールだ。


&timestamp

macOS の ping コマンドには --apple-time というオプションがあるのをご存知だろうか。これを指定すると、RTT だけではなく、パケットが到着した時刻を表示してくれる。

util::filter モジュールの timestamp 関数を使うと、コマンド出力の各行の先頭にタイムスタンプを挿入する。

ping コマンドに使うとこんな感じだ。

~/.optex.d/ping.rc には、こんな風に書いてある。


~/.optex.d/ping.rc

option --time -Mutil::filter --of &timestamp


util::filter モジュールをロードして、出力フィルタとして timestamp 関数を指定している。


--if

--if オプションにはコマンドを指定することができるので、こんな設定を ~/.optex.d/default.rc に置いておくと、すべてのコマンドの標準入力で圧縮データを入られるようになる。


~/.optex.d/default.rc

option --gz -Mutil::filter --if 'gunzip -c'


これだと圧縮ファイルは指定できないが、次の process substitution などと組み合わせれば実現可能だ。


--io-color

標準出力とエラー出力が混ざって見にくいと感じたことはないだろうか。util::filter モジュールで定義されている --io-color オプションを使うと、標準エラー出力の色を変えて出力してくれる。自分の ~/.optex.d/default.rc には次のように指定してあるので optex を通じて実行すると、すべてのコマンドのエラー出力が着色されるようになっている。


~/.optex.d/default.rc

autoload -Mutil::filter --io-color --set-io-color

option default --io-color

デフォルトでは 555/201;E という色を指定してあるので、気に入らなければこれを変えてもいいし --set-io-color オプションを使って好きな色を設定してもいい。色の指定方法については perldoc Getopt::EX::Colormap を参照して欲しい。ここで使っているのは RGB 6x6x6 216色の色指定で、555 は白で 000 が黒だ。E は実はマイブームの機能で、行削除のシークエンスを出力する。この時、背景色が指定されていると、その色で行末までを塗りつぶすので、上の画像のような結果になる。


util/filter.pm

option --set-io-color &io_color($<shift>)

option --io-color --set-io-color STDERR=555/201;E


-Mutil::argv

util::argv モジュールは、コマンド引数を何とかするモジュール。

たとえば、times 関数を使うと、引数を増やすことができる。

$ optex echo -Mutil::argv::times=count=3 a b c

a a a b b b c c c

まあ、こんなのはあまり役には立たない。

自分はずっと tcsh を使っていて、process substitution の機能を使いたい時だけ bash を使っていたのだが、最近ついに30年以上使い続けた tcsh からログインシェルを bash に移行した。

これも、util::argv::proc 関数を使うと実現できる。


インストール

とりあえず、

$ cpanm App::optex

でインストールできるはず。

cpanm がない場合は、cpanminus で検索してインストールしてください。



追記: コンフィグレーションファイル

~/.optex.d/config.toml で設定情報を指定できるようにした。


alias

今までのままでも、別の名前でリンクを作って設定ファイルを作れば alias 的な使い方はできたのだが、設定ファイルの中で指定できるようにした。次のように、文字列か配列で指定できる。


~/.optex.d/config.toml

[alias]

pgrep = [ "greple", "-Mperl", "--code" ]
hello = "echo -n 'hello world!'"


no-module

元々 Getopt::EX を使っているコマンドは -M オプションを解釈するので、optex から呼ぶと混乱する。そのため no-module と指定してあるコマンドについてはモジュールオプションを解釈しないようにした。


~/.optex.d/config.toml

no-module = [

"greple",
"pgrep",
]