日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

sortコマンドでフィールド区切り文字を指定

先日のシェル芸勉強会福岡サテライトでちょっと話題に上がったのがsortコマンドの使い方。UNIXユーザーな方には定番の並べかえツールだが、オプションを使いこなすことでより便利な使い方が可能だ。

今回のネタも下記の本がヒントになっています。著者の上田さん、ありがとうございます。

www.kadokawa.co.jp gihyo.jp

sortコマンドでは、並べかえのキーにするフィールドを-kオプションで指定出来る。 フィールドの区切り文字はデフォルトで連続スペースなのだが、フィールドの区切り文字を-tオプションで別の文字に指定出来る。

使用例

今回の実行環境は、MacもしくはFreeBSDである。

10進数のIPアドレスの並べかえ

10進数のIPアドレスなどを並べ替える場合を考える。

$ cat ip_address 
192.168.1.2
192.168.2.1
192.168.1.10
192.168.1.1

オプションなしでは第4オクテットが 1 -> 10 -> 2 の順になってしまう。

$ cat ip_address | sort
192.168.1.1
192.168.1.10
192.168.1.2
192.168.2.1

そこで-tオプションで.を区切り文字にし、第3オクテットと第4オクテットの順で数値順に並べる。

$ cat ip_address | sort -t'.' -k3,3n -k4,4n
192.168.1.1
192.168.1.2
192.168.1.10
192.168.2.1

圧縮回転した過去ログの連結

FreeBSDで過去ログを時系列にzcatで連結したい場合があるが、この時もsortの-tオプションが有効。

過去のログを1日分毎に回転しながら圧縮保存したい場合がある。FreeBSDではnewsyslogというログ回転管理のツールを使う。newsyslogの設定で、毎日0時にログを圧縮回転、最大20個分まで保存することを考える。1日分の過去ログファイルは、毎日下記のように変更されていく。

maillog.0.gz -> maillog.1.gz -> maillog.2.gz ..... maillog.9.gz -> maillog.10.gz ... -> maillog.20.gz

過去ログファイルを現在のログファイルを時系列にzcatで連結するには、下記のようなコマンドになる。

$ zcat /var/log/maillog.20.gz /var/log/maillog.19.gz .... /var/log/maillog.0.gz | cat - /var/log/maillog

しかしシェル芸やシェルスクリプトで処理する場合、この順番で過去ログファイルを並べようとすると、意外な落とし穴がある。ファイル名の数字部分が桁が揃ってないのが原因で、数字順に並んでくれないのだ。

lsコマンドの場合。maillog.1.gzの次がmaillog.10.gzになってしまう。つまり辞書順。

$ ls -l /var/log/maillog.*.bz2
-rw-r-----  1 root  wheel    8098  2月 21 00:00 /var/log/maillog.0.bz2
-rw-r-----  1 root  wheel    8115  2月 20 00:00 /var/log/maillog.1.bz2
-rw-r-----  1 root  wheel    7923  2月 11 00:00 /var/log/maillog.10.bz2
-rw-r-----  1 root  wheel    7859  2月 10 00:00 /var/log/maillog.11.bz2
-rw-r-----  1 root  wheel    8296  2月  9 00:00 /var/log/maillog.12.bz2
-rw-r-----  1 root  wheel    7895  2月  8 00:00 /var/log/maillog.13.bz2
-rw-r-----  1 root  wheel    8438  2月  7 00:00 /var/log/maillog.14.bz2
-rw-r-----  1 root  wheel    7746  2月  6 00:00 /var/log/maillog.15.bz2
-rw-r-----  1 root  wheel    8298  2月  5 00:00 /var/log/maillog.16.bz2
-rw-r-----  1 root  wheel    7860  2月  4 00:00 /var/log/maillog.17.bz2
-rw-r-----  1 root  wheel    8365  2月  3 00:00 /var/log/maillog.18.bz2
-rw-r-----  1 root  wheel    9119  2月  2 00:00 /var/log/maillog.19.bz2
-rw-r-----  1 root  wheel    8055  2月 19 00:00 /var/log/maillog.2.bz2
-rw-r-----  1 root  wheel    7884  2月 18 00:00 /var/log/maillog.3.bz2
-rw-r-----  1 root  wheel    7712  2月 17 00:00 /var/log/maillog.4.bz2
-rw-r-----  1 root  wheel    8131  2月 16 00:00 /var/log/maillog.5.bz2
-rw-r-----  1 root  wheel    8338  2月 15 00:00 /var/log/maillog.6.bz2
-rw-r-----  1 root  wheel    7963  2月 14 00:00 /var/log/maillog.7.bz2
-rw-r-----  1 root  wheel    7726  2月 13 00:00 /var/log/maillog.8.bz2
-rw-r-----  1 root  wheel    8291  2月 12 00:00 /var/log/maillog.9.bz2

