シェルの標準出力を表っぽく表示する

こんにちは!奥村です!

突然の私事ではございますが、最近シェルにはまっています。
以前にはこのような記事を書かせて頂きました。

blog.engineer.adways.net

本日もシェル関係の記事を書いていきたいと思います。

本題

MySQLでSELECTをすると、周りに線がついてカッコよく表示されますよね。 こんな感じで

mysql> select user, host from user;
+---------------+----------------------------+
| user          | host                       |
+---------------+----------------------------+
| mysql.session | localhost                  |
| mysql.sys     | localhost                  |
| root          | localhost                  |
+---------------+----------------------------+

かっこいいですし、結構見やすいと思います。
この出力にフィルタを使ってどうにかこうにかしようと思わないですよね?(少なくとも僕は思わないです)
そもそも、データベース側で抽出するデータを絞れるのだから、フィルタとかする必要ないですよね。

何が言いたいかと言うと、
データを絞って、最終的に見る出力は、再利用性が低くても見やすかったらいいんじゃないか
ということです。

っていうのは後乗せサクサクで、ただ単純にMySQLの表みたいに表示したいと思い、シェルスクリプトを書いてみました。

スクリプト

  • table.sh
if [ -p /dev/stdin ] ; then
    STDIN=$(cat -)
    COLUMS=$(echo "$STDIN"| awk '{ print NF }' | sort -nr | head -n 1)
    LENGTH_ARRAY=""
    RESULT=""
    mkfifo table_pipe
    for i in `seq $COLUMS`
    do
        COLUM_VALUES=$(echo "$STDIN" | tr -s ' ' | cut -d ' ' -f $i)
        MAX_WORD_LENGTH=$(echo "$STDIN" | tr -s ' ' | cut -d ' ' -f $i | awk '{ print length }' | sort -nr | head -n 1)
        LENGTH_ARRAY="$LENGTH_ARRAY $MAX_WORD_LENGTH"
        COLUM_LINES=""
        for j in $COLUM_VALUES
        do
            CURRENT_LENGTH=$(echo $j | awk '{ print length }')
            COLUM_RAW=`eval $(echo "printf \" %-"$MAX_WORD_LENGTH"s\\\n\" $j")`
            COLUM_LINES="$COLUM_LINES\\n$COLUM_RAW"
        done
#        RESULT=`paste -d '|' <(echo -e "$RESULT") <(echo -e "$COLUM_TEXT")`
        echo -e "$RESULT" > table_pipe &
        RESULT=`echo -e "$COLUM_LINES" | paste -d '|' table_pipe -`
    done
    echo -e "$RESULT" > table_pipe &
    FORMAT_RESULT=`echo "$STDIN" | sed 's/.*/ /' | paste -d '|' table_pipe -`
    LENGTH_ARRAY=$(echo "$LENGTH_ARRAY" | sed 's/^\s//')
    FLAME=''
    for w in `seq $COLUMS`
    do
       NUM=$(echo "$LENGTH_ARRAY" | cut -d ' ' -f $w)
       FLAME=`echo "$FLAME"$(printf "+-")`
       FLAME=`echo "$FLAME"$(eval $(echo "printf \"%"$NUM"s\\\n\"") | tr ' ' '-')`
    done
    FLAME="$FLAME+"
    echo "$FLAME"
    echo "$FORMAT_RESULT" | tail -n +2
    echo "$FLAME"
    rm table_pipe
else
    echo "need STDIN"
fi

変数名つけるのはうまくないので、多めに見下さい。

本当はワンライナーでやろうと思っていましたが、結構序盤の内に諦めました。
そして思ったより長くなってしまいました。恐らくもっと効率の良い方法はあるかと思います。

パイプでつないで値を作れるの良いですね

ちょっとこだわったところ

  • mkfifoを使った。

これを作っている過程で知ったコマンドなんですが、

mkfifo <name>

とすることで、名前付きパイプを作ることができるコマンドです。

mkfifo

$ mkfifo test_pipe
$ ls -la | grep test_pipe
prw-rw-r-- 1 vagrant vagrant    0 Feb 23 11:55 test_pipe

パーミッションの欄を見る通り、種類がpの物が作成されます。

この名前付パイプに出力をリダイレクトすると、別のところで、その値を使えるというものです。

        echo -e "$RESULT" > table_pipe &
        RESULT=`echo -e "$COLUM_LINES" | paste -d '|' table_pipe -`

この部分で使っています。

Bashで実行する場合は、

 RESULT=`paste -d '|' <(echo -e "$RESULT") <(echo -e "$COLUM_TEXT")`

上記2行の分をリダイレクトを使ってpasteに渡すことも可能ですが、もしかすると、Bashが入っていない環境で動かすことがあるかもしれませんので、
mkfifoを使ってみました。

