Sample Site

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 : }
  • 2-4行:標準入力 / ファイル 判別
  • 5行 :gawk引数 ARG1=「数値:0」か「文字列:ファイル名」
  • 7-8行:標準入力 / ファイル 自動選択
  • 9-16行:フィッシャー&イェーツのシャッフル実装部
  • 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