Daniel
Daniel

Reputation: 594

In bash, how to sort strings including numbers?

I have a script that outputs file paths (via find) :

/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3-something16
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something1
/foo/bar/example/foo/bar/example/foo-bar-example/1.2-something5
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something2
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something10

How can I list them in Bash so that they are in ascending numeric order based on the number at the end and regardless of the version ( 1.2, 1.2.3 or 1.2.3.4)

Ps : the something part can eventually contain a dash.

Desired output :

/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something1
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something2
/foo/bar/example/foo/bar/example/foo-bar-example/1.2-something5
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something10
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3-something16

I used a temporary sentinel character to delimit the number at the end but it's a little bit complicated in my case.

Upvotes: 2

Views: 1593

Answers (3)

fedorqui
fedorqui

Reputation: 289535

Extract the number at the end, prepend it to all the lines, sort numerically and finally delete that number:

sed -r 's/(.*)([^0-9])([0-9]+)$/\3\1\2\3/' file | sort -n | sed 's/^[0-9]*//'

With your input, this returns:

/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something1
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something2
/foo/bar/example/foo/bar/example/foo-bar-example/1.2-something5
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something10
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3-something16

I think this is the Schwartzian transform.


By pieces:

$ sed -r 's/(.*)([^0-9])([0-9]+)$/\3\1\2\3/' a
16/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3-something16
1/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something1
5/foo/bar/example/foo/bar/example/foo-bar-example/1.2-something5
2/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something2
10/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something10
#^
#note the numbers here

$ sed -r 's/(.*)([^0-9])([0-9]+)$/\3\1\2\3/' a | sort -n 
1/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something1
2/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something2
5/foo/bar/example/foo/bar/example/foo-bar-example/1.2-something5
10/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something10
16/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3-something16
                                                  # ^
                                                  # now it is ordered

$ sed -r 's/(.*)([^0-9])([0-9]+)$/\3\1\2\3/' a | sort -n | sed 's/^[0-9]*//'
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something1
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something2
/foo/bar/example/foo/bar/example/foo-bar-example/1.2-something5
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something10
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3-something16

Upvotes: 4

Zumo de Vidrio
Zumo de Vidrio

Reputation: 2091

If you can ensure that you will never have a '#' character in your file, you can try:

sed -e 's/something/#/g' filename.txt | sort -t# -k2 -n | sed -e 's/#/something/g'

Upvotes: 1

anubhava
anubhava

Reputation: 785008

You can use this sed + sort + awk:

sed -E 's/.*[^0-9]([0-9]+)$/\1 &/' file | sort -n | awk '{print $2}'

/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something1
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something2
/foo/bar/example/foo/bar/example/foo-bar-example/1.2-something5
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3.4-something10
/foo/bar/example/foo/bar/example/foo-bar-example/1.2.3-something16

Upvotes: 3

Related Questions