How can I get unique values from an array in Bash?

前端 未结 14 1069
-上瘾入骨i
-上瘾入骨i 2020-11-27 12:50

I\'ve got almost the same question as here.

I have an array which contains aa ab aa ac aa ad, etc. Now I want to select all unique elements from this ar

相关标签:
14条回答
  • 2020-11-27 13:10

    A bit hacky, but this should do it:

    echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '
    

    To save the sorted unique results back into an array, do Array assignment:

    sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
    

    If your shell supports herestrings (bash should), you can spare an echo process by altering it to:

    tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' '
    

    Input:

    ids=(aa ab aa ac aa ad)
    

    Output:

    aa ab ac ad
    

    Explanation:

    • "${ids[@]}" - Syntax for working with shell arrays, whether used as part of echo or a herestring. The @ part means "all elements in the array"
    • tr ' ' '\n' - Convert all spaces to newlines. Because your array is seen by shell as elements on a single line, separated by spaces; and because sort expects input to be on separate lines.
    • sort -u - sort and retain only unique elements
    • tr '\n' ' ' - convert the newlines we added in earlier back to spaces.
    • $(...) - Command Substitution
    • Aside: tr ' ' '\n' <<< "${ids[@]}" is a more efficient way of doing: echo "${ids[@]}" | tr ' ' '\n'
    0 讨论(0)
  • 2020-11-27 13:12

    Another option for dealing with embedded whitespace, is to null-delimit with printf, make distinct with sort, then use a loop to pack it back into an array:

    input=(a b c "$(printf "d\ne")" b c "$(printf "d\ne")")
    output=()
    
    while read -rd $'' element
    do 
      output+=("$element")
    done < <(printf "%s\0" "${input[@]}" | sort -uz)
    

    At the end of this, input and output contain the desired values (provided order isn't important):

    $ printf "%q\n" "${input[@]}"
    a
    b
    c
    $'d\ne'
    b
    c
    $'d\ne'
    
    $ printf "%q\n" "${output[@]}"
    a
    b
    c
    $'d\ne'
    
    0 讨论(0)
  • 2020-11-27 13:14

    this one will also preserve order:

    echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'
    

    and to modify the original array with the unique values:

    ARRAY=($(echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'))
    
    0 讨论(0)
  • 2020-11-27 13:15

    If your array elements have white space or any other shell special character (and can you be sure they don't?) then to capture those first of all (and you should just always do this) express your array in double quotes! e.g. "${a[@]}". Bash will literally interpret this as "each array element in a separate argument". Within bash this simply always works, always.

    Then, to get a sorted (and unique) array, we have to convert it to a format sort understands and be able to convert it back into bash array elements. This is the best I've come up with:

    eval a=($(printf "%q\n" "${a[@]}" | sort -u))
    

    Unfortunately, this fails in the special case of the empty array, turning the empty array into an array of 1 empty element (because printf had 0 arguments but still prints as though it had one empty argument - see explanation). So you have to catch that in an if or something.

    Explanation: The %q format for printf "shell escapes" the printed argument, in just such a way as bash can recover in something like eval! Because each element is printed shell escaped on it's own line, the only separator between elements is the newline, and the array assignment takes each line as an element, parsing the escaped values into literal text.

    e.g.

    > a=("foo bar" baz)
    > printf "%q\n" "${a[@]}"
    'foo bar'
    baz
    > printf "%q\n"
    ''
    

    The eval is necessary to strip the escaping off each value going back into the array.

    0 讨论(0)
  • 2020-11-27 13:15

    Without loosing the original ordering:

    uniques=($(tr ' ' '\n' <<<"${original[@]}" | awk '!u[$0]++' | tr '\n' ' '))
    
    0 讨论(0)
  • 2020-11-27 13:16

    If you want a solution that only uses bash internals, you can set the values as keys in an associative array, and then extract the keys:

    declare -A uniqs
    list=(foo bar bar "bar none")
    for f in "${list[@]}"; do 
      uniqs["${f}"]=""
    done
    
    for thing in "${!uniqs[@]}"; do
      echo "${thing}"
    done
    

    This will output

    bar
    foo
    bar none
    
    0 讨论(0)
提交回复
热议问题