3

I have an array of timestamps...

arr[0]="04-Feb-2021-21-05-48"
arr[1]="18-Jan-2021-12-19-48"
arr[2]="25-Jan-2021-00-24-29"
arr[3]="26-Jan-2021-00-28-33"
arr[4]="04-Feb-2021-21-06-49"
arr[5]="18-Jan-2021-12-19-48"
arr[6]="25-Jan-2021-00-24-29"
arr[7]="26-Jan-2021-00-28-33"

I want to sort this array and using the following command it sorts the timestamps into a new array..

IFS=$'\n' sorted=($(sort -r -t- -k2.1,2.3M <<<"${arr[*]}")); unset IFS
printf "[%s]\n" "${sorted[@]}"

I get this output but it is not what I want...

[26-Jan-2021-00-28-33]
[26-Jan-2021-00-28-33]
[25-Jan-2021-00-24-29]
[25-Jan-2021-00-24-29]
[18-Jan-2021-12-19-48]
[18-Jan-2021-12-19-48]
[04-Feb-2021-21-06-49]
[04-Feb-2021-21-05-48]

Instead I want the timestamps to be sorted in descending order.
So how do I get this result?

[04-Feb-2021-21-06-49]
[04-Feb-2021-21-05-48]
[26-Jan-2021-00-28-33]
[26-Jan-2021-00-28-33]
[25-Jan-2021-00-24-29]
[25-Jan-2021-00-24-29]
[18-Jan-2021-12-19-48]
[18-Jan-2021-12-19-48]

I tried these versions of the sort command, none worked for me...

IFS=$'\n' sorted=($(sort -r -t- -k3.1,3.4 -k2.1,2.3M <<<"${arr[*]}")); unset IFS
IFS=$'\n' sorted=($(sort -r -t- -k2.1,2.3M <<<"${arr[*]}")); unset IFS
IFS=$'\n' sorted=($(sort -t- -k2.1,2.3M <<<"${arr[*]}")); unset IFS

Update

I updated my question to clarify that I want to sort the timestamps in descending order and not just to sort the array of timestamps by the month field.

Cheers.

2
  • 1
    IFS=$'\n' sorted=($(sort -t- -k3,3nr -k2,2Mr -n -r <<<"${arr[*]}"))
    – altblue
    Feb 5, 2021 at 1:48
  • I like your answer that shows, as per the sort command man page that the character position in a field starts at 1 by default and the stop position defaults to the line's end. So a start and end position can be omitted in my case. I didn't think to try -n for numerical sort either. This sort command also works for me: sort -r -t- -k3,3r -k2,2Mr Feb 5, 2021 at 22:10

6 Answers 6

8

To apply the "reverse" only on the given column, specify it after the -k:

sort -t- -k2.1,2.3Mr
#                  ~
6
  • Thank you but I don't want to apply the reverse only on a given column. I want to sort the list of dates in descending order for my given timestamp format. If I follow your advice then 18-Jan comes third in the list instead of 26-Jan. Do I need to specify a -k for each field and the r in each field to achieve a proper descending list of timestamps? Feb 4, 2021 at 23:33
  • This command seems to sort my timestamps in descending order... sort -r -t- -k3.1,3.4r -k2.1,2.3Mr -k1.1,1.2r -k4.1,4.2 -k5.1,5.2 -k6.1,6.2 I don't really understand it but it seems to work. My case is a bit complicated because I don't want each field to independently reverse sort I need the time portions to be related to the date portions but I would prefer not to change my field separators. Feb 4, 2021 at 23:45
  • 3
    @FlexMcMurphy, GNU sort with --debug said that "sort: option '-r' only applies to last-resort comparison", so it looks like you may need to explicitly mention reversing for each key field. Though are you sure you need to give the character positions and that sort -k3,3r -k2,2Mr -k1,1r -k4 doesn't do? (that last one without the end position on purpose.)
    – ilkkachu
    Feb 5, 2021 at 0:23
  • The output of this command is not exactly what OP wants, since the H:M:S are not reversed. Feb 5, 2021 at 0:28
  • 1
    @Flex sort -t- -k3,3r -k2,2Mr -k1,1r -k4r ? Feb 5, 2021 at 0:33
7

Use the reverse -r in the key you want reversed, but try this to get the output you want. Instead of playing with complicated sort's, let's complicate it by turning the strings into appropiate date format, then sort, then go back to the original format:

printf "%s\n" "${sorted[@]}" | \
  awk -F'-' '{ print $1" " tolower($2)" "$3" "$4":"$5":"$6 }' | \ # 04 feb 2021 21:05:48
  xargs -I {} date -d {} +"%Y-%m-%d %H:%M:%S" | \ # 2021-02-04 21:05:48
  sort -k1r | \
  xargs -I {} date -d {} +"[%d-%b-%Y-%H-%M-%S]"

Output:

[04-Feb-2021-21-06-49]
[04-Feb-2021-21-05-48]
[26-Jan-2021-00-28-33]
[26-Jan-2021-00-28-33]
[25-Jan-2021-00-24-29]
[25-Jan-2021-00-24-29]
[18-Jan-2021-12-19-48]
[18-Jan-2021-12-19-48]
5

Some perl:

printf '%s\n' "${arr[@]}" \
| perl -MTime::Piece -lne '
    $t = Time::Piece->strptime($_, "%d-%b-%Y-%H-%M-%S");
    push @dates, [$t, $_];
    END {print for map {$_->[1]} sort {$b->[0] <=> $a->[0]} @dates}
  '
1

It seems that specifying the M modifier in a keydef resets the r modifier for that keydef, so you need to specify Mr for it instead. This isn't very obvious in the manual, so not surprising that you ran into this problem.

+ sort -r -t- -k2.1,2.3Mr
[04-Feb-2021-21-06-49]
[04-Feb-2021-21-05-48]
[26-Jan-2021-00-28-33]
[26-Jan-2021-00-28-33]
[25-Jan-2021-00-24-29]
[25-Jan-2021-00-24-29]
[18-Jan-2021-12-19-48]
[18-Jan-2021-12-19-48]
1

As pointed out by @ilkkachu the start and stop position of each key is the first and end position by default so they can be omitted in my case so -k3,3rinstead of -k3.1,3.4r.

@choroba's solution sort -t- -k2.1,2.3Mrwas the most helpful to me in solving my problem. Indeed when sorting on a Month field it seems necessary to include the -r within the key... so sort -t- -k2,2Mr but not sort -r -t- -k2,2M

After I clarified that I needed to sort the timestamps in descending order and not just on the month field @altblue produced I think, the best overall answer:

sort -t- -k3,3nr -k2,2Mr -n -r

Except I don't think the -r is needed in the key for the year -k3,3nr. As mentioned by @Toby Speight it seems the -r is needed in the key for the month -k2,2Mr because specifying the M modifier in a keydef resets the r modifier for that keydef. Therefore, to sort the timestamps in descending order you need to specify the -r "globally" and also Mr for the Month keydef.

After considering all the helpful answers and doing some more testing I found it is not necessary in my case to specify a numerical sort and it is indeed enough to sort on the year -k3,3 and month -k2,2Mr only:

sort -r -t- -k3,3 -k2,2Mr
1
  • 1
    Excellent summary of all the bits from the other answers! Feb 6, 2021 at 13:14
0
sort -t = -k 2.9nr -k 2.5Mr -k 2.2nr -k 2.14,2.21r

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.