How to find files in subdirs and sort them by filename in a single command

filenamesfindsort

Result of a normal find using find . ! -path "./build*" -name "*.txt":

./tool/001-sub.txt
./tool/000-main.txt
./zo/001-int.txt
./zo/id/002-and.txt
./as/002-mod.txt

and when sorted with sort -n:

./as/002-mod.txt
./tool/000-main.txt
./tool/001-sub.txt
./zo/001-int.txt
./zo/id/002-and.txt

however the desired output is:

./tool/000-main.txt
./zo/001-int.txt
./tool/001-sub.txt
./zo/id/002-and.txt
./as/002-mod.txt

which means output is sorted based on filename only, but folder information should be maintained as part of the output.

Edit: Make example more complicated as the subdirectory structure may include more than one level.

Best Answer

You need to sort by the last field (considering / as a field separator). Unfortunately, I can't think of a tool that can do this when the number of fields varies (if only sort -k could take negative values).

To get around this, you'll have to do a decorate-sort-undecorate. That is, take the filename and put it at the beginning followed by a field separator, then do a sort, then remove the first column and field separator.

find . ! -path "./build*" -name "*.txt" |\
    awk -vFS=/ -vOFS=/ '{ print $NF,$0 }' |\
    sort -n -t / |\
    cut -f2- -d/

That awk command says the field separator FS is set to /; this affects the way it reads fields. The output field separator OFS is also set to /; this affects the way it prints records. The next statement says print the last column (NF is the number of fields in the record, so it also happens to be the index of the last field) as well as the whole record ($0 is the whole record); it will print them with the OFS between them. Then the list is sorted, treating / as the field separator - since we have the filename first in the record, it will sort by that. Then the cut prints only fields 2 through the end, again treating / as the field separator.

Related Question