読者です 読者をやめる 読者になる 読者になる

プレフィックスが同じファイル毎に最新のファイルのみ抽出するワンライナー

次のような感じで、名前の一部分が同じファイルが複数グループあるような場合、グループごとに最新のファイルだけを抽出したい。

$ ls -lT
 total 0
 -rw-r--r--  1 user  staff  0  5 30 20:28:26 2014 foo.bar.001
 -rw-r--r--  1 user  staff  0  5 30 20:28:29 2014 foo.bar.002
 -rw-r--r--  1 user  staff  0  5 30 20:28:38 2014 foo.gao.001
 -rw-r--r--  1 user  staff  0  5 30 20:28:36 2014 foo.gao.002
 -rw-r--r--  1 user  staff  0  5 30 20:15:19 2014 hoge.003
 -rw-r--r--  1 user  staff  0  5 30 20:15:31 2014 hoge.006
 -rw-r--r--  1 user  staff  0  5 30 20:15:23 2014 hoge.piyo.001
 -rw-r--r--  1 user  staff  0  5 30 20:15:28 2014 hoge.piyo.002
 -rw-r--r--  1 user  staff  0  5 30 20:15:21 2014 hoge.piyo.004
 -rw-r--r--  1 user  staff  0  5 30 20:15:27 2014 hoge.piyo.005

抽出したいファイル

foo.bar.002
foo.gao.001
hoge.003
hoge.piyo.002

最後の"."より前の部分が同じファイル名のものを同グループとみなす。
"."の数が一定ではないあたりが悩ましいポイント。
ちなみにMacbashで挑戦。

プレフィックス部分が同じファイルでグループ化すればいいので、タイムスタンプがYYYYMMDDhhmmddみたいに出せれば、単純にファイル名でソートした後にタイムスタンプで並べ替えるだけでいい。
しかし、Macのlsがタイムスタンプの書式指定(--time-styleまたは-D)をサポートしていないようで、時間順での並べ替えに四苦八苦した結果、こんな感じになった。

$ ls -1t | \
> awk '{print NR, $NF, $NF}' | \
> perl -pe 's/\.(?=[^\.]+$)/ /g' | \
> LANG=C sort -k3,3 -k1,1 | \
> awk 'BEGIN { key="" } { if($3!=key) { print $2; } key=$3; }'

普通のsedだと正規表現の先読みが使えないので、ちょっとインチキしてperlを使ってしまった。
GNU sedなら-Pでperl互換表現を有効すれば先読みが使える(Macには入ってない)。
正規表現の部分(と次のsortのキー番号)を変えれば、ファイルの末尾や中間部分が一致するようなケースも同じ要領でいける。

最後の一行は、以前載せたgetfirstのまんま。

一行コマンドずつ実行した結果。

$ ls -1t
foo.gao.001
foo.gao.002
foo.bar.002
foo.bar.001
hoge.006
hoge.piyo.002
hoge.piyo.005
hoge.piyo.001
hoge.piyo.004
hoge.003
$ ls -1t | awk '{print NR, $NF, $NF}'
1 foo.gao.001 foo.gao.001
2 foo.gao.002 foo.gao.002
3 foo.bar.002 foo.bar.002
4 foo.bar.001 foo.bar.001
5 hoge.006 hoge.006
6 hoge.piyo.002 hoge.piyo.002
7 hoge.piyo.005 hoge.piyo.005
8 hoge.piyo.001 hoge.piyo.001
9 hoge.piyo.004 hoge.piyo.004
10 hoge.003 hoge.003
$ ls -1t | awk '{print NR, $NF, $NF}' | perl -pe 's/\.(?=[^\.]+$)/ /g'
1 foo.gao.001 foo.gao 001
2 foo.gao.002 foo.gao 002
3 foo.bar.002 foo.bar 002
4 foo.bar.001 foo.bar 001
5 hoge.006 hoge 006
6 hoge.piyo.002 hoge.piyo 002
7 hoge.piyo.005 hoge.piyo 005
8 hoge.piyo.001 hoge.piyo 001
9 hoge.piyo.004 hoge.piyo 004
10 hoge.003 hoge 003
$ ls -1t | awk '{print NR, $NF, $NF}' | perl -pe 's/\.(?=[^\.]+$)/ /g' | LANG=C sort -k3,3 -k1,1
3 foo.bar.002 foo.bar 002
4 foo.bar.001 foo.bar 001
1 foo.gao.001 foo.gao 001
2 foo.gao.002 foo.gao 002
10 hoge.003 hoge 003
5 hoge.006 hoge 006
6 hoge.piyo.002 hoge.piyo 002
7 hoge.piyo.005 hoge.piyo 005
8 hoge.piyo.001 hoge.piyo 001
9 hoge.piyo.004 hoge.piyo 004
$ ls -1t | awk '{print NR, $NF, $NF}' | perl -pe 's/\.(?=[^\.]+$)/ /g' | LANG=C sort -k3,3 -k1,1 | awk 'BEGIN { key="" } { if($3!=key) { print $2; } key=$3; }'
foo.bar.002
foo.gao.001
hoge.003
hoge.piyo.002