Wie definiere ich Hash-Tabellen in Bash?


Antworten:


938

Bash 4

Bash 4 unterstützt diese Funktion nativ. Stellen Sie sicher , dass Ihr Skript Hash - Bang ist #!/usr/bin/env bashoder #!/bin/bashso dass Sie nicht am Ende mit sh. Stellen Sie sicher , entweder sind Ausführung Skript direkt, oder führen Sie scriptmit bash script. (Nicht ausgeführt wird tatsächlich ein Bash - Skript mit Bash nicht passieren, und wird wirklich verwirrend!)

Sie deklarieren ein assoziatives Array, indem Sie Folgendes tun:

declare -A animals

Sie können es mit dem normalen Array-Zuweisungsoperator mit Elementen füllen. Wenn Sie beispielsweise eine Karte haben möchten von animal[sound(key)] = animal(value):

animals=( ["moo"]="cow" ["woof"]="dog")

Oder führen Sie sie zusammen:

declare -A animals=( ["moo"]="cow" ["woof"]="dog")

Verwenden Sie sie dann wie normale Arrays. Verwenden

  • animals['key']='value' Wert setzen

  • "${animals[@]}" um die Werte zu erweitern

  • "${!animals[@]}"(beachten Sie die !), um die Tasten zu erweitern

Vergiss nicht, sie zu zitieren:

echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done

Bash 3

Vor Bash 4 haben Sie keine assoziativen Arrays. Nicht verwenden eval, um sie zu emulieren . Vermeiden Sie evalwie die Pest, denn es ist die Pest von Shell Scripting. Der wichtigste Grund ist, dass evalIhre Daten als ausführbarer Code behandelt werden (es gibt auch viele andere Gründe).

In erster Linie : Erwägen Sie ein Upgrade auf Bash 4. Dies erleichtert Ihnen den gesamten Vorgang erheblich.

Wenn es einen Grund gibt, warum Sie kein Upgrade durchführen können, declareist dies eine weitaus sicherere Option. Es wertet Daten nicht wie Bash-Code aus evalund erlaubt daher nicht so einfach das Einfügen von willkürlichem Code.

Bereiten wir die Antwort vor, indem wir die Konzepte einführen:

Erstens die Indirektion.

$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow

Zweitens declare:

$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow

Bring sie zusammen:

# Set a value:
declare "array_$index=$value"

# Get a value:
arrayGet() { 
    local array=$1 index=$2
    local i="${array}_$index"
    printf '%s' "${!i}"
}

Lass es uns benutzen:

$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow

Hinweis: declareKann nicht in eine Funktion eingefügt werden. Bei jeder Verwendung declareinnerhalb einer Bash-Funktion wird die von ihr lokal erstellte Variable in den Bereich dieser Funktion verschoben, was bedeutet, dass wir damit nicht auf globale Arrays zugreifen oder diese ändern können. (In Bash 4 können Sie deklarieren -g verwenden, um globale Variablen zu deklarieren. In Bash 4 können Sie jedoch zunächst assoziative Arrays verwenden, um diese Problemumgehung zu vermeiden.)

Zusammenfassung:

  • Aktualisieren Sie auf Bash 4 und verwenden Sie es declare -Afür assoziative Arrays.
  • Verwenden Sie die declareOption, wenn Sie kein Upgrade durchführen können.
  • Verwenden Sie awkstattdessen und vermeiden Sie das Problem insgesamt.

1
@ Richard: Vermutlich verwenden Sie Bash nicht wirklich. Ist Ihr Hashbang sh anstelle von Bash, oder rufen Sie Ihren Code auf andere Weise mit sh auf? Versuchen Sie, dies vor Ihrer Deklaration zu korrigieren: Echo "$ BASH_VERSION $ POSIXLY_CORRECT", es sollte ausgegeben werden 4.xund nicht y.
lhunath

5
Kein Upgrade möglich: Der einzige Grund, warum ich Skripte in Bash schreibe, ist die Portabilität "überall ausführen". Wenn Sie sich also auf eine nicht universelle Funktion von Bash verlassen, wird dieser Ansatz ausgeschlossen. Was schade ist, denn sonst wäre es eine hervorragende Lösung für mich gewesen!
Steve Pitchers

