4

How can I take sort bash arguments alphabetically?

$ ./script.sh bbb aaa ddd ccc

and put it into an array such that I now have an array {aaa, bbb, ccc, ddd}

jaypal singh
  • 71,025
  • 22
  • 98
  • 142
ehaydenr
  • 240
  • 2
  • 13

4 Answers4

6

You can do:

A=( $(sort <(printf "%s\n" "$@")) )

printf "%s\n" "${A[@]}"
aaa
bbb
ccc
ddd

It is using steps:

  1. sort the arguments list i.e."$@"`
  2. store output of sort in an array
  3. Print the sorted array
anubhava
  • 713,503
  • 59
  • 514
  • 593
3

I hope following 2 lines will help.

sorted=$(printf '%s\n' "$@"|sort)

echo $sorted

This will give you a sorted cmdline args.I wonder though why its needed :)

But anyway it will sort your cmdlines

Removed whatever was not required.

Andriy Ivaneyko
  • 18,421
  • 4
  • 52
  • 73
PradyJord
  • 2,046
  • 11
  • 18
0

Here's an invocation that breaks all the other solutions proposed here:

./script.sh "foo bar" "*" "" $'baz\ncow'

Here's a piece of code that works correctly:

array=()
(( $# )) && while IFS= read -r -d '' var
do
  array+=("$var")
done <  <(printf "%s\0" "$@" | sort -z)
that other guy
  • 109,738
  • 11
  • 156
  • 185
  • I downvoted this because I don't know a lot of case where sorting long field with linebreak are really a need. In the other hand, I know a lot of cases where quick sort on small fields may be usefull. – F. Hauri May 10 '14 at 00:10
  • @F.Hauri It was a poorly written question with no mention of edge cases. I would have down voted the question. – jaypal singh May 10 '14 at 00:14
  • @F.Hauri As long as you're doing it for fair and logical reasons and not petty childishness, I'm fine with that. – that other guy May 10 '14 at 00:16
  • @jaypal I agreee, and I won't downvote this answer at all because there is a real effort to present a reliable way of working with *null terminated strings*, but SO won't let me correct my vote now... – F. Hauri May 10 '14 at 00:16
0

As there seem not appreciate my effort to reducing forks, there is a better solution than using IFS for parsing and setting a variable

Part 1: Very short and robust solution:

As suggested by @rici in a comment on another post, I add the -t argument to mapfile:

mapfile -t args < <(sort < <(printf "%s\n" "$@"))

This work with white space too.

Sample:

#!/bin/bash

mapfile args < <(sort < <(printf "%s\n" "$@"))

mapfile -t args < <(sort < <(printf "%s\n" "$@"))

declare -p args

for (( i=0 ; i<${#args[@]} ;i++));do

    printf "%3d: %s\n" $i "${args[i]%$'\n'}"

    printf "%3d: %s\n" $i "${args[i]}"

done

run sample:

/tmp/script ccc "a a" aaa ddd aa AA z aab
declare -a args='([0]="aa
" [1]="a a
" [2]="AA
" [3]="aaa
" [4]="aab
" [5]="ccc
" [6]="ddd
" [7]="z
")'
  0: aa
  1: a a
  2: AA
  3: aaa
  4: aab
  5: ccc
  6: ddd
  7: z

Part 2: Very quick: pure bash way (without forks!)

Nota: Of course, this is not the better, robust way of doing sort, but in many cases, this could efficiently be used.

As (at least) one guy seem prefer a to be sorted before aa, this is edited to replace z by 0.

This sample is limited to 1st 6 chars but you could replace 6 by bigger number but add same number of z.

#!/bin/bash

sep='§'
for i;do
    a=${i//[^a-zA-Z0-9]/0}000000
    args[36#${a:0:6}]+=${args[36#${a:0:6}]+$sep}${i}
  done

IFS=$sep args=(${args[*]})
printf "%s\n" ${args[@]}

declare -p args

For case sensitivity, you could replace 36# by 64#:

Working sample:

#!/bin/bash

sep=§
base=64
chars=8
fillc=0
usage() {
  cat <<eousage
Usage: $0 [-ai] [-p precision] [-s inner separator]
    -a     for sorting \`\`empty'' After (\`\`aa'' after \`\`aaa'')
    -i     for case Insensitive
    -p NUM tell the number of characters to compare (default: $chars)
    -s SEP let you precise inner separator, (default \`\`$sep'')
eousage
}

while getopts "iap:s:" opt;do case $opt in
    a ) fillc=z ;;
    i ) base=36 ;;
    p ) chars=$OPTARG ;;
    s ) sep=$OPTARG ;;
    * ) usage ; exit 1 ;;
  esac ; done ;
shift $[OPTIND-1]

printf -v cfill "%${chars}s"
cfill=${cfill// /$fillc}
for i;do
    a=${i//[^a-zA-Z0-9]/$fillc}$cfill
    idx=$[$base#${a:0:$chars}]
    args[$idx]+=${args[$idx]+$sep}${i}
  done

declare -p args
IFS=$sep args=(${args[*]})
declare -p args

for (( i=0 ; i++<${#args[@]} ;b));do
    printf "%3d: %s\n" $i ${args[i-1]}
  done

Run cases:

/tmp/script ccc aaa ddd aa AA z aab
declare -a args='([44667659878400]="aa" [44678397296640]="aaa" 
    [44679471038464]="aab" [53614076755968]="ccc" [58081916485632]="ddd" 
    [153931627888640]="z" [160803575562240]="AA")'
declare -a args='([0]="aa" [1]="aaa" [2]="aab" [3]="ccc" [4]="ddd" 
    [5]="z" [6]="AA")'
  1: aa
  2: aaa
  3: aab
  4: ccc
  5: ddd
  6: z
  7: AA

Case insensitive:

/tmp/script -i ccc aaa ddd aa AA z aab
declare -a args='([805409464320]="aa§AA" [806014126080]="aaa" 
    [806074592256]="aab" [967216951296]="ccc" [1047818363904]="ddd" 
    [2742745743360]="z")'
declare -a args='([0]="aa" [1]="AA" [2]="aaa" [3]="aab" [4]="ccc" 
    [5]="ddd" [6]="z")'
  1: aa
  2: AA
  3: aaa
  4: aab
  5: ccc
  6: ddd

Empty sorted after:

/tmp/script -ia ccc aaa ddd aa AA z aab
declare -a args='([806074592255]="aaa" [806135058431]="aab" 
    [807586246655]="aa§AA" [967277417471]="ccc" [1047878830079]="ddd" 
    [2821109907455]="z")'
declare -a args='([0]="aaa" [1]="aab" [2]="aa" [3]="AA" [4]="ccc" 
    [5]="ddd" [6]="z")'
  1: aaa
  2: aab
  3: aa
  4: AA
  5: ccc
  6: ddd
  7: z

precision: 1 chars:

/tmp/script -iap1 ccc aaa ddd aa AA z aab
declare -a args='([10]="aaa§aa§AA§aab" [12]="ccc" [13]="ddd" [35]="z")'
declare -a args='([0]="aaa" [1]="aa" [2]="AA" [3]="aab" [4]="ccc" 
    [5]="ddd" [6]="z")'
  1: aaa
  2: aa
  3: AA
  4: aab
  5: ccc
  6: ddd
  7: z

and precision: 10 chars:

/tmp/script -p 10 ccc aaa ddd aa AA z aab
declare -a args='([182958734861926400]="aa" [183002715327037440]="aaa" 
    [183007113373548544]="aab" [219603258392444928]="ccc" 
    [237903529925148672]="ddd" [630503947831869440]="z" 
    [658651445502935040]="AA")'
declare -a args='([0]="aa" [1]="aaa" [2]="aab" [3]="ccc" [4]="ddd" 
    [5]="z" [6]="AA")'
  1: aa
  2: aaa
  3: aab
  4: ccc
  5: ddd
  6: z
  7: AA

Whitespaces and other chars:

/tmp/script -is @ ccc "a a" aaa ddd 'a*a' 'a§a' aa AA z aab
declare -a args='([784246302720]="a a@a*a@a§a" [805409464320]="aa@AA" 
    [806014126080]="aaa" [806074592256]="aab" [967216951296]="ccc" 
    [1047818363904]="ddd" [2742745743360]="z")'
declare -a args='([0]="a a" [1]="a*a" [2]="a§a" [3]="aa" [4]="AA" 
    [5]="aaa" [6]="aab" [7]="ccc" [8]="ddd" [9]="z")'
  1: a a
  2: a*a
  3: a§a
  4: aa
  5: AA
  6: aaa
  7: aab
  8: ccc
  9: ddd
 10: z
F. Hauri
  • 58,205
  • 15
  • 105
  • 122
  • It sorts `abc` before `a` and drops empty elements. Wouldn't a bubble sort be easier and more accurate? – that other guy May 09 '14 at 22:58
  • Depending on *what you are doing (sorting)* this could be an interesting way of sorting, using pure bash! – F. Hauri May 09 '14 at 23:00
  • Why downvote this! .1 this answer perfectly to question, .2 this could be a lot more efficient than doing forks .3 my answer has two part, first is a very short and robust solution using `mapfile` wich is more indicated for ensuring read of whitespace and other characters. – F. Hauri May 09 '14 at 23:57
  • 1
    @F.Hauri Agree, this is a well written answer and down vote without a reason is uncalled for. +1 to offset the damage. – jaypal singh May 10 '14 at 00:00
  • @jaypal thanks (I hate to post *thanks answer* comments, but... ;-) – F. Hauri May 10 '14 at 00:05
  • I downvoted this because it was unnecessarily long and convoluted, and adds arbitrary limits like only sorting on the first N characters. You've since added another better answer, but it's just a copy of rici's comment from another post. – that other guy May 10 '14 at 00:06
  • @thatotherguy You're wrong: it's not **unnecessary** long, because it's one of the simplier way of doing this **without forks**. And no I did'nt *copy* this comment wich was an answer addressed to you (not me)... – F. Hauri May 10 '14 at 00:50