Wie erstelle ich ein Array eindeutiger Elemente aus einem String / Array in Bash?

8

Wenn ich eine Zeichenfolge "1 2 3 2 1" oder ein Array [1,2,3,2,1] habe, wie kann ich die eindeutigen Werte auswählen, d. H.

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

oder

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

Ähnlich wie uniq, aber uniq scheint auf ganzen Linien zu funktionieren, nicht auf Mustern innerhalb einer Linie ...

Michael Durrant
quelle

Antworten:

4

Mit GNU awk(dies behält auch die ursprüngliche Reihenfolge bei)

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

In readein bashArray

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
iruvar
quelle
Wie kann ich das dann zu einem Array machen?
Michael Durrant
@ MichaelDurrant, wenn Sie ein bashArray meinen , fügte einen Weg hinzu
iruvar
Sehen Sie hier, ob Ihr Array Leerzeichen enthält
Tom Hale
@iruvar kannst du bitte erklären was das eigentlich bedeutet? Ich bin neu in awk scripting und es wäre hilfreich, wenn Sie klären könnten, was tatsächlich passiert, wenn Sie dies sagen! a [$ 0] ++
Abhishek
@iruvar Wenn es nicht möglich ist, in Kommentaren eine Website zu erklären, die zumindest die obige Syntax erklärt, wäre dies von Vorteil.
Abhishek
9

Wenn Sie zsh verwenden:

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

oder (wenn die KSH_ARRAYSOption nicht eingestellt ist) sogar

$ echo ${(u)array}
1 2 3
jimmij
quelle
1
Wenn das Array leere Elemente enthalten kann, sollten Sie stattdessen "${(u)array[@]}"oder "${(@u)array}"verwenden (beachten Sie die Anführungszeichen).
Stéphane Chazelas
Ich verwende zsh 5.1.1 (x86_64-ubuntu-linux-gnu) und ${(u)array}arbeite auch dann, wenn das Array leer ist oder eine leere Zeichenfolge ohne Anführungszeichen enthält.
Kiamlaluno
4

Für ein Array mit beliebigen Werten ist es ziemlich schwierig, bashda es keinen eingebauten Operator dafür hat.

bash Das Speichern von NUL-Zeichen in seinen Variablen wird jedoch nicht unterstützt. Sie können dies also verwenden, um dies an andere Befehle zu übergeben:

Das Äquivalent von zsh's:

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

auf einem neueren GNU-System könnte sein:

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

Alternativ können Sie in neueren Versionen von bashund unter der Annahme, dass keines der Array-Elemente leer ist, assoziative Arrays verwenden:

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

Mit Bash 4.4 und neuer und mit GNU sort:

readarray -td '' new_array < <(
  printf '%s\0' "${array[@]}" | LC_ALL=C sort -zu)

Die Reihenfolge der Elemente wäre in diesen verschiedenen Lösungen nicht gleich.

Mit tcsh:

set -f new_array = ($array:q)

Würde behalten die f irst Element ( a b a=> a b) , wie zsh‚s (u)Expansion Flagge.

set -l new_array = ($array:q)

Würde das letzte ( a b a=> b a) behalten . Diese entfernen jedoch leere Elemente aus dem Array.

Stéphane Chazelas
quelle
1

Diese Lösung hat bei mir funktioniert.

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

Das Obige erzeugt 1 2 3 als Ausgabe.

Kürzere Version, wie von Costas vorgeschlagen, könnte sein,

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

Um die Endergebnisse in einem Array zu speichern, können Sie Folgendes tun:

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

Wenn ich jetzt ein Echo mache arr, ist dies die Ausgabe, die ich bekomme.

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

Verweise

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

Ramesh
quelle
@ Costas, danke. Ich habe es in die Antwort aufgenommen.
Ramesh
Wie kann ich das Endergebnis zu einem Array machen?
Michael Durrant
@ MichaelDurrant, bitte lesen Sie die aktualisierte Antwort und lassen Sie mich wissen, ob dies in Ordnung ist.
Ramesh
Wenn Sie das Ergebnis in ein Array tr '\n' ' '
Costas
0

Um es vollständig in der Shell zu tun und das Ergebnis in ein Array zu setzen,

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 Worten: Wenn wir ein bestimmtes Wort noch nicht gesehen haben, fügen Sie es dem resultArray hinzu und markieren Sie es als gesehen. Sobald ein Wort gesehen wurde, ignorieren Sie nachfolgende Erscheinungen.

Scott
quelle
2
Beachten Sie, dass Sie unset seenzuvor einen declare -A seenFall benötigen, der $seenzuvor definiert wurde (auch als skalare Variable aus der Umgebung).
Stéphane Chazelas