日々之迷歩

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

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

ファイルを再帰的に更新時刻で並べ替えるワンライナー

ファイルの更新時刻順に並べ替えたいことは結構あると思うのだが、フォルダを下って再帰的に行う場合はどうすればいいのだろうか?

まあWindowsのExplorerやMacのFinderだと、フォルダを全て開いて変更日の列をクリックとかすればいいかも。ただファイルの数が多いとフォルダを開くのは大変かも?ではFreeBSDやLinuxのCUIなシェルの場合はどうするか?

lsコマンドには-tオプションがあり、ファイルの更新時刻の新しい順で並べ替えてくれる。だが、ディレクトリを再帰的に下る-Rオプションと併用しても、同じディレクトリ内しか並べ替えてくれない。

findコマンドはディレクトリを再帰的に下っていくことができるが、ファイルの更新時刻順に並べ替える機能がない?(あったら教えていただけるとありがたや・・・)

んむむ、lsfindも片手落ちじゃないか・・・ということでどうすればいいか?

お題

下記のような状態のファイルとディレクトリ構成の場合で実験。

$ ls -Rl DIR
total 0
drwxr-xr-x+ 5 papiro  staff  170  9  2 00:22 dir1
drwxr-xr-x+ 4 papiro  staff  136  9  2 00:22 dir2
-rw-r--r--+ 1 papiro  staff    0  9  1 08:00 file1
-rw-r--r--+ 1 papiro  staff    0  9  2 00:16 file2
-rw-r--r--+ 1 papiro  staff    0 10 24  2014 file3

DIR/dir1:
total 0
-rw-r--r--+ 1 papiro  staff  0  9  2 00:20 file4
-rw-r--r--+ 1 papiro  staff  0 12 31  2014 file5
-rw-r--r--+ 1 papiro  staff  0 12 31  2013 file6

DIR/dir2:
total 0
-rw-r--r--+ 1 papiro  staff  0 12 31  2013 file7
-rw-r--r--+ 1 papiro  staff  0  1  1  2015 file8

最初の思いつき(だがダメな場合も)

最初に思いついたのは、下記のようにxargsを使ってfindlsの連携。これでいいじゃん?と思ったのだが・・・

$ find DIR -type f | xargs ls -tl
-rw-r--r--+ 1 papiro  staff  0  9  2 00:20 DIR/dir1/file4
-rw-r--r--+ 1 papiro  staff  0  9  2 00:16 DIR/file2
-rw-r--r--+ 1 papiro  staff  0  9  1 08:00 DIR/file1
-rw-r--r--+ 1 papiro  staff  0  1  1  2015 DIR/dir2/file8
-rw-r--r--+ 1 papiro  staff  0 12 31  2014 DIR/dir1/file5
-rw-r--r--+ 1 papiro  staff  0 10 24  2014 DIR/file3
-rw-r--r--+ 1 papiro  staff  0 12 31  2013 DIR/dir1/file6
-rw-r--r--+ 1 papiro  staff  0 12 31  2013 DIR/dir2/file7

これは、ディレクトリとファイルが少ない時だけ有効。ディレクトリとファイルが大量にあるとダメなのだ。

xargsの動きを思い出すと、シェルの引数の長さの制限いっぱい毎にコマンドを複数回実行している。仮にファイルの引数制限が1000個だとすると、下記のようなコマンドが実行されている。

ls -tl file0001 file0002 .... file1000
ls -tl file1001 file1002 .... file2000
ls -tl file2001 file2002 .... file3000

つまり1000個ずつで更新時間の並べ替えが行われており、全体としての並べ替えにはなっていない。

じゃあどげするの?

じゃあファイル更新時刻の文字列情報を使って、sortコマンドで並べ替えてやればいいかも?しかしlsコマンドの-lオプションで更新時刻を表示は意外と厄介。

$ ls -l DIR
total 0
drwxr-xr-x+ 5 papiro  staff  170  9  2 00:22 dir1
drwxr-xr-x+ 4 papiro  staff  136  9  2 00:22 dir2
-rw-r--r--+ 1 papiro  staff    0  9  1 08:00 file1
-rw-r--r--+ 1 papiro  staff    0  9  2 00:16 file2
-rw-r--r--+ 1 papiro  staff    0 10 24  2014 file3

8列目の表示が困ったちゃんで、更新時刻が1年より新しいと時分、古いと年を表示する。6列目と7列目も2桁表示してくれない。sortコマンドで扱いにくい。

ということで、更新時刻表示を固定長で表示する方法があればいいのでは?

FreeBSD

最近のFreeBSDでは、lsコマンドの-Dオプションで、更新時刻の出力フォーマットが指定できる。ということで下記のシェル芸でオケ。

