1

I want to sort a set of files with the same number of '-' delimiters in each filename (with -n for numeric sorting).

Here is an example list

/home/flora/edvart/docs/schimmel-01.png
/home/flora/edvart/docs/schimmel-02.png
/home/flora/edvart/docs/schimmel-03.png
/home/flora/edvart/docs/schimmel-04.png
/home/flora/edvart/docs/schimmel-05.png
/home/flora/edvart/docs/schimmel-06.png
/home/flora/edvart/docs/schimmel-07.png
/home/flora/edvart/docs/schimmel-08.png
/home/flora/edvart/docs/schimmel-09.png
/home/flora/edvart/docs/schimmel-10.png
/home/flora/edvart/docs/schimmel-11.png
/home/flora/edvart/docs/schimmel-12.png
/home/flora/edvart/docs/schimmel-13.png

Here is the code I am using

for fl in "$@"; do
   fnme=${fl##*/}
   ftyp=${fl##*.}

   fdir=${fl%/*}
   fnam=${fnme%.*}

   nf=$( echo "$fnam" | awk -F '-' '{print NF}')
   ifld=$(( nf + 1 ))

   find "$fdir" -type f -name "${fnam}-*.png" |
     awk -F'[-.]' '{print $(NF-1), $0}' RS='\0' ORS='\0' |
     sort -znt '-' -k "$ifld"n | 
   while IFS= read -r flimg
   do
     echo "$flimg"
   done
done

But when I try with the first three files being-1, -2, -3, the result is still not sorted numerically

/home/flora/edvart/docs/schimmel-04.png
/home/flora/edvart/docs/schimmel-05.png
/home/flora/edvart/docs/schimmel-06.png
/home/flora/edvart/docs/schimmel-07.png
/home/flora/edvart/docs/schimmel-08.png
/home/flora/edvart/docs/schimmel-09.png
/home/flora/edvart/docs/schimmel-10.png
/home/flora/edvart/docs/schimmel-11.png
/home/flora/edvart/docs/schimmel-12.png
/home/flora/edvart/docs/schimmel-13.png
/home/flora/edvart/docs/schimmel-1.png
/home/flora/edvart/docs/schimmel-2.png
/home/flora/edvart/docs/schimmel-3.png
6
  • The number of dashes are always the same, but could be different on a different set of filenames.
    – Vera
    Oct 16, 2021 at 22:09
  • find | sort -z won't work. Either use find -print0 or remove the -z. And if you're staying with the -z you need to read NULL-delimited lines in your while read with read -d ''. Although to be honest just replacing that loop entirely with tr '\0' '\n would be better Oct 17, 2021 at 8:01
  • I did find "$fdir" -type f -name "${fnam}-*.png" -print0 | sort -znt '-' -k "$ifld"n | and using while IFS= read -d '' flimg, but got the same output as before. I think the sort -k tries also to pick the .png part.
    – Vera
    Oct 17, 2021 at 8:23
  • Try a version sort (which is what GNU sort calls a natural sort) instead of a numeric sort. i.e. -V rather than -n. Also, as mentioned by @roaima, use -print0 if you're going to use sort -z. Given that you're working with arbitrary filenames, I would not recommend relying on newlines as the delimiter - NUL-separated is the right way to do it.
    – cas
    Oct 17, 2021 at 9:40
  • I have introdiuced -print0 now.
    – Vera
    Oct 17, 2021 at 16:09

3 Answers 3

2

The zsh shell has a shell option called NUMERIC_GLOB_SORT. It makes globbing patterns sort numerically by default.

From a non-zsh shell:

zsh -o NUMERIC_GLOB_SORT -c 'printf "%s\n" /home/flora/edvart/docs/*.png'

or, using the (n) glob qualifier to enable this shell option only for the given pattern,

zsh -c 'printf "%s\n" /home/flora/edvart/docs/*.png(n)'

Or, passing the pattern as an argument rather than hard-coding it in the zsh -c script. Note that the pattern needs to be quoted.

zsh -c 'printf "%s\n" ${~1}(n)' zsh '/home/flora/edvart/docs/*.png'

Testing:

$ ls -d *.png
schimmel-04.png   schimmel-08.png   schimmel-11.png   schimmel-3.png
schimmel-05.png   schimmel-09.png   schimmel-12.png
schimmel-06.png   schimmel-1.png    schimmel-13.png
schimmel-07.png   schimmel-10.png   schimmel-2.png
$ zsh -c 'printf "%s\n" ${~1}(n)' zsh '*.png'
schimmel-1.png
schimmel-2.png
schimmel-3.png
schimmel-04.png
schimmel-05.png
schimmel-06.png
schimmel-07.png
schimmel-08.png
schimmel-09.png
schimmel-10.png
schimmel-11.png
schimmel-12.png
schimmel-13.png

If you have further numbers in your directory path, this would still work given that the numbers in the directory path are the same.

1

Perhaps it would be easier to manipulate the file list with sed to capture the last field and add it at the beginning:

sed -e 's/^.*-\([^-]*\)\.[^.]*/\1-&/g'

Now you can sort based on the first field of the fake "filename", and after sorting, you can remove the extra data using cut skipping the first field.

cut -f2- -d-

So:

ls | sed | sort | cut

Example

I created this small directory and touched some zero-length files in it:

Oct 17 01:06 .
Oct 17 01:06 ..
Oct 17 01:06 201-ventosa-title-001.png
Oct 17 01:06 201-ventosa-title-002.png
Oct 17 01:06 201-ventosa-title-12.png
Oct 17 01:06 201-ventosa-title-17.png
Oct 17 01:06 201-ventosa-title-356.png
Oct 17 01:06 201-ventosa-title-91.png
Oct 17 01:06 333-ventosa-longer-title-this-time-77.jpg

ls | sed -e 's/^.*-\([^-]*\)\.[^.]*/\1-&/g' | sort -rn | cut -f2- -d-

gives, as expected,

201-ventosa-title-356.png
201-ventosa-title-91.png
333-ventosa-longer-title-this-time-77.jpg
201-ventosa-title-17.png
201-ventosa-title-12.png
201-ventosa-title-002.png
201-ventosa-title-001.png

(Removing the "r" option from sort reverses the order).

Note that the "77" element gets positioned correctly even if the number is in a different field.

5
  • My primary need in to handle normal numbering as well, when I have -1, -2, -3, ... -356 rather than the straightforward -001, -002, ..., -356 sequence.
    – Vera
    Oct 16, 2021 at 22:37
  • Then the -n option of sort is your friend (interprets fields numerically rather that lexicographically).
    – LSerni
    Oct 16, 2021 at 22:40
  • Have used sort -znt '-' -k "$ifld"n but still got the fie listing to start from the double digits.
    – Vera
    Oct 16, 2021 at 23:04
  • I must confess I am not familiar with all those options of sort. I can say that this solution works as required with sort -n. I have added the example.
    – LSerni
    Oct 16, 2021 at 23:14
  • -k defines the field to sort on, with delimiter defined by -t '-'.
    – Vera
    Oct 16, 2021 at 23:20
0

Problem originated because I was using mawk 1.3.3-17. Release 1.3.4 or later can use \0 as a record separator with the meaning "null character". Older releases of mawk, BSD awk, Busybox awk, Plan 9 awk etc. all treat the string \0 in RS as if RS had been the empty string, i.e. it enables "paragraph mode" (double newlines delimit records).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .