Bash sort array according to length of elements?











up vote
9
down vote

favorite
2












Given an array of strings, I would like to sort the array according to the length of each element.



For example...



    array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)


Should sort to...



    "the longest string in the list"
"also a medium string"
"medium string"
"middle string"
"short string"
"tiny string"


(As a bonus, it would be nice if the list sorted strings of the same length, alphabetically. In the above example medium string was sorted before middle string even though they are the same length. But that's not a "hard" requirement, if it over complicates the solution).



It is OK if the array is sorted in-place (i.e. "array" is modified) or if a new sorted array is created.










share|improve this question







New contributor




PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
















  • 1




    some interesting answers over here, you should be able to adapt one to test for string length as well stackoverflow.com/a/30576368/2876682
    – frostschutz
    yesterday















up vote
9
down vote

favorite
2












Given an array of strings, I would like to sort the array according to the length of each element.



For example...



    array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)


Should sort to...



    "the longest string in the list"
"also a medium string"
"medium string"
"middle string"
"short string"
"tiny string"


(As a bonus, it would be nice if the list sorted strings of the same length, alphabetically. In the above example medium string was sorted before middle string even though they are the same length. But that's not a "hard" requirement, if it over complicates the solution).



It is OK if the array is sorted in-place (i.e. "array" is modified) or if a new sorted array is created.










share|improve this question







New contributor




PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
















  • 1




    some interesting answers over here, you should be able to adapt one to test for string length as well stackoverflow.com/a/30576368/2876682
    – frostschutz
    yesterday













up vote
9
down vote

favorite
2









up vote
9
down vote

favorite
2






2





Given an array of strings, I would like to sort the array according to the length of each element.



For example...



    array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)


Should sort to...



    "the longest string in the list"
"also a medium string"
"medium string"
"middle string"
"short string"
"tiny string"


(As a bonus, it would be nice if the list sorted strings of the same length, alphabetically. In the above example medium string was sorted before middle string even though they are the same length. But that's not a "hard" requirement, if it over complicates the solution).



It is OK if the array is sorted in-place (i.e. "array" is modified) or if a new sorted array is created.










share|improve this question







New contributor




PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











Given an array of strings, I would like to sort the array according to the length of each element.



For example...



    array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)


Should sort to...



    "the longest string in the list"
"also a medium string"
"medium string"
"middle string"
"short string"
"tiny string"


(As a bonus, it would be nice if the list sorted strings of the same length, alphabetically. In the above example medium string was sorted before middle string even though they are the same length. But that's not a "hard" requirement, if it over complicates the solution).



It is OK if the array is sorted in-place (i.e. "array" is modified) or if a new sorted array is created.







bash shell-script sort array






share|improve this question







New contributor




PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











share|improve this question







New contributor




PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









share|improve this question




share|improve this question






New contributor




PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









asked yesterday









PJ Singh

1543




1543




New contributor




PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





New contributor





PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






PJ Singh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








  • 1




    some interesting answers over here, you should be able to adapt one to test for string length as well stackoverflow.com/a/30576368/2876682
    – frostschutz
    yesterday














  • 1




    some interesting answers over here, you should be able to adapt one to test for string length as well stackoverflow.com/a/30576368/2876682
    – frostschutz
    yesterday








1




1




some interesting answers over here, you should be able to adapt one to test for string length as well stackoverflow.com/a/30576368/2876682
– frostschutz
yesterday




some interesting answers over here, you should be able to adapt one to test for string length as well stackoverflow.com/a/30576368/2876682
– frostschutz
yesterday










6 Answers
6






active

oldest

votes

















up vote
10
down vote



accepted










If the strings don't contain newlines, the following should work. It sorts the indices of the array by the length, using the strings themselves as the secondary sort criterion.



#!/bin/bash
array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)
expected=(
"the longest string in the list"
"also a medium string"
"medium string"
"middle string"
"short string"
"tiny string"
)

indexes=( $(
for i in "${!array[@]}" ; do
printf '%s %s %sn' $i "${#array[i]}" "${array[i]}"
done | sort -nrk2,2 -rk3 | cut -f1 -d' '
))

for i in "${indexes[@]}" ; do
sorted+=("${array[i]}")
done

diff <(echo "${expected[@]}")
<(echo "${sorted[@]}")


Note that moving to a real programming language can greatly simplify the solution, e.g. in Perl, you can just



sort { length $b <=> length $a or $a cmp $b } @array





share|improve this answer























  • In Python: sorted(array, key=lambda s: (len(s), s))
    – wjandrea
    yesterday










  • In Ruby: array.sort { |a| a.size }
    – Dmitry Kudriavtsev
    16 hours ago


















up vote
8
down vote













readarray -t array < <(
for str in "${array[@]}"; do
printf '%dt%sn' "${#str}" "$str"
done | sort -k 1,1nr -k 2 | cut -f 2- )


This reads the values of the sorted array from a process substitution.



The process substitution contains a loop. The loop output each element of the array prepended by the element's length and a tab character in-between.



The output of the loop is sorted numerically from largest to smallest (and alphabetically if the lengths are the same; use -k 2r in place of -k 2 to reverse the alphabetical order) and the result of that is sent to cut which deletes the column with the string lengths.



Sort test script followed by a test run:



array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)

readarray -t array < <(
for str in "${array[@]}"; do
printf '%dt%sn' "${#str}" "$str"
done | sort -k 1,1nr -k 2 | cut -f 2- )

printf '%sn' "${array[@]}"




$ bash script.sh
the longest string in the list
also a medium string
medium string
middle string
short string
tiny string


This assumes that the strings do not contain newlines. On GNU systems with a recent bash, you can support embedded newlines in the data by using the nul-character as the record separator instead of newline:



readarray -d '' -t array < <(
for str in "${array[@]}"; do
printf '%dt%s' "${#str}" "$str"
done | sort -z -k 1,1nr -k 2 | cut -z -f 2- )


Here, the data is printed with trailing in the loop instead of newlines, the sort and cut reads nul-delimited lines through their -z GNU options and readarray finally reads the nul-delimited data with -d ''.






share|improve this answer



















  • 3




    Note that -d '' is in fact -d '' as bash can't pass NUL characters to commands, even its builtins. But it does understand -d '' as meaning delimit on NUL. Note that you need bash 4.4+ for that.
    – Stéphane Chazelas
    yesterday










  • dat edit seems familiar, doesn't it?
    – Isaac
    yesterday










  • @StéphaneChazelas No, it is not '', it is $''. And yes, it converts (almost exactly) to ''. But that is a way to comunicate to other readers the actual intent of using a NUL delimiter.
    – Isaac
    yesterday










  • @Isaac Sorry, which edit?
    – Kusalananda
    yesterday






  • 1




    I should have said, and I diddn't, sorry: No, not at all, just that two .... etc. Having said that to ease my mind: I am still playing with it a little bit, hoping to expand solutions a little more forward. Maybe I will edit my answer (again), and thanks for confirming that the solution is useful as it stands (it looks similar to yours 😛 ).
    – Isaac
    20 hours ago


















up vote
4
down vote













I won't completely repeat what I've already said about sorting in bash, just you can sort within bash, but maybe you shouldn't. Below is a bash-only implementation of an insertion sort, which is O(n2), and so is only tolerable for small arrays. It sorts the array elements in-place by their length, in decreasing order. It does not do a secondary alphabetical sort.



array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)

function sort_inplace {
local i j tmp
for ((i=0; i <= ${#array[@]} - 2; i++))
do
for ((j=i + 1; j <= ${#array[@]} - 1; j++))
do
local ivalue jvalue
ivalue=${#array[i]}
jvalue=${#array[j]}
if [[ $ivalue < $jvalue ]]
then
tmp=${array[i]}
array[i]=${array[j]}
array[j]=$tmp
fi
done
done
}

echo Initial:
declare -p array

sort_inplace

echo Sorted:
declare -p array


As evidence that this is a specialized solution, consider the timings of the existing three answers on various size arrays:



# 6 elements
Choroba: 0m0.004s
Kusalananda: 0m0.004s
Jeff: 0m0.018s ## already 4 times slower!

# 1000 elements
Choroba: 0m0.004s
Kusalananda: 0m0.004s
Jeff: 0m0.021s ## up to 5 times slower, now!

5000 elements
Choroba: 0m0.004s
Kusalananda: 0m0.004s
Jeff: 0m0.019s

# 10000 elements
Choroba: 0m0.004s
Kusalananda: 0m0.006s
Jeff: 0m0.020s

# 99000 elements
Choroba: 0m0.015s
Kusalananda: 0m0.012s
Jeff: 0m0.119s


Choroba and Kusalananda have the right idea: compute the lengths once and use dedicated utilities for sorting and text processing.






share|improve this answer






























    up vote
    4
    down vote













    A hackish? (complex) and fast one line way to sort the array by length

    (safe for newlines and sparse arrays):



    #!/bin/bash
    in=(
    "tiny string"
    "the longest
    string also containing
    newlines"
    "middle string"
    "medium string"
    "also a medium string"
    "short string"
    "test * string"
    "*"
    "?"
    "[abc]"
    )

    readarray -td $'' sorted < <(
    for i in "${in[@]}"
    do printf '%s %s' "${#i}" "$i";
    done |
    sort -bz -k1,1rn -k2 |
    cut -zd " " -f2-
    )

    printf '%sn' "${sorted[@]}"


    On one line:



    readarray -td $'' sorted < <(for i in "${in[@]}";do printf '%s %s' "${#i}" "$i"; done | sort -bz -k1,1rn -k2 | cut -zd " " -f2-)


    On execution



    $ ./script
    the longest
    string also containing
    newlines
    also a medium string
    medium string
    middle string
    test * string
    short string
    tiny string
    [abc]
    ?
    *





    share|improve this answer






























      up vote
      4
      down vote













      This also handles array elements with newlines in them; it works by passing through sort only the length and the index of each element. It should work with bash and ksh.



      in=(
      "tiny string"
      "the longest
      string also containing
      newlines"
      "middle string"
      "medium string"
      "also a medium string"
      "short string"
      )
      out=()

      unset IFS
      for a in $(for i in ${!in[@]}; do echo ${#in[i]}/$i; done | sort -rn); do
      out+=("${in[${a#*/}]}")
      done

      for a in "${out[@]}"; do printf '"%s"n' "$a"; done


      If the elements of the same length also have to be sorted lexicographically, the loop could be changed like this:



      IFS='
      '
      for a in $(for i in ${!in[@]}; do printf '%sn' "$i ${#in[i]} ${in[i]//$IFS/ }"; done | sort -k 2,2nr -k 3 | cut -d' ' -f1); do
      out+=("${in[$a]}")
      done


      This will also pass to sort the strings (with newlines changed to spaces), but they would still be copied from the source to the destination array by their indexes. In both examples, the $(...) will see only lines containing numbers (and the / character in the first example), so it won't be tripped by globbing characters or spaces in the strings.






      share|improve this answer























      • @Isaac there's no need for quoting ${!in[@]}, because unset IFS resets it to space,tab,newline, and quoting those variables wouldn't suffice anyway, because the $(...) command substitution is itself split with IFS.
        – mosvy
        yesterday












      • No, the strings are still copied from the in to the out array by their index. The ${//} substituted ones are built just for the sake of sort.
        – mosvy
        yesterday












      • Cleaned comments. Now it breaks if in contains something like "testing * here" and shopt -s nullglob (and/or some others) get set at the script before the for loop. I'll insist: quote your expansions, avoid the pain.
        – Isaac
        yesterday










      • Cannot reproduce. In the second example, the $(...) command substitution sees only the indexes (a list of numbers separated by newlines), because of the cut -d' ' -f1 after the sort. This could be easily demonstrated by a tee /dev/tty at the end of the $(...).
        – mosvy
        4 hours ago












      • Sorry, my bad, I missed the cut.
        – Stéphane Chazelas
        3 hours ago


















      up vote
      3
      down vote













      In case switching to zsh is an option, a hackish way there (for arrays containing any sequence of bytes):



      array=('' blah $'xnynz' $'xy' '1 2 3')
      sorted_array=( /(e'{reply=("$array[@]")}'nOe'{REPLY=$#REPLY}') )


      zsh allows defining sort orders for its glob expansion via glob qualifiers. So here, we're tricking it to do it for arbitrary arrays by globbing on /, but replacing / with the elements of the array (e'{reply=("$array[@]")}') and then numerically order (in reverse with uppercase O) the elements based on their length (Oe'{REPLY=$#REPLY}').



      Note that it's based on the length in number of characters. For number of bytes, set the locale to C (LC_ALL=C).



      Another bash 4.4+ approach (assuming not too big an array):



      readarray -td '' sorted_array < <(
      perl -l0 -e 'print for sort {length $b <=> length $a} @ARGV
      ' -- "${array[@]}")


      (that's length in bytes).



      With older versions of bash, you could always do:



      eval "sorted_array=($(
      perl -l0 -e 'for (sort {length $b <=> length $a} @ARGV) {
      '"s/'/'\\''/g"'; printf " ''%s''", $_}' -- "${array[@]}"
      ))"


      (which would also work with ksh93, zsh, yash, mksh).






      share|improve this answer























        Your Answer








        StackExchange.ready(function() {
        var channelOptions = {
        tags: "".split(" "),
        id: "106"
        };
        initTagRenderer("".split(" "), "".split(" "), channelOptions);

        StackExchange.using("externalEditor", function() {
        // Have to fire editor after snippets, if snippets enabled
        if (StackExchange.settings.snippets.snippetsEnabled) {
        StackExchange.using("snippets", function() {
        createEditor();
        });
        }
        else {
        createEditor();
        }
        });

        function createEditor() {
        StackExchange.prepareEditor({
        heartbeatType: 'answer',
        convertImagesToLinks: false,
        noModals: true,
        showLowRepImageUploadWarning: true,
        reputationToPostImages: null,
        bindNavPrevention: true,
        postfix: "",
        imageUploader: {
        brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
        contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
        allowUrls: true
        },
        onDemand: true,
        discardSelector: ".discard-answer"
        ,immediatelyShowMarkdownHelp:true
        });


        }
        });






        PJ Singh is a new contributor. Be nice, and check out our Code of Conduct.










         

        draft saved


        draft discarded


















        StackExchange.ready(
        function () {
        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f482393%2fbash-sort-array-according-to-length-of-elements%23new-answer', 'question_page');
        }
        );

        Post as a guest















        Required, but never shown

























        6 Answers
        6






        active

        oldest

        votes








        6 Answers
        6






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes








        up vote
        10
        down vote



        accepted










        If the strings don't contain newlines, the following should work. It sorts the indices of the array by the length, using the strings themselves as the secondary sort criterion.



        #!/bin/bash
        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )
        expected=(
        "the longest string in the list"
        "also a medium string"
        "medium string"
        "middle string"
        "short string"
        "tiny string"
        )

        indexes=( $(
        for i in "${!array[@]}" ; do
        printf '%s %s %sn' $i "${#array[i]}" "${array[i]}"
        done | sort -nrk2,2 -rk3 | cut -f1 -d' '
        ))

        for i in "${indexes[@]}" ; do
        sorted+=("${array[i]}")
        done

        diff <(echo "${expected[@]}")
        <(echo "${sorted[@]}")


        Note that moving to a real programming language can greatly simplify the solution, e.g. in Perl, you can just



        sort { length $b <=> length $a or $a cmp $b } @array





        share|improve this answer























        • In Python: sorted(array, key=lambda s: (len(s), s))
          – wjandrea
          yesterday










        • In Ruby: array.sort { |a| a.size }
          – Dmitry Kudriavtsev
          16 hours ago















        up vote
        10
        down vote



        accepted










        If the strings don't contain newlines, the following should work. It sorts the indices of the array by the length, using the strings themselves as the secondary sort criterion.



        #!/bin/bash
        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )
        expected=(
        "the longest string in the list"
        "also a medium string"
        "medium string"
        "middle string"
        "short string"
        "tiny string"
        )

        indexes=( $(
        for i in "${!array[@]}" ; do
        printf '%s %s %sn' $i "${#array[i]}" "${array[i]}"
        done | sort -nrk2,2 -rk3 | cut -f1 -d' '
        ))

        for i in "${indexes[@]}" ; do
        sorted+=("${array[i]}")
        done

        diff <(echo "${expected[@]}")
        <(echo "${sorted[@]}")


        Note that moving to a real programming language can greatly simplify the solution, e.g. in Perl, you can just



        sort { length $b <=> length $a or $a cmp $b } @array





        share|improve this answer























        • In Python: sorted(array, key=lambda s: (len(s), s))
          – wjandrea
          yesterday










        • In Ruby: array.sort { |a| a.size }
          – Dmitry Kudriavtsev
          16 hours ago













        up vote
        10
        down vote



        accepted







        up vote
        10
        down vote



        accepted






        If the strings don't contain newlines, the following should work. It sorts the indices of the array by the length, using the strings themselves as the secondary sort criterion.



        #!/bin/bash
        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )
        expected=(
        "the longest string in the list"
        "also a medium string"
        "medium string"
        "middle string"
        "short string"
        "tiny string"
        )

        indexes=( $(
        for i in "${!array[@]}" ; do
        printf '%s %s %sn' $i "${#array[i]}" "${array[i]}"
        done | sort -nrk2,2 -rk3 | cut -f1 -d' '
        ))

        for i in "${indexes[@]}" ; do
        sorted+=("${array[i]}")
        done

        diff <(echo "${expected[@]}")
        <(echo "${sorted[@]}")


        Note that moving to a real programming language can greatly simplify the solution, e.g. in Perl, you can just



        sort { length $b <=> length $a or $a cmp $b } @array





        share|improve this answer














        If the strings don't contain newlines, the following should work. It sorts the indices of the array by the length, using the strings themselves as the secondary sort criterion.



        #!/bin/bash
        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )
        expected=(
        "the longest string in the list"
        "also a medium string"
        "medium string"
        "middle string"
        "short string"
        "tiny string"
        )

        indexes=( $(
        for i in "${!array[@]}" ; do
        printf '%s %s %sn' $i "${#array[i]}" "${array[i]}"
        done | sort -nrk2,2 -rk3 | cut -f1 -d' '
        ))

        for i in "${indexes[@]}" ; do
        sorted+=("${array[i]}")
        done

        diff <(echo "${expected[@]}")
        <(echo "${sorted[@]}")


        Note that moving to a real programming language can greatly simplify the solution, e.g. in Perl, you can just



        sort { length $b <=> length $a or $a cmp $b } @array






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited yesterday

























        answered yesterday









        choroba

        25.7k44370




        25.7k44370












        • In Python: sorted(array, key=lambda s: (len(s), s))
          – wjandrea
          yesterday










        • In Ruby: array.sort { |a| a.size }
          – Dmitry Kudriavtsev
          16 hours ago


















        • In Python: sorted(array, key=lambda s: (len(s), s))
          – wjandrea
          yesterday










        • In Ruby: array.sort { |a| a.size }
          – Dmitry Kudriavtsev
          16 hours ago
















        In Python: sorted(array, key=lambda s: (len(s), s))
        – wjandrea
        yesterday




        In Python: sorted(array, key=lambda s: (len(s), s))
        – wjandrea
        yesterday












        In Ruby: array.sort { |a| a.size }
        – Dmitry Kudriavtsev
        16 hours ago




        In Ruby: array.sort { |a| a.size }
        – Dmitry Kudriavtsev
        16 hours ago












        up vote
        8
        down vote













        readarray -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%sn' "${#str}" "$str"
        done | sort -k 1,1nr -k 2 | cut -f 2- )


        This reads the values of the sorted array from a process substitution.



        The process substitution contains a loop. The loop output each element of the array prepended by the element's length and a tab character in-between.



        The output of the loop is sorted numerically from largest to smallest (and alphabetically if the lengths are the same; use -k 2r in place of -k 2 to reverse the alphabetical order) and the result of that is sent to cut which deletes the column with the string lengths.



        Sort test script followed by a test run:



        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )

        readarray -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%sn' "${#str}" "$str"
        done | sort -k 1,1nr -k 2 | cut -f 2- )

        printf '%sn' "${array[@]}"




        $ bash script.sh
        the longest string in the list
        also a medium string
        medium string
        middle string
        short string
        tiny string


        This assumes that the strings do not contain newlines. On GNU systems with a recent bash, you can support embedded newlines in the data by using the nul-character as the record separator instead of newline:



        readarray -d '' -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%s' "${#str}" "$str"
        done | sort -z -k 1,1nr -k 2 | cut -z -f 2- )


        Here, the data is printed with trailing in the loop instead of newlines, the sort and cut reads nul-delimited lines through their -z GNU options and readarray finally reads the nul-delimited data with -d ''.






        share|improve this answer



















        • 3




          Note that -d '' is in fact -d '' as bash can't pass NUL characters to commands, even its builtins. But it does understand -d '' as meaning delimit on NUL. Note that you need bash 4.4+ for that.
          – Stéphane Chazelas
          yesterday










        • dat edit seems familiar, doesn't it?
          – Isaac
          yesterday










        • @StéphaneChazelas No, it is not '', it is $''. And yes, it converts (almost exactly) to ''. But that is a way to comunicate to other readers the actual intent of using a NUL delimiter.
          – Isaac
          yesterday










        • @Isaac Sorry, which edit?
          – Kusalananda
          yesterday






        • 1




          I should have said, and I diddn't, sorry: No, not at all, just that two .... etc. Having said that to ease my mind: I am still playing with it a little bit, hoping to expand solutions a little more forward. Maybe I will edit my answer (again), and thanks for confirming that the solution is useful as it stands (it looks similar to yours 😛 ).
          – Isaac
          20 hours ago















        up vote
        8
        down vote













        readarray -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%sn' "${#str}" "$str"
        done | sort -k 1,1nr -k 2 | cut -f 2- )


        This reads the values of the sorted array from a process substitution.



        The process substitution contains a loop. The loop output each element of the array prepended by the element's length and a tab character in-between.



        The output of the loop is sorted numerically from largest to smallest (and alphabetically if the lengths are the same; use -k 2r in place of -k 2 to reverse the alphabetical order) and the result of that is sent to cut which deletes the column with the string lengths.



        Sort test script followed by a test run:



        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )

        readarray -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%sn' "${#str}" "$str"
        done | sort -k 1,1nr -k 2 | cut -f 2- )

        printf '%sn' "${array[@]}"




        $ bash script.sh
        the longest string in the list
        also a medium string
        medium string
        middle string
        short string
        tiny string


        This assumes that the strings do not contain newlines. On GNU systems with a recent bash, you can support embedded newlines in the data by using the nul-character as the record separator instead of newline:



        readarray -d '' -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%s' "${#str}" "$str"
        done | sort -z -k 1,1nr -k 2 | cut -z -f 2- )


        Here, the data is printed with trailing in the loop instead of newlines, the sort and cut reads nul-delimited lines through their -z GNU options and readarray finally reads the nul-delimited data with -d ''.






        share|improve this answer



















        • 3




          Note that -d '' is in fact -d '' as bash can't pass NUL characters to commands, even its builtins. But it does understand -d '' as meaning delimit on NUL. Note that you need bash 4.4+ for that.
          – Stéphane Chazelas
          yesterday










        • dat edit seems familiar, doesn't it?
          – Isaac
          yesterday










        • @StéphaneChazelas No, it is not '', it is $''. And yes, it converts (almost exactly) to ''. But that is a way to comunicate to other readers the actual intent of using a NUL delimiter.
          – Isaac
          yesterday










        • @Isaac Sorry, which edit?
          – Kusalananda
          yesterday






        • 1




          I should have said, and I diddn't, sorry: No, not at all, just that two .... etc. Having said that to ease my mind: I am still playing with it a little bit, hoping to expand solutions a little more forward. Maybe I will edit my answer (again), and thanks for confirming that the solution is useful as it stands (it looks similar to yours 😛 ).
          – Isaac
          20 hours ago













        up vote
        8
        down vote










        up vote
        8
        down vote









        readarray -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%sn' "${#str}" "$str"
        done | sort -k 1,1nr -k 2 | cut -f 2- )


        This reads the values of the sorted array from a process substitution.



        The process substitution contains a loop. The loop output each element of the array prepended by the element's length and a tab character in-between.



        The output of the loop is sorted numerically from largest to smallest (and alphabetically if the lengths are the same; use -k 2r in place of -k 2 to reverse the alphabetical order) and the result of that is sent to cut which deletes the column with the string lengths.



        Sort test script followed by a test run:



        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )

        readarray -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%sn' "${#str}" "$str"
        done | sort -k 1,1nr -k 2 | cut -f 2- )

        printf '%sn' "${array[@]}"




        $ bash script.sh
        the longest string in the list
        also a medium string
        medium string
        middle string
        short string
        tiny string


        This assumes that the strings do not contain newlines. On GNU systems with a recent bash, you can support embedded newlines in the data by using the nul-character as the record separator instead of newline:



        readarray -d '' -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%s' "${#str}" "$str"
        done | sort -z -k 1,1nr -k 2 | cut -z -f 2- )


        Here, the data is printed with trailing in the loop instead of newlines, the sort and cut reads nul-delimited lines through their -z GNU options and readarray finally reads the nul-delimited data with -d ''.






        share|improve this answer














        readarray -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%sn' "${#str}" "$str"
        done | sort -k 1,1nr -k 2 | cut -f 2- )


        This reads the values of the sorted array from a process substitution.



        The process substitution contains a loop. The loop output each element of the array prepended by the element's length and a tab character in-between.



        The output of the loop is sorted numerically from largest to smallest (and alphabetically if the lengths are the same; use -k 2r in place of -k 2 to reverse the alphabetical order) and the result of that is sent to cut which deletes the column with the string lengths.



        Sort test script followed by a test run:



        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )

        readarray -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%sn' "${#str}" "$str"
        done | sort -k 1,1nr -k 2 | cut -f 2- )

        printf '%sn' "${array[@]}"




        $ bash script.sh
        the longest string in the list
        also a medium string
        medium string
        middle string
        short string
        tiny string


        This assumes that the strings do not contain newlines. On GNU systems with a recent bash, you can support embedded newlines in the data by using the nul-character as the record separator instead of newline:



        readarray -d '' -t array < <(
        for str in "${array[@]}"; do
        printf '%dt%s' "${#str}" "$str"
        done | sort -z -k 1,1nr -k 2 | cut -z -f 2- )


        Here, the data is printed with trailing in the loop instead of newlines, the sort and cut reads nul-delimited lines through their -z GNU options and readarray finally reads the nul-delimited data with -d ''.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited yesterday

























        answered yesterday









        Kusalananda

        116k15218351




        116k15218351








        • 3




          Note that -d '' is in fact -d '' as bash can't pass NUL characters to commands, even its builtins. But it does understand -d '' as meaning delimit on NUL. Note that you need bash 4.4+ for that.
          – Stéphane Chazelas
          yesterday










        • dat edit seems familiar, doesn't it?
          – Isaac
          yesterday










        • @StéphaneChazelas No, it is not '', it is $''. And yes, it converts (almost exactly) to ''. But that is a way to comunicate to other readers the actual intent of using a NUL delimiter.
          – Isaac
          yesterday










        • @Isaac Sorry, which edit?
          – Kusalananda
          yesterday






        • 1




          I should have said, and I diddn't, sorry: No, not at all, just that two .... etc. Having said that to ease my mind: I am still playing with it a little bit, hoping to expand solutions a little more forward. Maybe I will edit my answer (again), and thanks for confirming that the solution is useful as it stands (it looks similar to yours 😛 ).
          – Isaac
          20 hours ago














        • 3




          Note that -d '' is in fact -d '' as bash can't pass NUL characters to commands, even its builtins. But it does understand -d '' as meaning delimit on NUL. Note that you need bash 4.4+ for that.
          – Stéphane Chazelas
          yesterday










        • dat edit seems familiar, doesn't it?
          – Isaac
          yesterday










        • @StéphaneChazelas No, it is not '', it is $''. And yes, it converts (almost exactly) to ''. But that is a way to comunicate to other readers the actual intent of using a NUL delimiter.
          – Isaac
          yesterday










        • @Isaac Sorry, which edit?
          – Kusalananda
          yesterday






        • 1




          I should have said, and I diddn't, sorry: No, not at all, just that two .... etc. Having said that to ease my mind: I am still playing with it a little bit, hoping to expand solutions a little more forward. Maybe I will edit my answer (again), and thanks for confirming that the solution is useful as it stands (it looks similar to yours 😛 ).
          – Isaac
          20 hours ago








        3




        3




        Note that -d '' is in fact -d '' as bash can't pass NUL characters to commands, even its builtins. But it does understand -d '' as meaning delimit on NUL. Note that you need bash 4.4+ for that.
        – Stéphane Chazelas
        yesterday




        Note that -d '' is in fact -d '' as bash can't pass NUL characters to commands, even its builtins. But it does understand -d '' as meaning delimit on NUL. Note that you need bash 4.4+ for that.
        – Stéphane Chazelas
        yesterday












        dat edit seems familiar, doesn't it?
        – Isaac
        yesterday




        dat edit seems familiar, doesn't it?
        – Isaac
        yesterday












        @StéphaneChazelas No, it is not '', it is $''. And yes, it converts (almost exactly) to ''. But that is a way to comunicate to other readers the actual intent of using a NUL delimiter.
        – Isaac
        yesterday




        @StéphaneChazelas No, it is not '', it is $''. And yes, it converts (almost exactly) to ''. But that is a way to comunicate to other readers the actual intent of using a NUL delimiter.
        – Isaac
        yesterday












        @Isaac Sorry, which edit?
        – Kusalananda
        yesterday




        @Isaac Sorry, which edit?
        – Kusalananda
        yesterday




        1




        1




        I should have said, and I diddn't, sorry: No, not at all, just that two .... etc. Having said that to ease my mind: I am still playing with it a little bit, hoping to expand solutions a little more forward. Maybe I will edit my answer (again), and thanks for confirming that the solution is useful as it stands (it looks similar to yours 😛 ).
        – Isaac
        20 hours ago




        I should have said, and I diddn't, sorry: No, not at all, just that two .... etc. Having said that to ease my mind: I am still playing with it a little bit, hoping to expand solutions a little more forward. Maybe I will edit my answer (again), and thanks for confirming that the solution is useful as it stands (it looks similar to yours 😛 ).
        – Isaac
        20 hours ago










        up vote
        4
        down vote













        I won't completely repeat what I've already said about sorting in bash, just you can sort within bash, but maybe you shouldn't. Below is a bash-only implementation of an insertion sort, which is O(n2), and so is only tolerable for small arrays. It sorts the array elements in-place by their length, in decreasing order. It does not do a secondary alphabetical sort.



        array=(
        "tiny string"
        "the longest string in the list"
        "middle string"
        "medium string"
        "also a medium string"
        "short string"
        )

        function sort_inplace {
        local i j tmp
        for ((i=0; i <= ${#array[@]} - 2; i++))
        do
        for ((j=i + 1; j <= ${#array[@]} - 1; j++))
        do
        local ivalue jvalue
        ivalue=${#array[i]}
        jvalue=${#array[j]}
        if [[ $ivalue < $jvalue ]]
        then
        tmp=${array[i]}
        array[i]=${array[j]}
        array[j]=$tmp
        fi
        done
        done
        }

        echo Initial:
        declare -p array

        sort_inplace

        echo Sorted:
        declare -p array


        As evidence that this is a specialized solution, consider the timings of the existing three answers on various size arrays:



        # 6 elements
        Choroba: 0m0.004s
        Kusalananda: 0m0.004s
        Jeff: 0m0.018s ## already 4 times slower!

        # 1000 elements
        Choroba: 0m0.004s
        Kusalananda: 0m0.004s
        Jeff: 0m0.021s ## up to 5 times slower, now!

        5000 elements
        Choroba: 0m0.004s
        Kusalananda: 0m0.004s
        Jeff: 0m0.019s

        # 10000 elements
        Choroba: 0m0.004s
        Kusalananda: 0m0.006s
        Jeff: 0m0.020s

        # 99000 elements
        Choroba: 0m0.015s
        Kusalananda: 0m0.012s
        Jeff: 0m0.119s


        Choroba and Kusalananda have the right idea: compute the lengths once and use dedicated utilities for sorting and text processing.






        share|improve this answer



























          up vote
          4
          down vote













          I won't completely repeat what I've already said about sorting in bash, just you can sort within bash, but maybe you shouldn't. Below is a bash-only implementation of an insertion sort, which is O(n2), and so is only tolerable for small arrays. It sorts the array elements in-place by their length, in decreasing order. It does not do a secondary alphabetical sort.



          array=(
          "tiny string"
          "the longest string in the list"
          "middle string"
          "medium string"
          "also a medium string"
          "short string"
          )

          function sort_inplace {
          local i j tmp
          for ((i=0; i <= ${#array[@]} - 2; i++))
          do
          for ((j=i + 1; j <= ${#array[@]} - 1; j++))
          do
          local ivalue jvalue
          ivalue=${#array[i]}
          jvalue=${#array[j]}
          if [[ $ivalue < $jvalue ]]
          then
          tmp=${array[i]}
          array[i]=${array[j]}
          array[j]=$tmp
          fi
          done
          done
          }

          echo Initial:
          declare -p array

          sort_inplace

          echo Sorted:
          declare -p array


          As evidence that this is a specialized solution, consider the timings of the existing three answers on various size arrays:



          # 6 elements
          Choroba: 0m0.004s
          Kusalananda: 0m0.004s
          Jeff: 0m0.018s ## already 4 times slower!

          # 1000 elements
          Choroba: 0m0.004s
          Kusalananda: 0m0.004s
          Jeff: 0m0.021s ## up to 5 times slower, now!

          5000 elements
          Choroba: 0m0.004s
          Kusalananda: 0m0.004s
          Jeff: 0m0.019s

          # 10000 elements
          Choroba: 0m0.004s
          Kusalananda: 0m0.006s
          Jeff: 0m0.020s

          # 99000 elements
          Choroba: 0m0.015s
          Kusalananda: 0m0.012s
          Jeff: 0m0.119s


          Choroba and Kusalananda have the right idea: compute the lengths once and use dedicated utilities for sorting and text processing.






          share|improve this answer

























            up vote
            4
            down vote










            up vote
            4
            down vote









            I won't completely repeat what I've already said about sorting in bash, just you can sort within bash, but maybe you shouldn't. Below is a bash-only implementation of an insertion sort, which is O(n2), and so is only tolerable for small arrays. It sorts the array elements in-place by their length, in decreasing order. It does not do a secondary alphabetical sort.



            array=(
            "tiny string"
            "the longest string in the list"
            "middle string"
            "medium string"
            "also a medium string"
            "short string"
            )

            function sort_inplace {
            local i j tmp
            for ((i=0; i <= ${#array[@]} - 2; i++))
            do
            for ((j=i + 1; j <= ${#array[@]} - 1; j++))
            do
            local ivalue jvalue
            ivalue=${#array[i]}
            jvalue=${#array[j]}
            if [[ $ivalue < $jvalue ]]
            then
            tmp=${array[i]}
            array[i]=${array[j]}
            array[j]=$tmp
            fi
            done
            done
            }

            echo Initial:
            declare -p array

            sort_inplace

            echo Sorted:
            declare -p array


            As evidence that this is a specialized solution, consider the timings of the existing three answers on various size arrays:



            # 6 elements
            Choroba: 0m0.004s
            Kusalananda: 0m0.004s
            Jeff: 0m0.018s ## already 4 times slower!

            # 1000 elements
            Choroba: 0m0.004s
            Kusalananda: 0m0.004s
            Jeff: 0m0.021s ## up to 5 times slower, now!

            5000 elements
            Choroba: 0m0.004s
            Kusalananda: 0m0.004s
            Jeff: 0m0.019s

            # 10000 elements
            Choroba: 0m0.004s
            Kusalananda: 0m0.006s
            Jeff: 0m0.020s

            # 99000 elements
            Choroba: 0m0.015s
            Kusalananda: 0m0.012s
            Jeff: 0m0.119s


            Choroba and Kusalananda have the right idea: compute the lengths once and use dedicated utilities for sorting and text processing.






            share|improve this answer














            I won't completely repeat what I've already said about sorting in bash, just you can sort within bash, but maybe you shouldn't. Below is a bash-only implementation of an insertion sort, which is O(n2), and so is only tolerable for small arrays. It sorts the array elements in-place by their length, in decreasing order. It does not do a secondary alphabetical sort.



            array=(
            "tiny string"
            "the longest string in the list"
            "middle string"
            "medium string"
            "also a medium string"
            "short string"
            )

            function sort_inplace {
            local i j tmp
            for ((i=0; i <= ${#array[@]} - 2; i++))
            do
            for ((j=i + 1; j <= ${#array[@]} - 1; j++))
            do
            local ivalue jvalue
            ivalue=${#array[i]}
            jvalue=${#array[j]}
            if [[ $ivalue < $jvalue ]]
            then
            tmp=${array[i]}
            array[i]=${array[j]}
            array[j]=$tmp
            fi
            done
            done
            }

            echo Initial:
            declare -p array

            sort_inplace

            echo Sorted:
            declare -p array


            As evidence that this is a specialized solution, consider the timings of the existing three answers on various size arrays:



            # 6 elements
            Choroba: 0m0.004s
            Kusalananda: 0m0.004s
            Jeff: 0m0.018s ## already 4 times slower!

            # 1000 elements
            Choroba: 0m0.004s
            Kusalananda: 0m0.004s
            Jeff: 0m0.021s ## up to 5 times slower, now!

            5000 elements
            Choroba: 0m0.004s
            Kusalananda: 0m0.004s
            Jeff: 0m0.019s

            # 10000 elements
            Choroba: 0m0.004s
            Kusalananda: 0m0.006s
            Jeff: 0m0.020s

            # 99000 elements
            Choroba: 0m0.015s
            Kusalananda: 0m0.012s
            Jeff: 0m0.119s


            Choroba and Kusalananda have the right idea: compute the lengths once and use dedicated utilities for sorting and text processing.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited yesterday

























            answered yesterday









            Jeff Schaller

            36.1k952119




            36.1k952119






















                up vote
                4
                down vote













                A hackish? (complex) and fast one line way to sort the array by length

                (safe for newlines and sparse arrays):



                #!/bin/bash
                in=(
                "tiny string"
                "the longest
                string also containing
                newlines"
                "middle string"
                "medium string"
                "also a medium string"
                "short string"
                "test * string"
                "*"
                "?"
                "[abc]"
                )

                readarray -td $'' sorted < <(
                for i in "${in[@]}"
                do printf '%s %s' "${#i}" "$i";
                done |
                sort -bz -k1,1rn -k2 |
                cut -zd " " -f2-
                )

                printf '%sn' "${sorted[@]}"


                On one line:



                readarray -td $'' sorted < <(for i in "${in[@]}";do printf '%s %s' "${#i}" "$i"; done | sort -bz -k1,1rn -k2 | cut -zd " " -f2-)


                On execution



                $ ./script
                the longest
                string also containing
                newlines
                also a medium string
                medium string
                middle string
                test * string
                short string
                tiny string
                [abc]
                ?
                *





                share|improve this answer



























                  up vote
                  4
                  down vote













                  A hackish? (complex) and fast one line way to sort the array by length

                  (safe for newlines and sparse arrays):



                  #!/bin/bash
                  in=(
                  "tiny string"
                  "the longest
                  string also containing
                  newlines"
                  "middle string"
                  "medium string"
                  "also a medium string"
                  "short string"
                  "test * string"
                  "*"
                  "?"
                  "[abc]"
                  )

                  readarray -td $'' sorted < <(
                  for i in "${in[@]}"
                  do printf '%s %s' "${#i}" "$i";
                  done |
                  sort -bz -k1,1rn -k2 |
                  cut -zd " " -f2-
                  )

                  printf '%sn' "${sorted[@]}"


                  On one line:



                  readarray -td $'' sorted < <(for i in "${in[@]}";do printf '%s %s' "${#i}" "$i"; done | sort -bz -k1,1rn -k2 | cut -zd " " -f2-)


                  On execution



                  $ ./script
                  the longest
                  string also containing
                  newlines
                  also a medium string
                  medium string
                  middle string
                  test * string
                  short string
                  tiny string
                  [abc]
                  ?
                  *





                  share|improve this answer

























                    up vote
                    4
                    down vote










                    up vote
                    4
                    down vote









                    A hackish? (complex) and fast one line way to sort the array by length

                    (safe for newlines and sparse arrays):



                    #!/bin/bash
                    in=(
                    "tiny string"
                    "the longest
                    string also containing
                    newlines"
                    "middle string"
                    "medium string"
                    "also a medium string"
                    "short string"
                    "test * string"
                    "*"
                    "?"
                    "[abc]"
                    )

                    readarray -td $'' sorted < <(
                    for i in "${in[@]}"
                    do printf '%s %s' "${#i}" "$i";
                    done |
                    sort -bz -k1,1rn -k2 |
                    cut -zd " " -f2-
                    )

                    printf '%sn' "${sorted[@]}"


                    On one line:



                    readarray -td $'' sorted < <(for i in "${in[@]}";do printf '%s %s' "${#i}" "$i"; done | sort -bz -k1,1rn -k2 | cut -zd " " -f2-)


                    On execution



                    $ ./script
                    the longest
                    string also containing
                    newlines
                    also a medium string
                    medium string
                    middle string
                    test * string
                    short string
                    tiny string
                    [abc]
                    ?
                    *





                    share|improve this answer














                    A hackish? (complex) and fast one line way to sort the array by length

                    (safe for newlines and sparse arrays):



                    #!/bin/bash
                    in=(
                    "tiny string"
                    "the longest
                    string also containing
                    newlines"
                    "middle string"
                    "medium string"
                    "also a medium string"
                    "short string"
                    "test * string"
                    "*"
                    "?"
                    "[abc]"
                    )

                    readarray -td $'' sorted < <(
                    for i in "${in[@]}"
                    do printf '%s %s' "${#i}" "$i";
                    done |
                    sort -bz -k1,1rn -k2 |
                    cut -zd " " -f2-
                    )

                    printf '%sn' "${sorted[@]}"


                    On one line:



                    readarray -td $'' sorted < <(for i in "${in[@]}";do printf '%s %s' "${#i}" "$i"; done | sort -bz -k1,1rn -k2 | cut -zd " " -f2-)


                    On execution



                    $ ./script
                    the longest
                    string also containing
                    newlines
                    also a medium string
                    medium string
                    middle string
                    test * string
                    short string
                    tiny string
                    [abc]
                    ?
                    *






                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited yesterday

























                    answered yesterday









                    Isaac

                    9,58411443




                    9,58411443






















                        up vote
                        4
                        down vote













                        This also handles array elements with newlines in them; it works by passing through sort only the length and the index of each element. It should work with bash and ksh.



                        in=(
                        "tiny string"
                        "the longest
                        string also containing
                        newlines"
                        "middle string"
                        "medium string"
                        "also a medium string"
                        "short string"
                        )
                        out=()

                        unset IFS
                        for a in $(for i in ${!in[@]}; do echo ${#in[i]}/$i; done | sort -rn); do
                        out+=("${in[${a#*/}]}")
                        done

                        for a in "${out[@]}"; do printf '"%s"n' "$a"; done


                        If the elements of the same length also have to be sorted lexicographically, the loop could be changed like this:



                        IFS='
                        '
                        for a in $(for i in ${!in[@]}; do printf '%sn' "$i ${#in[i]} ${in[i]//$IFS/ }"; done | sort -k 2,2nr -k 3 | cut -d' ' -f1); do
                        out+=("${in[$a]}")
                        done


                        This will also pass to sort the strings (with newlines changed to spaces), but they would still be copied from the source to the destination array by their indexes. In both examples, the $(...) will see only lines containing numbers (and the / character in the first example), so it won't be tripped by globbing characters or spaces in the strings.






                        share|improve this answer























                        • @Isaac there's no need for quoting ${!in[@]}, because unset IFS resets it to space,tab,newline, and quoting those variables wouldn't suffice anyway, because the $(...) command substitution is itself split with IFS.
                          – mosvy
                          yesterday












                        • No, the strings are still copied from the in to the out array by their index. The ${//} substituted ones are built just for the sake of sort.
                          – mosvy
                          yesterday












                        • Cleaned comments. Now it breaks if in contains something like "testing * here" and shopt -s nullglob (and/or some others) get set at the script before the for loop. I'll insist: quote your expansions, avoid the pain.
                          – Isaac
                          yesterday










                        • Cannot reproduce. In the second example, the $(...) command substitution sees only the indexes (a list of numbers separated by newlines), because of the cut -d' ' -f1 after the sort. This could be easily demonstrated by a tee /dev/tty at the end of the $(...).
                          – mosvy
                          4 hours ago












                        • Sorry, my bad, I missed the cut.
                          – Stéphane Chazelas
                          3 hours ago















                        up vote
                        4
                        down vote













                        This also handles array elements with newlines in them; it works by passing through sort only the length and the index of each element. It should work with bash and ksh.



                        in=(
                        "tiny string"
                        "the longest
                        string also containing
                        newlines"
                        "middle string"
                        "medium string"
                        "also a medium string"
                        "short string"
                        )
                        out=()

                        unset IFS
                        for a in $(for i in ${!in[@]}; do echo ${#in[i]}/$i; done | sort -rn); do
                        out+=("${in[${a#*/}]}")
                        done

                        for a in "${out[@]}"; do printf '"%s"n' "$a"; done


                        If the elements of the same length also have to be sorted lexicographically, the loop could be changed like this:



                        IFS='
                        '
                        for a in $(for i in ${!in[@]}; do printf '%sn' "$i ${#in[i]} ${in[i]//$IFS/ }"; done | sort -k 2,2nr -k 3 | cut -d' ' -f1); do
                        out+=("${in[$a]}")
                        done


                        This will also pass to sort the strings (with newlines changed to spaces), but they would still be copied from the source to the destination array by their indexes. In both examples, the $(...) will see only lines containing numbers (and the / character in the first example), so it won't be tripped by globbing characters or spaces in the strings.






                        share|improve this answer























                        • @Isaac there's no need for quoting ${!in[@]}, because unset IFS resets it to space,tab,newline, and quoting those variables wouldn't suffice anyway, because the $(...) command substitution is itself split with IFS.
                          – mosvy
                          yesterday












                        • No, the strings are still copied from the in to the out array by their index. The ${//} substituted ones are built just for the sake of sort.
                          – mosvy
                          yesterday












                        • Cleaned comments. Now it breaks if in contains something like "testing * here" and shopt -s nullglob (and/or some others) get set at the script before the for loop. I'll insist: quote your expansions, avoid the pain.
                          – Isaac
                          yesterday










                        • Cannot reproduce. In the second example, the $(...) command substitution sees only the indexes (a list of numbers separated by newlines), because of the cut -d' ' -f1 after the sort. This could be easily demonstrated by a tee /dev/tty at the end of the $(...).
                          – mosvy
                          4 hours ago












                        • Sorry, my bad, I missed the cut.
                          – Stéphane Chazelas
                          3 hours ago













                        up vote
                        4
                        down vote










                        up vote
                        4
                        down vote









                        This also handles array elements with newlines in them; it works by passing through sort only the length and the index of each element. It should work with bash and ksh.



                        in=(
                        "tiny string"
                        "the longest
                        string also containing
                        newlines"
                        "middle string"
                        "medium string"
                        "also a medium string"
                        "short string"
                        )
                        out=()

                        unset IFS
                        for a in $(for i in ${!in[@]}; do echo ${#in[i]}/$i; done | sort -rn); do
                        out+=("${in[${a#*/}]}")
                        done

                        for a in "${out[@]}"; do printf '"%s"n' "$a"; done


                        If the elements of the same length also have to be sorted lexicographically, the loop could be changed like this:



                        IFS='
                        '
                        for a in $(for i in ${!in[@]}; do printf '%sn' "$i ${#in[i]} ${in[i]//$IFS/ }"; done | sort -k 2,2nr -k 3 | cut -d' ' -f1); do
                        out+=("${in[$a]}")
                        done


                        This will also pass to sort the strings (with newlines changed to spaces), but they would still be copied from the source to the destination array by their indexes. In both examples, the $(...) will see only lines containing numbers (and the / character in the first example), so it won't be tripped by globbing characters or spaces in the strings.






                        share|improve this answer














                        This also handles array elements with newlines in them; it works by passing through sort only the length and the index of each element. It should work with bash and ksh.



                        in=(
                        "tiny string"
                        "the longest
                        string also containing
                        newlines"
                        "middle string"
                        "medium string"
                        "also a medium string"
                        "short string"
                        )
                        out=()

                        unset IFS
                        for a in $(for i in ${!in[@]}; do echo ${#in[i]}/$i; done | sort -rn); do
                        out+=("${in[${a#*/}]}")
                        done

                        for a in "${out[@]}"; do printf '"%s"n' "$a"; done


                        If the elements of the same length also have to be sorted lexicographically, the loop could be changed like this:



                        IFS='
                        '
                        for a in $(for i in ${!in[@]}; do printf '%sn' "$i ${#in[i]} ${in[i]//$IFS/ }"; done | sort -k 2,2nr -k 3 | cut -d' ' -f1); do
                        out+=("${in[$a]}")
                        done


                        This will also pass to sort the strings (with newlines changed to spaces), but they would still be copied from the source to the destination array by their indexes. In both examples, the $(...) will see only lines containing numbers (and the / character in the first example), so it won't be tripped by globbing characters or spaces in the strings.







                        share|improve this answer














                        share|improve this answer



                        share|improve this answer








                        edited 3 hours ago

























                        answered yesterday









                        mosvy

                        4,265221




                        4,265221












                        • @Isaac there's no need for quoting ${!in[@]}, because unset IFS resets it to space,tab,newline, and quoting those variables wouldn't suffice anyway, because the $(...) command substitution is itself split with IFS.
                          – mosvy
                          yesterday












                        • No, the strings are still copied from the in to the out array by their index. The ${//} substituted ones are built just for the sake of sort.
                          – mosvy
                          yesterday












                        • Cleaned comments. Now it breaks if in contains something like "testing * here" and shopt -s nullglob (and/or some others) get set at the script before the for loop. I'll insist: quote your expansions, avoid the pain.
                          – Isaac
                          yesterday










                        • Cannot reproduce. In the second example, the $(...) command substitution sees only the indexes (a list of numbers separated by newlines), because of the cut -d' ' -f1 after the sort. This could be easily demonstrated by a tee /dev/tty at the end of the $(...).
                          – mosvy
                          4 hours ago












                        • Sorry, my bad, I missed the cut.
                          – Stéphane Chazelas
                          3 hours ago


















                        • @Isaac there's no need for quoting ${!in[@]}, because unset IFS resets it to space,tab,newline, and quoting those variables wouldn't suffice anyway, because the $(...) command substitution is itself split with IFS.
                          – mosvy
                          yesterday












                        • No, the strings are still copied from the in to the out array by their index. The ${//} substituted ones are built just for the sake of sort.
                          – mosvy
                          yesterday












                        • Cleaned comments. Now it breaks if in contains something like "testing * here" and shopt -s nullglob (and/or some others) get set at the script before the for loop. I'll insist: quote your expansions, avoid the pain.
                          – Isaac
                          yesterday










                        • Cannot reproduce. In the second example, the $(...) command substitution sees only the indexes (a list of numbers separated by newlines), because of the cut -d' ' -f1 after the sort. This could be easily demonstrated by a tee /dev/tty at the end of the $(...).
                          – mosvy
                          4 hours ago












                        • Sorry, my bad, I missed the cut.
                          – Stéphane Chazelas
                          3 hours ago
















                        @Isaac there's no need for quoting ${!in[@]}, because unset IFS resets it to space,tab,newline, and quoting those variables wouldn't suffice anyway, because the $(...) command substitution is itself split with IFS.
                        – mosvy
                        yesterday






                        @Isaac there's no need for quoting ${!in[@]}, because unset IFS resets it to space,tab,newline, and quoting those variables wouldn't suffice anyway, because the $(...) command substitution is itself split with IFS.
                        – mosvy
                        yesterday














                        No, the strings are still copied from the in to the out array by their index. The ${//} substituted ones are built just for the sake of sort.
                        – mosvy
                        yesterday






                        No, the strings are still copied from the in to the out array by their index. The ${//} substituted ones are built just for the sake of sort.
                        – mosvy
                        yesterday














                        Cleaned comments. Now it breaks if in contains something like "testing * here" and shopt -s nullglob (and/or some others) get set at the script before the for loop. I'll insist: quote your expansions, avoid the pain.
                        – Isaac
                        yesterday




                        Cleaned comments. Now it breaks if in contains something like "testing * here" and shopt -s nullglob (and/or some others) get set at the script before the for loop. I'll insist: quote your expansions, avoid the pain.
                        – Isaac
                        yesterday












                        Cannot reproduce. In the second example, the $(...) command substitution sees only the indexes (a list of numbers separated by newlines), because of the cut -d' ' -f1 after the sort. This could be easily demonstrated by a tee /dev/tty at the end of the $(...).
                        – mosvy
                        4 hours ago






                        Cannot reproduce. In the second example, the $(...) command substitution sees only the indexes (a list of numbers separated by newlines), because of the cut -d' ' -f1 after the sort. This could be easily demonstrated by a tee /dev/tty at the end of the $(...).
                        – mosvy
                        4 hours ago














                        Sorry, my bad, I missed the cut.
                        – Stéphane Chazelas
                        3 hours ago




                        Sorry, my bad, I missed the cut.
                        – Stéphane Chazelas
                        3 hours ago










                        up vote
                        3
                        down vote













                        In case switching to zsh is an option, a hackish way there (for arrays containing any sequence of bytes):



                        array=('' blah $'xnynz' $'xy' '1 2 3')
                        sorted_array=( /(e'{reply=("$array[@]")}'nOe'{REPLY=$#REPLY}') )


                        zsh allows defining sort orders for its glob expansion via glob qualifiers. So here, we're tricking it to do it for arbitrary arrays by globbing on /, but replacing / with the elements of the array (e'{reply=("$array[@]")}') and then numerically order (in reverse with uppercase O) the elements based on their length (Oe'{REPLY=$#REPLY}').



                        Note that it's based on the length in number of characters. For number of bytes, set the locale to C (LC_ALL=C).



                        Another bash 4.4+ approach (assuming not too big an array):



                        readarray -td '' sorted_array < <(
                        perl -l0 -e 'print for sort {length $b <=> length $a} @ARGV
                        ' -- "${array[@]}")


                        (that's length in bytes).



                        With older versions of bash, you could always do:



                        eval "sorted_array=($(
                        perl -l0 -e 'for (sort {length $b <=> length $a} @ARGV) {
                        '"s/'/'\\''/g"'; printf " ''%s''", $_}' -- "${array[@]}"
                        ))"


                        (which would also work with ksh93, zsh, yash, mksh).






                        share|improve this answer



























                          up vote
                          3
                          down vote













                          In case switching to zsh is an option, a hackish way there (for arrays containing any sequence of bytes):



                          array=('' blah $'xnynz' $'xy' '1 2 3')
                          sorted_array=( /(e'{reply=("$array[@]")}'nOe'{REPLY=$#REPLY}') )


                          zsh allows defining sort orders for its glob expansion via glob qualifiers. So here, we're tricking it to do it for arbitrary arrays by globbing on /, but replacing / with the elements of the array (e'{reply=("$array[@]")}') and then numerically order (in reverse with uppercase O) the elements based on their length (Oe'{REPLY=$#REPLY}').



                          Note that it's based on the length in number of characters. For number of bytes, set the locale to C (LC_ALL=C).



                          Another bash 4.4+ approach (assuming not too big an array):



                          readarray -td '' sorted_array < <(
                          perl -l0 -e 'print for sort {length $b <=> length $a} @ARGV
                          ' -- "${array[@]}")


                          (that's length in bytes).



                          With older versions of bash, you could always do:



                          eval "sorted_array=($(
                          perl -l0 -e 'for (sort {length $b <=> length $a} @ARGV) {
                          '"s/'/'\\''/g"'; printf " ''%s''", $_}' -- "${array[@]}"
                          ))"


                          (which would also work with ksh93, zsh, yash, mksh).






                          share|improve this answer

























                            up vote
                            3
                            down vote










                            up vote
                            3
                            down vote









                            In case switching to zsh is an option, a hackish way there (for arrays containing any sequence of bytes):



                            array=('' blah $'xnynz' $'xy' '1 2 3')
                            sorted_array=( /(e'{reply=("$array[@]")}'nOe'{REPLY=$#REPLY}') )


                            zsh allows defining sort orders for its glob expansion via glob qualifiers. So here, we're tricking it to do it for arbitrary arrays by globbing on /, but replacing / with the elements of the array (e'{reply=("$array[@]")}') and then numerically order (in reverse with uppercase O) the elements based on their length (Oe'{REPLY=$#REPLY}').



                            Note that it's based on the length in number of characters. For number of bytes, set the locale to C (LC_ALL=C).



                            Another bash 4.4+ approach (assuming not too big an array):



                            readarray -td '' sorted_array < <(
                            perl -l0 -e 'print for sort {length $b <=> length $a} @ARGV
                            ' -- "${array[@]}")


                            (that's length in bytes).



                            With older versions of bash, you could always do:



                            eval "sorted_array=($(
                            perl -l0 -e 'for (sort {length $b <=> length $a} @ARGV) {
                            '"s/'/'\\''/g"'; printf " ''%s''", $_}' -- "${array[@]}"
                            ))"


                            (which would also work with ksh93, zsh, yash, mksh).






                            share|improve this answer














                            In case switching to zsh is an option, a hackish way there (for arrays containing any sequence of bytes):



                            array=('' blah $'xnynz' $'xy' '1 2 3')
                            sorted_array=( /(e'{reply=("$array[@]")}'nOe'{REPLY=$#REPLY}') )


                            zsh allows defining sort orders for its glob expansion via glob qualifiers. So here, we're tricking it to do it for arbitrary arrays by globbing on /, but replacing / with the elements of the array (e'{reply=("$array[@]")}') and then numerically order (in reverse with uppercase O) the elements based on their length (Oe'{REPLY=$#REPLY}').



                            Note that it's based on the length in number of characters. For number of bytes, set the locale to C (LC_ALL=C).



                            Another bash 4.4+ approach (assuming not too big an array):



                            readarray -td '' sorted_array < <(
                            perl -l0 -e 'print for sort {length $b <=> length $a} @ARGV
                            ' -- "${array[@]}")


                            (that's length in bytes).



                            With older versions of bash, you could always do:



                            eval "sorted_array=($(
                            perl -l0 -e 'for (sort {length $b <=> length $a} @ARGV) {
                            '"s/'/'\\''/g"'; printf " ''%s''", $_}' -- "${array[@]}"
                            ))"


                            (which would also work with ksh93, zsh, yash, mksh).







                            share|improve this answer














                            share|improve this answer



                            share|improve this answer








                            edited 5 hours ago

























                            answered yesterday









                            Stéphane Chazelas

                            293k54551891




                            293k54551891






















                                PJ Singh is a new contributor. Be nice, and check out our Code of Conduct.










                                 

                                draft saved


                                draft discarded


















                                PJ Singh is a new contributor. Be nice, and check out our Code of Conduct.













                                PJ Singh is a new contributor. Be nice, and check out our Code of Conduct.












                                PJ Singh is a new contributor. Be nice, and check out our Code of Conduct.















                                 


                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function () {
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f482393%2fbash-sort-array-according-to-length-of-elements%23new-answer', 'question_page');
                                }
                                );

                                Post as a guest















                                Required, but never shown





















































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown

































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown







                                Popular posts from this blog

                                Can a sorcerer learn a 5th-level spell early by creating spell slots using the Font of Magic feature?

                                Does disintegrating a polymorphed enemy still kill it after the 2018 errata?

                                A Topological Invariant for $pi_3(U(n))$