別のアプローチとしては、

  • 一行ずつ行を加工していって、最後に上から並べる

のような方法も思いついたのですが、今回は列の大きさを使いまわしたかったので、列ごとに処理し、最後にpasteするようにしてみました。

実行

それではどんな感じに実行されるか見てましょう。

[vagrant@localhost cool_table]$ ls -la | tail -n +2 | sh table.sh
+-----------+--+--------+--------+-----+----+---+------+----------+
| drwxrwxr-x| 3| vagrant| vagrant| 67  | Feb| 23| 16:05| .        |
| drwxrwxr-x| 4| vagrant| vagrant| 36  | Feb| 23| 09:24| ..       |
| -rw-rw-r--| 1| vagrant| vagrant| 1470| Feb| 23| 16:05| table2.sh|
| -rw-rw-r--| 1| vagrant| vagrant| 1370| Feb| 23| 15:45| table.sh |
| drwxrwxr-x| 2| vagrant| vagrant| 38  | Feb| 23| 11:48| testdir  |
| prw-rw-r--| 1| vagrant| vagrant| 0   | Feb| 23| 11:55| test_pipe|
+-----------+--+--------+--------+-----+----+---+------+----------+

ここの

ls -la | tail -n +2

がデータ抽出部分になりますね! これをしなかった場合。

[vagrant@localhost cool_table]$ ls -la | sh table.sh
+-----------+--+--------+--------+-----+----+---+------+----------+
| total     | 8| vagrant| vagrant| 67  | Feb| 23| 16:05| .        |
| drwxrwxr-x| 3| vagrant| vagrant| 36  | Feb| 23| 09:24| ..       |
| drwxrwxr-x| 4| vagrant| vagrant| 1470| Feb| 23| 16:05| table2.sh|
| -rw-rw-r--| 1| vagrant| vagrant| 1370| Feb| 23| 15:45| table.sh |
| -rw-rw-r--| 1| vagrant| vagrant| 38  | Feb| 23| 11:48| testdir  |
| drwxrwxr-x| 2| vagrant| vagrant| 0   | Feb| 23| 11:55| test_pipe|
| prw-rw-r--| 1||||||||
+-----------+--+--------+--------+-----+----+---+------+----------+

なんとまぁ悲しい結果になります。
標準入力として与えるデータは、空白区切りの表形式になっているものを想定しています。

[vagrant@localhost cool_table]$ ps ux | grep systemd | sh table2.sh
+--------+------+----+----+-------+----+------+---+------+-----+-----+-------------+--------+
| vagrant| 28456| 0.0| 0.0| 112648| 948| pts/0| S+| 16:11| 0:00| grep| --color=auto| systemd|
+--------+------+----+----+-------+----+------+---+------+-----+-----+-------------+--------+

まぁ綺麗に表示できましたね。 ですが

[vagrant@localhost cool_table]$ ps aux | grep systemd | sh table2.sh
+--------+------+----+----+-------+-----+------+---+------+------+----------------------------------+-------------+-------------------+---------+------------+---------------------+
| root   | 1    | 0.0| 0.3| 188668| 3216| ?    | Ss| 2017 | 2:35 | /usr/lib/systemd/systemd         | --system    | --deserialize     | 21      | --nopidfile| --systemd-activation|
| root   | 485  | 0.0| 0.3| 34980 | 3508| ?    | Ss| 2017 | 1:01 | /usr/lib/systemd/systemd-journald| --system    | --address=systemd:| --nofork|||
| dbus   | 634  | 0.0| 0.1| 26888 | 1792| ?    | Ss| 2017 | 12:10| /bin/dbus-daemon                 | --color=auto| systemd           ||||
| root   | 641  | 0.0| 0.2| 26984 | 2080| ?    | Ss| 2017 | 1:00 | /usr/lib/systemd/systemd-logind  ||||||
| root   | 17940| 0.0| 0.1| 43120 | 1468| ?    | Ss| Jan17| 0:00 | /usr/lib/systemd/systemd-udevd   ||||||
| vagrant| 29880| 0.0| 0.0| 112652| 952 | pts/0| S+| 16:30| 0:00 | grep                             ||||||
+--------+------+----+----+-------+-----+------+---+------+------+----------------------------------+-------------+-------------------+---------+------------+---------------------+

値が入ってないセルは圧縮されてしまうようです。

まとめ

今回作ったtable.shls -la | tail -n +2 を表形式でみるためのツールとなってしまいました。

ですが、空白区切り表形式の物は綺麗に表示されるはずですので、ぜひ試してみて下さい。

最後までご覧頂きありがとうございました。