Sign up ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

If I have a string "1 2 3 2 1" - or an array [1,2,3,2,1] - how can I select the unique values, i.e.

"1 2 3 2 1" produces "1 2 3" 

or

[1,2,3,2,1] produces [1,2,3]

Similar to uniq but uniq seems to work on whole lines, not patterns within a line...

share|improve this question

5 Answers 5

up vote 2 down vote accepted

With GNU awk (this also retains original order)

printf '%s\n' "1 2 3 2 1" | awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}'
1 2 3 

To read into a bash array

read -ra arr<<<$(printf '%s\n' "1 2 3 2 1" |
 awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}')
printf "%s\n"  "${arr[@]}"
1
2
3
share|improve this answer
    
How can I then make that an array ? –  Michael Durrant Nov 10 '14 at 17:36
    
@MichaelDurrant, if you mean a bash array, added a way –  1_CR Nov 10 '14 at 18:07

If you are using zsh:

$ array=(1 2 3 2 1)
$ echo ${(u)array[@]}
1 2 3

or (if KSH_ARRAYS option is not set) even

$ echo ${(u)array}
1 2 3
share|improve this answer
    
If the array may contain empty elements, you should use "${(u)array[@]}" or "${(@u)array}" instead (note the quotes). –  Stéphane Chazelas Nov 10 '14 at 19:49

For an array with arbitrary values, it's quite tricky with bash as it doesn't have a builtin operator for that.

bash however happens not to support storing NUL characters in its variables, so you can make use of that to pass that to other commands:

The equivalent of zsh's:

new_array=("${(@u}array}")

on a recent GNU system, could be:

eval "new_array=($(
  printf "%s\0" "${array[@]}" |
    LC_ALL=C sort -zu |
    xargs -r0 bash -c 'printf "%q\n" "$@"' sh
  ))"

Alternatively, with recent versions of bash, you could use associated arrays:

unset hash
typeset -A hash
for i in "${array[@]}"; do
  hash[$i]=
done
new_array=("${!hash[@]}")

The order of the elements would not be the same in those different solutions.

share|improve this answer

This solution worked for me.

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

The above produces 1 2 3 as the output.

Shorter version as suggested by Costas could be,

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

To store the end results to an array, you could do something like,

IFS=$' '
arr=($(printf "%s\n" "${ids[@]}" | sort -u | tr '\n' ' '))
unset IFS

Now, when I do an echo on arr, this is the output I get.

echo "${arr[@]}"
1 2 3

References

http://stackoverflow.com/a/13648438/1742825 http://stackoverflow.com/a/9449633/1742825

share|improve this answer
    
@Costas, thanks. I have incorporated it to the answer. –  Ramesh Nov 10 '14 at 17:30
    
How can I make the end result be an array? –  Michael Durrant Nov 10 '14 at 17:36
    
@MichaelDurrant, please see the updated answer and let me know if this is fine. –  Ramesh Nov 10 '14 at 17:42
    
If you wants to put result into array you can strip last command tr '\n' ' ' –  Costas Nov 10 '14 at 21:08

To do it entirely in the shell and put the result in an array,

declare -A seen
for word in one two three two one
do
        if [ ! "${seen[$word]}" ]
        then
                result+=("$word")
                seen[$word]=1
        fi
done
echo "${result[@]}"

In words: if we haven’t seen a given word yet, add it to the result array and flag it as having been seen.  Once a word has been seen, ignore subsequent appearances of it.

share|improve this answer
2  
Note that you need unset seen before declare -A seen in case $seen was previously defined (even as a scalar variable from the environment). –  Stéphane Chazelas Nov 10 '14 at 20:10

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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