$ find DIR -type f | xargs ls -lD '%Y%m%d%H%M%S' | sort -k6,6
-rw-r--r--  1 papiro  wheel    0 20131231235959 DIR/dir1/file6
-rw-r--r--  1 papiro  wheel    0 20131231235959 DIR/dir2/file7
-rw-r--r--  1 papiro  wheel    0 20141024080000 DIR/file3
-rw-r--r--  1 papiro  wheel    0 20141231235959 DIR/dir1/file5
-rw-r--r--  1 papiro  wheel    0 20150101000000 DIR/dir2/file8
-rw-r--r--  1 papiro  wheel    0 20150901080000 DIR/file1
-rw-r--r--  1 papiro  wheel    0 20150902001605 DIR/file2
-rw-r--r--  1 papiro  wheel    0 20150902002011 DIR/dir1/file4

Linux

LinuxというかGNUのlsコマンドだと--time-styleオプションを使うことでdateコマンドと同様なフォーマット指定が可能。ということで下記のシェル芸でオケ。

$ find DIR -type f | xargs ls -l --time-style='+%Y%m%d%H%M%S'  | sort -k6,6
-rw-r--r-- 1 papiro wheel 0 20131231235959 DIR/dir1/file6
-rw-r--r-- 1 papiro wheel 0 20131231235959 DIR/dir2/file7
-rw-r--r-- 1 papiro wheel 0 20141024080000 DIR/file3
-rw-r--r-- 1 papiro wheel 0 20141231235959 DIR/dir1/file5
-rw-r--r-- 1 papiro wheel 0 20150101000000 DIR/dir2/file8
-rw-r--r-- 1 papiro wheel 0 20150901080000 DIR/file1
-rw-r--r-- 1 papiro wheel 0 20150902001605 DIR/file2
-rw-r--r-- 1 papiro wheel 0 20150902002011 DIR/dir1/file4

また--full-timeオプションだと下記のような固定長で日付と時刻表示可能。ということで下記でもオケ。

$ find DIR -type f | xargs ls --full-time | sort -k6,7
-rw-r--r-- 1 papiro wheel 0 2013-12-31 23:59:59.000000000 +0900 DIR/dir1/file6
-rw-r--r-- 1 papiro wheel 0 2013-12-31 23:59:59.000000000 +0900 DIR/dir2/file7
-rw-r--r-- 1 papiro wheel 0 2014-10-24 08:00:00.000000000 +0900 DIR/file3
-rw-r--r-- 1 papiro wheel 0 2014-12-31 23:59:59.000000000 +0900 DIR/dir1/file5
-rw-r--r-- 1 papiro wheel 0 2015-01-01 00:00:00.000000000 +0900 DIR/dir2/file8
-rw-r--r-- 1 papiro wheel 0 2015-09-01 08:00:00.000000000 +0900 DIR/file1
-rw-r--r-- 1 papiro wheel 0 2015-09-02 00:16:05.000000000 +0900 DIR/file2
-rw-r--r-- 1 papiro wheel 0 2015-09-02 00:20:11.000000000 +0900 DIR/dir1/file4

Mac

MacのコマンドはBSD系だからFreeBSDと同じで出来るかも?と思ったが、lsコマンドに-Dオプションが使えなかった。Macに搭載されているlsコマンドはバージョンが古いためか?なのでhomebrewでGNU coreutilsを入れ、GNUのlsコマンドglsを使ってLinuxと同じシェル芸でオケ。

$ find DIR -type f | xargs gls -l --time-style='+%Y%m%d%H%M%S' | sort -k6,6
-rw-r--r-- 1 papiro wheel 0 20131231235959 DIR/dir1/file6
-rw-r--r-- 1 papiro wheel 0 20131231235959 DIR/dir2/file7
-rw-r--r-- 1 papiro wheel 0 20141024080000 DIR/file3
-rw-r--r-- 1 papiro wheel 0 20141231235959 DIR/dir1/file5
-rw-r--r-- 1 papiro wheel 0 20150101000000 DIR/dir2/file8
-rw-r--r-- 1 papiro wheel 0 20150901080000 DIR/file1
-rw-r--r-- 1 papiro wheel 0 20150902001605 DIR/file2
-rw-r--r-- 1 papiro wheel 0 20150902002011 DIR/dir1/file4

$ find DIR -type f | xargs gls --full-time | sort -k6,7
-rw-r--r-- 1 papiro wheel 0 2013-12-31 23:59:59.000000000 +0900 DIR/dir1/file6
-rw-r--r-- 1 papiro wheel 0 2013-12-31 23:59:59.000000000 +0900 DIR/dir2/file7
-rw-r--r-- 1 papiro wheel 0 2014-10-24 08:00:00.000000000 +0900 DIR/file3
-rw-r--r-- 1 papiro wheel 0 2014-12-31 23:59:59.000000000 +0900 DIR/dir1/file5
-rw-r--r-- 1 papiro wheel 0 2015-01-01 00:00:00.000000000 +0900 DIR/dir2/file8
-rw-r--r-- 1 papiro wheel 0 2015-09-01 08:00:00.000000000 +0900 DIR/file1
-rw-r--r-- 1 papiro wheel 0 2015-09-02 00:16:05.000000000 +0900 DIR/file2
-rw-r--r-- 1 papiro wheel 0 2015-09-02 00:20:11.000000000 +0900 DIR/dir1/file4