3
Es ist eine Schande, dass OSX immer noch standardmäßig Bash 3 verwendet, da dies für viele Menschen die "Standardeinstellung" darstellt. Ich dachte, die ShellShock-Angst könnte der Schub gewesen sein, den sie brauchten, aber anscheinend nicht.
Ken

13
@ken es ist ein Lizenzproblem. Bash unter OSX steckt bei der neuesten Version ohne GPLv3-Lizenz fest.
lhunath

2
... oder sudo port install bashfür diejenigen (mit Bedacht IMHO), die nicht bereit sind, Verzeichnisse im PATH für alle Benutzer ohne explizite prozessbezogene Eskalation von Berechtigungen beschreibbar zu machen.
Charles Duffy

125

Es gibt eine Parametersubstitution, obwohl es auch ein Un-PC sein kann ... wie Indirektion.

#!/bin/bash

# Array pretending to be a Pythonic dictionary
ARRAY=( "cow:moo"
        "dinosaur:roar"
        "bird:chirp"
        "bash:rock" )

for animal in "${ARRAY[@]}" ; do
    KEY="${animal%%:*}"
    VALUE="${animal##*:}"
    printf "%s likes to %s.\n" "$KEY" "$VALUE"
done

printf "%s is an extinct animal which likes to %s\n" "${ARRAY[1]%%:*}" "${ARRAY[1]##*:}"

Der BASH 4-Weg ist natürlich besser, aber wenn Sie einen Hack brauchen ... reicht nur ein Hack. Sie können das Array / den Hash mit ähnlichen Techniken durchsuchen.


