ディレクトリとファイルの整理をすることになって、ファイル名に基づいて連番を振るリネーム作業が必要になった。
2_0001.dat
、10_0001.dat
、10_0002.dat
だとか、10_aa.dat
、10_ab.dat
、といった感じのファイルをいい感じに並び替える必要があった。
見ての通り、ファイル名は複数の数値を含んでいたり、ゼロ埋めだったり、文字の連番('aa', 'ab)もあった。なので、これを考慮して並び替えたい。 (要はWindowsのファイル名の昇順と概ね同じ並びにしたい。)
GNU CoreUtils のsort
ではできないっぽいので、perlでこう書いた。
sub is_number { return $_[0] =~ /^\d+(?:\.\d+)?$/; } sub by_number{ my $pattern = qr/(\d+(?:\.\d+)?|\D+)/; my @a = ($a =~ /$pattern/g); my @b = ($b =~ /$pattern/g); for(my $i=0; $i<@a&&$i<@b; ++$i){ my $cmp = (is_number($a[$i]) && is_number($b[$i])) ? $a[$i] <=> $b[$i] : $a[$i] cmp $b[$i]; if($cmp){ return $cmp; } } return scalar(@a) <=> scalar(@b); } # 使用例 my @sorted_datalist = sort by_number @datalist;
で、クソコード書き終えてから気付いた。 数値は先頭ゼロを含めて同じ桁なら、文字比較でもうまくいくはずなので、「数値を0埋めして、後は文字比較する」でよい気がする。
sub by_number{ my $ta = $a; my $tb = $b; $ta =~ s/(\d+)/sprintf("%010d",$1)/eg; # 数値10桁を越えそうなら書き換える $tb =~ s/(\d+)/sprintf("%010d",$1)/eg; return $ta cmp $tb; }
で、これぐらいならワンライナーに落とせそうだったので落とした。入力は1行1ファイル名。
# `#`区切りで`ゼロ埋め文字列#元文字列`に変換して出力して、並び替えたら元の文字列の部分だけをして出す。 cat "target.txt" | perl -ne 'chomp; $t=$_; s/(\d+)/sprintf("%010d",$1)/eg; print "$_#$t\n"' | sort | cut -d '#' -f 2
もっと簡単な方法はありそう。