シェルのファイル名展開を使った場合でも同様。辞書順。

$ echo /var/log/maillog.*.bz2 | tr ' ' '\n'
/var/log/maillog.0.bz2
/var/log/maillog.1.bz2
/var/log/maillog.10.bz2
/var/log/maillog.11.bz2
/var/log/maillog.12.bz2
/var/log/maillog.13.bz2
/var/log/maillog.14.bz2
/var/log/maillog.15.bz2
/var/log/maillog.16.bz2
/var/log/maillog.17.bz2
/var/log/maillog.18.bz2
/var/log/maillog.19.bz2
/var/log/maillog.2.bz2
/var/log/maillog.20.bz2
/var/log/maillog.3.bz2
/var/log/maillog.4.bz2
/var/log/maillog.5.bz2
/var/log/maillog.6.bz2
/var/log/maillog.7.bz2
/var/log/maillog.8.bz2
/var/log/maillog.9.bz2

この場合どうするか?過去ログのファイル名には.で区切ると第二フィールドが数字になっていることを利用。数字の降にファイル名を並べる。

$ ls /var/log/maillog.*.bz2 | sort -t'.' -k2,2nr
/var/log/maillog.20.bz2
/var/log/maillog.19.bz2
/var/log/maillog.18.bz2
/var/log/maillog.17.bz2
/var/log/maillog.16.bz2
/var/log/maillog.15.bz2
/var/log/maillog.14.bz2
/var/log/maillog.13.bz2
/var/log/maillog.12.bz2
/var/log/maillog.11.bz2
/var/log/maillog.10.bz2
/var/log/maillog.9.bz2
/var/log/maillog.8.bz2
/var/log/maillog.7.bz2
/var/log/maillog.6.bz2
/var/log/maillog.5.bz2
/var/log/maillog.4.bz2
/var/log/maillog.3.bz2
/var/log/maillog.2.bz2
/var/log/maillog.1.bz2
/var/log/maillog.0.bz2

あとはxargsに渡してzcatで連結してやればいい。

$ ls /var/log/maillog.*.bz2 | sort -t'.' -k2,2nr | xargs zcat | cat - /var/log/maillog

過去14日分だけならtailコマンドなどを併用して下記のように。

$ ls /var/log/maillog.*.bz2 | sort -t'.' -k2,2nr | tail -n 14 | xargs zcat | cat - /var/log/maillog

補足

lsコマンドの-tオプション

ここまで書いておいて今更だが、lsコマンドにはファイル更新時間で並べ替える-tオプションがある。なのでこちらを使ってもいい。