5
Ich würde das ändern, VALUE=${animal#*:}um den Fall zu schützen, in demARRAY[$x]="caesar:come:see:conquer"
Glenn Jackman

2
Es ist auch nützlich, doppelte Anführungszeichen um das $ {ARRAY [@]} zu setzen, falls die Schlüssel oder Werte Leerzeichen enthalten, wie infor animal in "${ARRAY[@]}"; do
devguydavid,

1
Aber ist die Effizienz nicht ziemlich schlecht? Ich denke an O (n * m), wenn Sie mit einer anderen Liste von Schlüsseln vergleichen möchten, anstatt an O (n) mit geeigneten Hashmaps (konstante Zeitsuche, O (1) für einen einzelnen Schlüssel).
CodeManX

1
Bei der Idee geht es weniger um Effizienz als vielmehr um Verständnis / Lesefähigkeit für Personen mit einem Hintergrund in Perl, Python oder sogar Bash 4. Ermöglicht das Schreiben auf ähnliche Weise.
Bubnoff

1
@CoDEmanX: Dies ist ein Hack , eine clevere und elegante, aber immer noch rudimentäre Problemumgehung , um den armen Seelen zu helfen, die 2007 noch mit Bash 3.x feststeckten. In einem so einfachen Code können Sie keine "richtigen Hashmaps" oder Effizienzaspekte erwarten.
MestreLion

85

Das habe ich hier gesucht:

declare -A hashmap
hashmap["key"]="value"
hashmap["key2"]="value2"
echo "${hashmap["key"]}"
for key in ${!hashmap[@]}; do echo $key; done
for value in ${hashmap[@]}; do echo $value; done
echo hashmap has ${#hashmap[@]} elements

Dies hat bei Bash 4.1.5 bei mir nicht funktioniert:

animals=( ["moo"]="cow" )

2
Beachten Sie, dass der Wert möglicherweise keine Leerzeichen enthält, andernfalls fügen Sie mehrere Elemente gleichzeitig hinzu
rubo77

6
Upvote für die Syntax hashmap ["key"] = "value", die auch mir in der ansonsten fantastisch akzeptierten Antwort fehlte.
Thomanski

@ rubo77 Schlüssel weder, es fügt mehrere Schlüssel hinzu. Wie kann man das umgehen?
Xeverous

25

Sie können die Schnittstelle hput () / hget () weiter ändern, sodass Sie Hashes wie folgt benannt haben:

hput() {
    eval "$1""$2"='$3'
}

hget() {
    eval echo '${'"$1$2"'#hash}'
}

und dann

hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`

Auf diese Weise können Sie andere Karten definieren, die nicht in Konflikt stehen (z. B. "Hauptstädte", die die Ländersuche nach Hauptstadt durchführen). Aber ich denke, Sie werden feststellen, dass dies alles ziemlich schrecklich ist, was die Leistung betrifft.

Wenn Sie wirklich eine schnelle Hash-Suche wünschen, gibt es einen schrecklichen, schrecklichen Hack, der wirklich gut funktioniert. Es ist dies: Schreiben Sie Ihre Schlüssel / Werte in eine temporäre Datei, eine pro Zeile, und verwenden Sie dann 'grep "^ $ key"', um sie herauszuholen. Verwenden Sie dazu Pipes mit cut oder awk oder sed oder was auch immer, um die Werte abzurufen.

Wie ich schon sagte, es klingt schrecklich und es klingt so, als ob es langsam sein und alle möglichen unnötigen E / A-Vorgänge ausführen sollte, aber in der Praxis ist es sehr schnell (der Festplatten-Cache ist fantastisch, nicht wahr?), Selbst für sehr großen Hash Tabellen. Sie müssen die Eindeutigkeit der Schlüssel selbst erzwingen usw. Selbst wenn Sie nur einige hundert Einträge haben, wird die Kombination aus Ausgabedatei und grep erheblich schneller sein - meiner Erfahrung nach um ein Vielfaches schneller. Es frisst auch weniger Speicher.

Hier ist eine Möglichkeit, dies zu tun:

hinit() {
    rm -f /tmp/hashmap.$1
}

hput() {
    echo "$2 $3" >> /tmp/hashmap.$1
}

hget() {
    grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };'
}

hinit capitals
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid

echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`

1
Großartig! Sie können es sogar wiederholen: für i in $ (compgen-A variable capitols); do hget "$ i" "" done
zhaorufei

22

Verwenden Sie einfach das Dateisystem

Das Dateisystem ist eine Baumstruktur, die als Hash-Map verwendet werden kann. Ihre Hash-Tabelle ist ein temporäres Verzeichnis, Ihre Schlüssel sind Dateinamen und Ihre Werte sind Dateiinhalte. Der Vorteil ist, dass es große Hashmaps verarbeiten kann und keine bestimmte Shell benötigt.

Hashtable-Erstellung

hashtable=$(mktemp -d)

Fügen Sie ein Element hinzu

echo $value > $hashtable/$key

Lesen Sie ein Element

value=$(< $hashtable/$key)

Performance

Natürlich ist es langsam, aber nicht so langsam. Ich habe es auf meinem Computer mit einer SSD und btrfs getestet und es liest / schreibt ungefähr 3000 Elemente pro Sekunde .


1
Welche Version von Bash unterstützt mkdir -d? (Nicht 4.3, auf Ubuntu 14. Ich würde zurückgreifen mkdir /run/shm/foo, oder wenn das RAM füllen würde mkdir /tmp/foo.)
Camille Goudeseune

1
Vielleicht mktemp -dwar stattdessen gemeint?
Reid Ellis

2
Neugierig, was ist der Unterschied zwischen $value=$(< $hashtable/$key)und value=$(< $hashtable/$key)? Vielen Dank!
Helin Wang

1
"auf meinem Computer getestet" Dies klingt nach einer großartigen Möglichkeit, ein Loch in Ihre SSD zu brennen. Nicht alle Linux-Distributionen verwenden standardmäßig tmpfs.
Kirbyfan64sos

Ich verarbeite ungefähr 50000 Hashes. Perl und PHP machen es ein Haar unter 1/2 Sekunde. Knoten in 1 Sekunde und so. Die FS-Option klingt langsam. Können wir jedoch sicherstellen, dass die Dateien irgendwie nur im RAM vorhanden sind?
Rolf

14
hput () {
  eval hash"$1"='$2'
}

hget () {
  eval echo '${hash'"$1"'#hash}'
}
hput France Paris
hput Netherlands Amsterdam
hput Spain Madrid
echo `hget France` and `hget Netherlands` and `hget Spain`

$ sh hash.sh
Paris and Amsterdam and Madrid

31
Seufz, das scheint unnötig beleidigend und es ist sowieso ungenau. Man würde die Eingabevalidierung, das Escapezeichen oder die Codierung (siehe, ich weiß es tatsächlich) nicht in die Eingeweide der Hash-Tabelle einfügen, sondern in einen Wrapper und so bald wie möglich nach der Eingabe.
DigitalRoss

@DigitalRoss können Sie erklären, was die Verwendung von #hash in eval echo '$ {hash' "$ 1" '# hash}' ist . für mich scheint es mir als kommentar nicht mehr als das. Hat #hash hier eine besondere Bedeutung?
Sanjay

@Sanjay ${var#start}entfernt den Text Start von Anfang an dem in der Variablen gespeicherten Wert var .
Jpaugh

11

Stellen Sie sich eine Lösung vor, bei der das eingebaute Bash-Read verwendet wird , wie im Code-Snippet eines folgenden ufw-Firewall-Skripts dargestellt. Dieser Ansatz hat den Vorteil, dass beliebig viele begrenzte Feldsätze (nicht nur 2) verwendet werden. Wir haben die | verwendet Trennzeichen, da für Portbereichsspezifizierer möglicherweise ein Doppelpunkt erforderlich ist, z. B. 6001: 6010 .

#!/usr/bin/env bash

readonly connections=(       
                            '192.168.1.4/24|tcp|22'
                            '192.168.1.4/24|tcp|53'
                            '192.168.1.4/24|tcp|80'
                            '192.168.1.4/24|tcp|139'
                            '192.168.1.4/24|tcp|443'
                            '192.168.1.4/24|tcp|445'
                            '192.168.1.4/24|tcp|631'
                            '192.168.1.4/24|tcp|5901'
                            '192.168.1.4/24|tcp|6566'
)

function set_connections(){
    local range proto port
    for fields in ${connections[@]}
    do
            IFS=$'|' read -r range proto port <<< "$fields"
            ufw allow from "$range" proto "$proto" to any port "$port"
    done
}

set_connections

2
@CharlieMartin: Lesen ist eine sehr mächtige Funktion und wird von vielen Bash-Programmierern nicht ausreichend genutzt. Es ermöglicht kompakte Formen der lisp-ähnlichen Listenverarbeitung. Zum Beispiel können wir im obigen Beispiel nur das erste Element IFS=$'|' read -r first rest <<< "$fields"
entfernen

6

Ich stimme @lhunath und anderen zu, dass das assoziative Array der richtige Weg für Bash 4 ist. Wenn Sie an Bash 3 festhalten (OSX, alte Distributionen, die Sie nicht aktualisieren können), können Sie auch expr verwenden, was überall eine Zeichenfolge sein sollte und reguläre Ausdrücke. Ich mag es besonders, wenn das Wörterbuch nicht zu groß ist.

  1. Wählen Sie 2 Trennzeichen, die Sie in Schlüsseln und Werten nicht verwenden (z. B. ',' und ':').
  2. Schreiben Sie Ihre Karte als Zeichenfolge (beachten Sie das Trennzeichen '' auch am Anfang und am Ende).

    animals=",moo:cow,woof:dog,"
  3. Verwenden Sie einen regulären Ausdruck, um die Werte zu extrahieren

    get_animal {
        echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
    }
  4. Teilen Sie die Zeichenfolge, um die Elemente aufzulisten

    get_animal_items {
        arr=$(echo "${animals:1:${#animals}-2}" | tr "," "\n")
        for i in $arr
        do
            value="${i##*:}"
            key="${i%%:*}"
            echo "${value} likes to $key"
        done
    }

Jetzt können Sie es verwenden:

$ animal = get_animal "moo"
cow
$ get_animal_items
cow likes to moo
dog likes to woof

5

Ich mochte die Antwort von Al P sehr, wollte aber, dass die Eindeutigkeit billig durchgesetzt wird, also ging ich noch einen Schritt weiter - benutze ein Verzeichnis. Es gibt einige offensichtliche Einschränkungen (Verzeichnisdateilimits, ungültige Dateinamen), aber es sollte in den meisten Fällen funktionieren.

hinit() {
    rm -rf /tmp/hashmap.$1
    mkdir -p /tmp/hashmap.$1
}

hput() {
    printf "$3" > /tmp/hashmap.$1/$2
}

hget() {
    cat /tmp/hashmap.$1/$2
}

hkeys() {
    ls -1 /tmp/hashmap.$1
}

hdestroy() {
    rm -rf /tmp/hashmap.$1
}

hinit ids

for (( i = 0; i < 10000; i++ )); do
    hput ids "key$i" "value$i"
done

for (( i = 0; i < 10000; i++ )); do
    printf '%s\n' $(hget ids "key$i") > /dev/null
done

hdestroy ids

Es ist auch ein bisschen besser in meinen Tests.

$ time bash hash.sh 
real    0m46.500s
user    0m16.767s
sys     0m51.473s

$ time bash dirhash.sh 
real    0m35.875s
user    0m8.002s
sys     0m24.666s

Ich dachte nur, ich würde mitmachen. Prost!

Bearbeiten: Hinzufügen von hdestroy ()


3

Zwei Dinge, Sie können Speicher anstelle von / tmp in jedem Kernel 2.6 verwenden, indem Sie / dev / shm (Redhat) verwenden. Andere Distributionen können variieren. Außerdem kann hget mit read wie folgt neu implementiert werden:

function hget {

  while read key idx
  do
    if [ $key = $2 ]
    then
      echo $idx
      return
    fi
  done < /dev/shm/hashmap.$1
}

Durch die Annahme, dass alle Schlüssel eindeutig sind, schließt die Rückgabe die Leseschleife kurz und verhindert, dass alle Einträge durchgelesen werden müssen. Wenn Ihre Implementierung doppelte Schlüssel haben kann, lassen Sie die Rückgabe einfach weg. Dies spart die Kosten für das Lesen und Gabeln von grep und awk. Die Verwendung von / dev / shm für beide Implementierungen ergab Folgendes unter Verwendung von time hget für einen Hash mit 3 Einträgen, der nach dem letzten Eintrag suchte:

Grep / Awk:

hget() {
    grep "^$2 " /dev/shm/hashmap.$1 | awk '{ print $2 };'
}

$ time echo $(hget FD oracle)
3

real    0m0.011s
user    0m0.002s
sys     0m0.013s

Lesen / Echo:

$ time echo $(hget FD oracle)
3

real    0m0.004s
user    0m0.000s
sys     0m0.004s

Bei mehreren Aufrufen habe ich nie weniger als 50% Verbesserung gesehen. Dies alles kann aufgrund der Verwendung von auf Gabel über Kopf zurückgeführt werden /dev/shm.


3

Ein Mitarbeiter hat gerade diesen Thread erwähnt. Ich habe Hash-Tabellen unabhängig in Bash implementiert und es ist nicht abhängig von Version 4. Aus einem meiner Blog-Posts im März 2010 (vor einigen Antworten hier ...) mit dem Titel Hash-Tabellen in Bash :

Ich früher verwendet , cksumum Hash aber seit übersetzt Java - String hashCode zu nativem bash / zsh.

# Here's the hashing function
ht() {
  local h=0 i
  for (( i=0; i < ${#1}; i++ )); do
    let "h=( (h<<5) - h ) + $(printf %d \'${1:$i:1})"
    let "h |= h"
  done
  printf "$h"
}

# Example:

myhash[`ht foo bar`]="a value"
myhash[`ht baz baf`]="b value"

echo ${myhash[`ht baz baf`]} # "b value"
echo ${myhash[@]} # "a value b value" though perhaps reversed
echo ${#myhash[@]} # "2" - there are two values (note, zsh doesn't count right)

Es ist nicht bidirektional und die eingebaute Methode ist viel besser, sollte aber auch nicht wirklich verwendet werden. Bash ist für schnelle Unikate gedacht, und solche Dinge sollten ziemlich selten mit Komplexität verbunden sein, die Hashes erfordern könnte, außer vielleicht bei Ihnen ~/.bashrcund Ihren Freunden.


Der Link in der Antwort ist beängstigend! Wenn Sie darauf klicken, stecken Sie in einer Umleitungsschleife fest. Bitte aktualisieren.
Rakib

1
@MohammadRakibAmin - Ja, meine Website ist nicht verfügbar und ich bezweifle, dass ich meinen Blog wiederbeleben werde. Ich habe den obigen Link auf eine archivierte Version aktualisiert. Danke für Ihr Interesse!
Adam Katz

2

Vor Bash 4 gibt es keine gute Möglichkeit, assoziative Arrays in Bash zu verwenden. Am besten verwenden Sie eine interpretierte Sprache, die solche Dinge wie awk tatsächlich unterstützt. Auf der anderen Seite tut Bash 4 sie unterstützen.

In Bezug auf weniger gute Möglichkeiten in Bash 3 finden Sie hier eine Referenz, die möglicherweise hilfreich ist: http://mywiki.wooledge.org/BashFAQ/006


2

Bash 3-Lösung:

Beim Lesen einiger Antworten habe ich eine kurze kleine Funktion zusammengestellt, die ich zurückgeben möchte, um anderen zu helfen.

# Define a hash like this
MYHASH=("firstName:Milan"
        "lastName:Adamovsky")

# Function to get value by key
getHashKey()
 {
  declare -a hash=("${!1}")
  local key
  local lookup=$2

  for key in "${hash[@]}" ; do
   KEY=${key%%:*}
   VALUE=${key#*:}
   if [[ $KEY == $lookup ]]
   then
    echo $VALUE
   fi
  done
 }

# Function to get a list of all keys
getHashKeys()
 {
  declare -a hash=("${!1}")
  local KEY
  local VALUE
  local key
  local lookup=$2

  for key in "${hash[@]}" ; do
   KEY=${key%%:*}
   VALUE=${key#*:}
   keys+="${KEY} "
  done

  echo $keys
 }

# Here we want to get the value of 'lastName'
echo $(getHashKey MYHASH[@] "lastName")


# Here we want to get all keys
echo $(getHashKeys MYHASH[@])

Ich denke, das ist ein ziemlich ordentlicher Ausschnitt. Es könnte eine kleine Bereinigung gebrauchen (allerdings nicht viel). In meiner Version habe ich 'Schlüssel' in 'Paar' umbenannt und KEY und VALUE in Kleinbuchstaben geschrieben (weil ich beim Exportieren von Variablen Großbuchstaben verwende). Ich habe auch getHashKey in getHashValue umbenannt und sowohl Schlüssel als auch Wert lokal gemacht (manchmal möchte man jedoch, dass sie nicht lokal sind). In getHashKeys ordne ich dem Wert nichts zu. Ich verwende Semikolon zur Trennung, da meine Werte URLs sind.

0

Ich habe auch den bash4-Weg benutzt, aber ich finde einen nervigen Fehler.

Ich musste den assoziativen Array-Inhalt dynamisch aktualisieren, also habe ich Folgendes verwendet:

for instanceId in $instanceList
do
   aws cloudwatch describe-alarms --output json --alarm-name-prefix $instanceId| jq '.["MetricAlarms"][].StateValue'| xargs | grep -E 'ALARM|INSUFFICIENT_DATA'
   [ $? -eq 0 ] && statusCheck+=([$instanceId]="checkKO") || statusCheck+=([$instanceId]="allCheckOk"
done

Ich finde heraus, dass das Anhängen von bash 4.3.11 an einen vorhandenen Schlüssel im Diktat dazu führte, dass der Wert angehängt wurde, falls er bereits vorhanden war. Nach einiger Wiederholung war der Inhalt des Werts beispielsweise "checkKOcheckKOallCheckOK" und dies war nicht gut.

Kein Problem mit Bash 4.3.39, bei dem das Anhängen eines vorhandenen Schlüssels bedeutet, den tatsächlichen Wert zu ersetzen, wenn er bereits vorhanden ist.

Ich habe dieses Problem gelöst, indem ich das assoziative Array statusCheck vor dem Cicle bereinigt / deklariert habe:

unset statusCheck; declare -A statusCheck

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.