Bash - ein Array umkehren

15

Gibt es eine einfache Möglichkeit, ein Array umzukehren?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

so würde ich bekommen: 7 6 5 4 3 2 1
anstelle von:1 2 3 4 5 6 7

nath
quelle

Antworten:

14

Ich habe die Frage wie geschrieben beantwortet und dieser Code kehrt das Array um. (Das Drucken der Elemente in umgekehrter Reihenfolge, ohne das Array umzukehren, ist nur eine forSchleife, die vom letzten Element bis auf Null herunterzählt.) Dies ist ein Standardalgorithmus zum "Ersten und Letzten Tauschen".

array=(1 2 3 4 5 6 7)

min=0
max=$(( ${#array[@]} -1 ))

while [[ min -lt max ]]
do
    # Swap current first and last elements
    x="${array[$min]}"
    array[$min]="${array[$max]}"
    array[$max]="$x"

    # Move closer
    (( min++, max-- ))
done

echo "${array[@]}"

Es funktioniert für Arrays mit ungerader und gerader Länge.

Roaima
quelle
Bitte beachten Sie, dass dies bei spärlichen Arrays nicht funktioniert.
Isaac
@Isaac Auf StackOverflow gibt es eine Lösung, wenn Sie damit umgehen müssen.
Roaima
Gelöst hier .
Isaac
17

Ein weiterer unkonventioneller Ansatz:

#!/bin/bash

array=(1 2 3 4 5 6 7)

f() { array=("${BASH_ARGV[@]}"); }

shopt -s extdebug
f "${array[@]}"
shopt -u extdebug

echo "${array[@]}"

Ausgabe:

7 6 5 4 3 2 1

Wenn extdebugaktiviert, BASH_ARGVenthält das Array in einer Funktion alle Positionsparameter in umgekehrter Reihenfolge.

Cyrus
quelle
Das ist ein toller Trick!
Valentin Bajrami
14

Unkonventioneller Ansatz (alles nicht rein bash):

  • Wenn alle Elemente in einem Array nur aus einem Zeichen bestehen (wie in der Frage), können Sie Folgendes verwenden rev:

    echo "${array[@]}" | rev
  • Andernfalls:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • und wenn du kannst zsh:

    echo ${(Oa)array}
jimmij
quelle
Ich habe gerade nach oben geschaut tac, als das Gegenteil von catganz gut zu merken, DANKE!
Nath
3
Obwohl ich die Idee von mag rev, muss ich erwähnen, dass revfür Zahlen mit zwei Ziffern nicht richtig funktioniert. Beispielsweise wird ein Array-Element der 12 Verwendung von rev als gedruckt 21. Probieren Sie es aus ;-)
George Vasiliou
@GeorgeVasiliou Ja, das funktioniert nur, wenn alle Elemente aus einem Zeichen bestehen (Zahlen, Buchstaben, Interpunktionen, ...). Deshalb habe ich auch eine zweite, allgemeinere Lösung gegeben.
Jimmy
8

Wenn Sie tatsächlich das Gegenteil in einem anderen Array wollen:

reverse() {
    # first argument is the array to reverse
    # second is the output array
    declare -n arr="$1" rev="$2"
    for i in "${arr[@]}"
    do
        rev=("$i" "${rev[@]}")
    done
}

Dann:

array=(1 2 3 4)
reverse array foo
echo "${foo[@]}"

Gibt:

4 3 2 1

Dies sollte Fälle korrekt behandeln, in denen beispielsweise ein Array-Index fehlt. array=([1]=1 [2]=2 [4]=4)In diesem Fall können durch Schleifen von 0 zum höchsten Index zusätzliche leere Elemente hinzugefügt werden.

muru
quelle
Vielen Dank für dieses, es funktioniert ziemlich gut, obwohl aus irgendeinem Grund shellcheckzwei Warnungen array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
ausgegeben werden
1
@nath sie sind indirekt verwendet, das ist, was die declareZeile ist.
muru
Clever, aber beachten Sie, dass dies declare -nin Bash-Versionen vor 4.3 nicht funktioniert.
G-Man sagt, dass Monica
8

So tauschen Sie die Array-Positionen aus (auch bei spärlichen Arrays) (seit Bash 3.0):

#!/bin/bash
# Declare an sparse array to test:
array=([5]=101 [6]=202 [10]=303 [11]=404 [20]=505 [21]=606 [40]=707)
echo "Initial array values"
declare -p array

swaparray(){ local temp; temp="${array[$1]}"
             array[$1]="${array[$2]}"
             array[$2]="$temp"
           }

ind=("${!array[@]}")                         # non-sparse array of indexes.

min=-1; max="${#ind[@]}"                     # limits to one before real limits.
while [[ min++ -lt max-- ]]                  # move closer on each loop.
do
    swaparray "${ind[min]}" "${ind[max]}"    # Exchange first and last
done

echo "Final Array swapped in place"
declare -p array
echo "Final Array values"
echo "${array[@]}"

Bei der Ausführung:

./script
Initial array values
declare -a array=([5]="101" [6]="202" [10]="303" [11]="404" [20]="505" [21]="606" [40]="707")

Final Array swapped in place
declare -a array=([5]="707" [6]="606" [10]="505" [11]="404" [20]="303" [21]="202" [40]="101")

Final Array values
707 606 505 404 303 202 101

Für ältere Bashs müssen Sie eine Schleife (in Bash (seit 2.04)) verwenden und $adas folgende Leerzeichen verwenden , um das nachfolgende Leerzeichen zu vermeiden:

#!/bin/bash

array=(101 202 303 404 505 606 707)
last=${#array[@]}

a=""
for (( i=last-1 ; i>=0 ; i-- ));do
    printf '%s%s' "$a" "${array[i]}"
    a=" "
done
echo

Für Bash seit 2.03:

#!/bin/bash
array=(101 202 303 404 505 606 707)
last=${#array[@]}

a="";i=0
while [[ last -ge $((i+=1)) ]]; do 
    printf '%s%s' "$a" "${array[ last-i ]}"
    a=" "
done
echo

Auch (mit dem bitweisen Negationsoperator) (seit Bash 4.2+):

#!/bin/bash
array=(101 202 303 404 505 606 707)
last=${#array[@]}

a=""
for (( i=0 ; i<last ; i++ )); do 
    printf '%s%s' "$a" "${array[~i]}"
    a=" "
done
echo
Isaac
quelle
In Bash-Versionen vor 4.3 scheint es nicht zu funktionieren, die Elemente eines Arrays von hinten mit negativen Indizes zu adressieren.
G-Man sagt, dass Monica
1
Tatsächlich wurde die Adressierung negativer Zahlen in 4.2-alpha geändert. Und das Skript mit negierten Werten funktioniert ab dieser Version. @ G-Man p. Negative Indizes für indizierte Arrays, die jetzt als Offsets vom maximal zugewiesenen Index + 1 behandelt werden. Bash-Hacker melden jedoch falsch 4.1 Auf numerisch indizierte Arrays kann von Ende an mit negativen Indizes zugegriffen werden
Isaac
3

Hässlich, nicht zu pflegen, aber Einzeiler:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
user23013
quelle
Nicht einfacher, aber kürzer: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
Isaac
Und selbst für spärliche Arrays:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Isaac
@Isaac Aber nicht mehr einzeilig und nur noch hässlich und für die spärliche Array-Version leider nicht mehr zu pflegen. (Sollte jedoch immer noch schneller als Pipes für kleine Arrays sein.)
user23013
Nun, technisch gesehen ist es ein "Einzeiler". kein one command, ja, aber ein "one liner" ist es. Ich bin damit einverstanden, ja, sehr hässlich und ein Wartungsproblem, aber es macht Spaß, damit zu spielen.
Isaac
1

Obwohl ich nichts Neues erzählen werde und auch tacdas Array umkehren werde, wäre es dennoch erwähnenswert, die folgende einzeilige Lösung mit der bash-Version 4.4 zu erwähnen:

$ read -d'\n' -a array < <(printf '%s\n' "${array[@]}" |tac)

Testen:

$ array=(1 2 3 4 5 6 10 11 12)
$ echo "${array[@]}"
1 2 3 4 5 6 10 11 12
$ read -d'\n' -a array < <(printf '%s\n' "${array[@]}"|tac)
$ echo "${array[@]}"
12 11 10 6 5 4 3 2 1

Beachten Sie, dass der in read enthaltene Variablenname der Name des ursprünglichen Arrays ist, sodass für die temporäre Speicherung kein Hilfsarray erforderlich ist.

Alternative Implementierung durch Anpassung des IFS:

$ IFS=$'\n' read -d '' -a array < <(printf '%s\n' "${array[@]}"|tac);declare -p array
declare -a array=([0]="12" [1]="11" [2]="10" [3]="6" [4]="5" [5]="4" [6]="3" [7]="2" [8]="1")

PS: Ich denke, obige Lösungen funktionieren nicht in der folgenden bashVersion, 4.4da verschiedene readBash-Funktionen implementiert wurden.

George Vasiliou
quelle
Die IFSVersion funktioniert , aber es ist auch Druck: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Bash benutzen 4.4-5. Sie haben zu entfernen , ;declare -p arrayam Ende der ersten Zeile, dann funktioniert es ...
Nath
1
@nath declare -pist nur ein schneller Weg, um bash das echte Array (Index und Inhalt) drucken zu lassen. Sie brauchen diesen declare -pBefehl nicht in Ihrem echten Skript. Wenn in Ihren Array-Zuweisungen etwas schief geht, kann dies dazu führen, dass ${array[0]}="1 2 3 4 5 6 10 11 12"= alle Werte im selben Index gespeichert sind - mit Echo werden Sie keinen Unterschied feststellen. Für einen schnellen Array-Ausdruck erhalten declare -p arraySie mit die tatsächlichen Array-Indices und den entsprechenden Wert in jedem Index.
George Vasiliou
@nath Übrigens hat die read -d'\n'Methode bei dir nicht funktioniert?
George Vasiliou
read -d'\n'funktioniert gut.
Nath
ahhh hast du! ENTSCHULDIGUNG :-)
nath
1

So kehren Sie ein beliebiges Array um (das beliebig viele Elemente mit beliebigen Werten enthalten kann):

Mit zsh:

array_reversed=("${(@Oa)array}")

Mit bash4.4+, da bashVariablen nicht enthalten NUL - Bytes wie auch immer, Sie GNU verwenden können , tac -s ''auf die Elemente gedruckt als NUL Datensätze begrenzt:

readarray -td '' array_reversed < <(
  ((${#array[@]})) && printf '%s\0' "${array[@]}" | tac -s '')

POSIXly, um das POSIX-Shell-Array ( $@, aus $1, $2...) umzukehren :

code='set --'
n=$#
while [ "$n" -gt 0 ]; do
  code="$code \"\${$n}\""
  n=$((n - 1))
done
eval "$code"
Stéphane Chazelas
quelle
1

Reine bash Lösung, würde als Einzeiler funktionieren.

$: for (( i=${#array[@]}-1; i>=0; i-- ))
>  do rev[${#rev[@]}]=${array[i]}
>  done
$: echo  "${rev[@]}"
7 6 5 4 3 2 1
Paul Hodges
quelle
Schön!!! DANKE; hier der eine Liner zum kopieren :-) `array = (1 2 3 4 5 6 7); for ((i = $ {# array [@]} - 1; i> = 0; i--)); do rev [$ {# rev [@]}] = $ {array [i]}; erledigt; echo "$ {rev [@]}" `
nath
Das rev+=( "${array[i]}" )scheint einfacher zu sein.
Isaac
Sechs von einem, ein halbes Dutzend von dem anderen. Ich bin nicht mit dieser Syntax einverstanden, habe aber keinen Grund dafür - nur Vorurteile und Vorlieben. Du machst du.
Paul Hodges
-1

Sie können auch in Betracht ziehen, seq

array=(1 2 3 4 5 6 7)

for i in $(seq $((${#array[@]} - 1)) -1 0); do
    echo ${array[$i]}
done

In freebsd können Sie den Inkrement-Parameter -1 weglassen:

for i in $(seq $((${#array[@]} - 1)) 0); do
    echo ${array[$i]}
done
M. Modugno
quelle
Beachten Sie, dass das Array dadurch nicht umgekehrt wird, sondern lediglich in umgekehrter Reihenfolge ausgedruckt wird.
Roaima
Einverstanden, mein Punkt war auch, den Zugang zu Indizes als Alternative zu betrachten.
M. Modugno