GAWK で linux コマンドを作ってみる
ここ数年、linux を使用することが多くなってきました。windows は仕事で使用しますが、家では CAD を触るときを除き、linux 一辺倒です。筆者は2000年から linux を使用しています。当時から比べると、GUI環境は安定し、周辺機器への対応も飛躍的に改善されました。いちいち手動でマウントしていた、あの頃が懐かしく思えます。
さて、中級のプログラマーにはおなじみ、コマンドの自作が今回のお題です。既に linux に備わっている機能でも、オリジナルでも構いませんが、アルゴリズムを考える訓練にはうってつけです。GAWK でつくりますので、あまり複雑なものはできませんが、実用性2割、訓練6割、やるせなさ2割でやっていきましょう。
shuffle
シャッフルを作ります。これは例えば、新品のトランプカード53枚を丁寧に執拗にシャッフルしたあと、1枚づつ最後までカードをめくると、良い具合にバラけているーというイメージです。有限重複なしのランダムデータが必要なときに使用します。
アルゴリズムはフィッシャー&イェーツのシャッフル(1938)を使用します。与えるファイルのデータが「ユニークである」ならば、当然ながら重複のないランダムなリストが得られます。...データを生成しているわけではないので割と高速です。筆者はデータベース等のテストデータ作成に利用しています。
.bashrc_shuffle
1 : function shuffle(){
2 : if [ -p /dev/stdin ]; then filename=0
3 : else filename=$1
4 : fi
5 : gawk -v ARG1=$filename 'BEGIN {
6 : srand(sprintf("%d%06d", systime(), PROCINFO["pid"]));
7 : if (ARG1) infile = ARG1;
8 : else infile = "/dev/stdin";
9 : while (getline < infile > 0) tbl[++cnt] = $0;
10 : for (; cnt > 0; cnt--) {
11 : rnd = int(rand() * cnt) + 1;
12 : tmp = tbl[cnt];
13 : tbl[cnt] = tbl[rnd];
14 : tbl[rnd] = tmp;
15 : }
16 : for (key in tbl) print tbl[key];
17 : }'
18 : }
shuffle()のテスト
$ seq 1024 8 2048 | column
1024 1128 1232 1336 1440 1544 1648 1752 1856 1960
1032 1136 1240 1344 1448 1552 1656 1760 1864 1968
1040 1144 1248 1352 1456 1560 1664 1768 1872 1976
1048 1152 1256 1360 1464 1568 1672 1776 1880 1984
1056 1160 1264 1368 1472 1576 1680 1784 1888 1992
1064 1168 1272 1376 1480 1584 1688 1792 1896 2000
1072 1176 1280 1384 1488 1592 1696 1800 1904 2008
1080 1184 1288 1392 1496 1600 1704 1808 1912 2016
1088 1192 1296 1400 1504 1608 1712 1816 1920 2024
1096 1200 1304 1408 1512 1616 1720 1824 1928 2032
1104 1208 1312 1416 1520 1624 1728 1832 1936 2040
1112 1216 1320 1424 1528 1632 1736 1840 1944 2048
1120 1224 1328 1432 1536 1640 1744 1848 1952
$ seq 1024 8 2048 | shuffle | column
1160 1464 1968 1112 1032 1200 1352 1240 1600 1344
1928 1448 1632 1488 1856 1944 1152 1336 1824 1408
1616 1456 1376 1568 1264 1936 1816 1088 1064 1672
1560 1664 1648 1480 1048 1056 1360 1760 1416 1120
2024 1024 1104 1136 1168 1696 1704 1656 1176 1496
1904 1328 1592 1728 1216 1528 1784 1280 1680 1440
2016 1792 1872 1768 1472 1888 1880 1976 1272 1640
1776 1424 1208 1072 1288 1544 1840 1128 1952 1256
1848 2040 1536 1576 1096 1384 1832 1720 1800 1040
1512 1752 1984 1400 1080 1584 2048 1184 1504 1320
1392 1552 1312 1864 1368 1192 1736 1912 1296 1712
2000 1896 1960 1304 1920 1744 1688 1232 1624 1608
1248 1224 1992 1520 1432 2032 1808 1144 2008
筆者の環境では、100万データのシャッフル / head 書き出しを 0.95 秒で完了しますが、本家 shuf は 0.08 秒と10倍強早いです。
本来、フィッシャー&イェーツのシャッフルアルゴリズムは、順番表を別途作成し、その順番(配列でいうインデックス)をランダムに入れ替えるのですが、AWK で実現するなら、確実に工程数が増え処理が遅れます。なので、順番(インデックス)はそのまま維持し、実体(配列でいう値)を入れ替えています。
prefix
データにプレフィックスとサフィックスを追記します。sed を使用するのが最適解かもしれないし、GAWKのワンライナーで書くというのもベターな気もしますが、あると便利なので作ります。
.bashrc_prefix
1 : function prefix(){
2 : if [ -p /dev/stdin ]; then
3 : filename=0
4 : else
5 : filename=${@:$#:1}
6 : fi
7 : if [ $# -gt 1 ]; then
8 : pref=$1
9 : suff=$2
10 : else
11 : pref=$1
12 : fi
13 : gawk -v ARG0=$filename -v ARG1=$pref -v ARG2=$suff 'BEGIN {
14 : if (ARG0) infile = ARG0;
15 : else infile = "/dev/stdin";
16 : if (ARG1 ~ /^seq,/) {
17 : st = substr(ARG1, 5) + 0;
18 : while (getline < infile > 0) printf("%6d %s\n", st++, ($0 ARG2));
19 : }
20 : else
21 : while (getline < infile > 0) print ARG1 $0 ARG2;
22 : }'
23 : }
prefix()のテスト
$ ls
Desktop Downloads PDF Public Videos awkf luaf
Documents Music Pictures Templates Warpinator lolipop nimf
$ ls | prefix '/home/mfi/' '/sample'
/home/mfi/Desktop/sample
/home/mfi/Documents/sample
/home/mfi/Downloads/sample
/home/mfi/Music/sample
/home/mfi/PDF/sample
/home/mfi/Pictures/sample
/home/mfi/Public/sample
/home/mfi/Templates/sample
/home/mfi/Videos/sample
/home/mfi/Warpinator/sample
/home/mfi/awkf/sample
/home/mfi/lolipop/sample
/home/mfi/luaf/sample
/home/mfi/nimf/sample
$ ls | prefix 'seq,1' '[help_file]'
1 Desktop[help_file]
2 Documents[help_file]
3 Downloads[help_file]
4 Music[help_file]
5 PDF[help_file]
6 Pictures[help_file]
7 Public[help_file]
8 Templates[help_file]
9 Videos[help_file]
10 Warpinator[help_file]
11 awkf[help_file]
12 lolipop[help_file]
13 luaf[help_file]
14 nimf[help_file]
cat -n / nl のように行番号を先頭に追記できるようにしてみました。
fm
前方一致検索によるデータの絞り込み(forward match)です。筆者の都合により uniq と sort が強制的にかかり、英字に関しては大文字小文字の区別を無視します。
.bashrc_fm
1 : function fm(){
2 : if [ -p /dev/stdin ]; then
3 : filename=0
4 : arg=$1
5 : else
6 : filename=${@:$#:1}
7 : if [ $# -gt 1 ]; then
8 : arg=$1
9 : else
10 : arg=""
11 : fi
12 : fi
13 : gawk -v ARG1=$filename -v ARG2=$arg 'BEGIN {
14 : if (ARG1) infile = ARG1;
15 : else infile = "/dev/stdin";
16 : while (getline < infile > 0) list[i = $0];
17 : PROCINFO["sorted_in"] = "@ind_str_asc";
18 : if (!ARG2) regex = ".";
19 : else regex = "^" ARG2;
20 : IGNORECASE = 1;
21 : for (i in list) if (i ~ regex) print i;
22 : }'
23 : }
fm()のテスト
$ env | fm xdg
XDG_CONFIG_DIRS=/etc/xdg/xdg-cinnamon:/etc/xdg
XDG_CURRENT_DESKTOP=X-Cinnamon
XDG_DATA_DIRS=/usr/share/cinnamon:/usr/share/gnome:/home/mfi/.local/share/flatpak/
exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share
XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/mfi
XDG_RUNTIME_DIR=/run/user/1000
XDG_SEAT=seat0
XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0
XDG_SESSION_CLASS=user
XDG_SESSION_DESKTOP=cinnamon
XDG_SESSION_ID=c1
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0
XDG_SESSION_TYPE=x11
XDG_VTNR=7