$ ls -ltr /var/log/maillog.*.bz2
-rw-r-----  1 root  wheel  8691  2月  1 00:00 /var/log/maillog.20.bz2
-rw-r-----  1 root  wheel  9119  2月  2 00:00 /var/log/maillog.19.bz2
-rw-r-----  1 root  wheel  8365  2月  3 00:00 /var/log/maillog.18.bz2
-rw-r-----  1 root  wheel  7860  2月  4 00:00 /var/log/maillog.17.bz2
-rw-r-----  1 root  wheel  8298  2月  5 00:00 /var/log/maillog.16.bz2
-rw-r-----  1 root  wheel  7746  2月  6 00:00 /var/log/maillog.15.bz2
-rw-r-----  1 root  wheel  8438  2月  7 00:00 /var/log/maillog.14.bz2
-rw-r-----  1 root  wheel  7895  2月  8 00:00 /var/log/maillog.13.bz2
-rw-r-----  1 root  wheel  8296  2月  9 00:00 /var/log/maillog.12.bz2
-rw-r-----  1 root  wheel  7859  2月 10 00:00 /var/log/maillog.11.bz2
-rw-r-----  1 root  wheel  7923  2月 11 00:00 /var/log/maillog.10.bz2
-rw-r-----  1 root  wheel  8291  2月 12 00:00 /var/log/maillog.9.bz2
-rw-r-----  1 root  wheel  7726  2月 13 00:00 /var/log/maillog.8.bz2
-rw-r-----  1 root  wheel  7963  2月 14 00:00 /var/log/maillog.7.bz2
-rw-r-----  1 root  wheel  8338  2月 15 00:00 /var/log/maillog.6.bz2
-rw-r-----  1 root  wheel  8131  2月 16 00:00 /var/log/maillog.5.bz2
-rw-r-----  1 root  wheel  7712  2月 17 00:00 /var/log/maillog.4.bz2
-rw-r-----  1 root  wheel  7884  2月 18 00:00 /var/log/maillog.3.bz2
-rw-r-----  1 root  wheel  8055  2月 19 00:00 /var/log/maillog.2.bz2
-rw-r-----  1 root  wheel  8115  2月 20 00:00 /var/log/maillog.1.bz2
-rw-r-----  1 root  wheel  8098  2月 21 00:00 /var/log/maillog.0.bz2

しかしうっかりファイル更新時間を変えてしまうことも考えられるので、sortでファイル名並べかえの方がいい気がする。

lsコマンドで更新時刻を出力

下記の記事で書いたように、ファイル更新日付を固定長で桁を揃えて出力し、並べかえのキーとして使う手もある。

papiro.hatenablog.jp

GNU sortコマンドの-Vオプション

GNU版sortコマンド限定だが、バージョン名で並べ替える-Vオプションがある。ピリオド区切りの数字をバージョン名として認識して並べかえてくれるようだ。

$ cat version 
1.3
2.1
1.1.12
1.1.9
1.1
1.2

$ cat version | sort
1.1
1.1.12
1.1.9
1.2
1.3
2.1

$ cat version | gsort -V
1.1
1.1.9
1.1.12
1.2
1.3
2.1

これを使うと手っ取り早いかも。

$ cat ip_address | gsort -V
192.168.1.1
192.168.1.2
192.168.1.10
192.168.2.1

$ ls /var/log/maillog.*.bz2 | gsort -Vr
/var/log/maillog.20.bz2
/var/log/maillog.19.bz2
/var/log/maillog.18.bz2
/var/log/maillog.17.bz2
/var/log/maillog.16.bz2
/var/log/maillog.15.bz2
/var/log/maillog.14.bz2
/var/log/maillog.13.bz2
/var/log/maillog.12.bz2
/var/log/maillog.11.bz2
/var/log/maillog.10.bz2
/var/log/maillog.9.bz2
/var/log/maillog.8.bz2
/var/log/maillog.7.bz2
/var/log/maillog.6.bz2
/var/log/maillog.5.bz2
/var/log/maillog.4.bz2
/var/log/maillog.3.bz2
/var/log/maillog.2.bz2
/var/log/maillog.1.bz2
/var/log/maillog.0.bz2

上記の例以外でも、フィールド長が変わるSNMPのMIBオブジェクトの並べかえにも便利。