Bash - odwróć tablicę

16

Czy istnieje prosty sposób na odwrócenie tablicy?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

więc otrzymałbym: 7 6 5 4 3 2 1
zamiast:1 2 3 4 5 6 7

nath
źródło

Odpowiedzi:

15

Odpowiedziałem na pytanie, jak napisano, a ten kod odwraca tablicę. (Drukowanie elementów w odwrotnej kolejności bez odwracania tablicy to tylko forpętla odliczająca od ostatniego elementu do zera.) Jest to standardowy algorytm „zamień pierwszy i ostatni”.

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[@]}"

Działa dla tablic o nieparzystej i parzystej długości.

roaima
źródło
Należy pamiętać, że nie działa to w przypadku rzadkich tablic.
Izaak,
@Isaac istnieje rozwiązanie na StackOverflow, jeśli chcesz je obsłużyć.
roaima
Rozwiązany tutaj .
Izaak
18

Kolejne niekonwencjonalne podejście:

#!/bin/bash

array=(1 2 3 4 5 6 7)

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

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

echo "${array[@]}"

Wynik:

7 6 5 4 3 2 1

Jeśli extdebugjest włączone, tablica BASH_ARGVzawiera w funkcji wszystkie parametry pozycyjne w odwrotnej kolejności.

Cyrus
źródło
To niesamowita sztuczka!
Valentin Bajrami,
15

Niekonwencjonalne podejście (wszystkie nie są czyste bash):

  • jeśli wszystkie elementy w tablicy są tylko jednym znakiem (jak w pytaniu), możesz użyć rev:

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

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • a jeśli możesz użyć zsh:

    echo ${(Oa)array}
jimmij
źródło
właśnie patrzyłem w górę tac, jako przeciwieństwo catcałkiem dobrego do zapamiętania, DZIĘKI!
nath
3
Chociaż podoba mi się pomysł rev, muszę wspomnieć, że revnie będzie działać poprawnie dla liczb z dwiema cyframi. Na przykład element tablicy 12 wykorzystujący rev zostanie wydrukowany jako 21. Spróbuj ;-)
George Vasiliou
@GeorgeVasiliou Tak, to zadziała tylko wtedy, gdy wszystkie elementy są jednym znakiem (cyfry, litery, znaki interpunkcyjne, ...). Dlatego podałem również drugie, bardziej ogólne rozwiązanie.
jimmij
8

Jeśli faktycznie chcesz odwrócić w innej tablicy:

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
}

Następnie:

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

Daje:

4 3 2 1

Powinno to poprawnie obsługiwać przypadki, w których brakuje indeksu tablicowego, powiedzmy, że miałeś array=([1]=1 [2]=2 [4]=4), w którym to przypadku zapętlenie od 0 do najwyższego indeksu może dodawać dodatkowe, puste elementy.

muru
źródło
Dzięki za to, działa całkiem dobrze, choć z jakiegoś powodu shellcheckdrukuje dwa ostrzeżenia: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.i dla:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
nath
1
@nath są używane pośrednio, po to declarejest linia.
muru
Sprytne, ale zauważ, że declare -nwydaje się , że nie działa w wersjach bash przed 4.3.
G-Man mówi „Przywróć Monikę”
8

Aby zamienić pozycje tablic na miejscu (nawet przy rzadkich tablicach) (od wersji 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[@]}"

Po wykonaniu:

./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

W przypadku starszej wersji bash musisz użyć pętli (w wersji bash (od 2.04)) i użyć, $aaby uniknąć końcowego miejsca:

#!/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

Dla bash od 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

Również (używając bitowego operatora negacji) (od 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
Izaak
źródło
Adresowanie elementów tablicy od końca do tyłu za pomocą ujemnych indeksów dolnych wydaje się nie działać w wersjach bash wcześniejszych niż 4.3.
G-Man mówi „Przywróć Monikę”
1
W rzeczywistości adresowanie liczb ujemnych zmieniono na 4,2-alfa. A skrypt z zanegowanymi wartościami działa z tej wersji. @ G-Man p. Negatywne indeksy dolne do indeksowanych tablic, teraz traktowane jako przesunięcia względem maksymalnego przypisanego indeksu + 1, ale hakerzy Bash niepoprawnie zgłaszają 4.1 tablice indeksowane numerycznie można uzyskać od końca przy użyciu indeksów ujemnych
Isaac
3

Brzydki, niemożliwy do utrzymania, ale jednowarstwowy:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
użytkownik23013
źródło
Nie prostsze, ale krócej: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
Izaak
A nawet w przypadku rzadkich tablic:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Izaak
@Isaac Ale już nie jest to jedna linijka i tylko brzydka i niemożliwa do utrzymania dla rzadkiej wersji tablicy, niestety. (Wciąż powinno być jednak szybsze niż rury dla małych tablic.)
user23013
Cóż, technicznie rzecz biorąc, jest to „jedna linijka”; tak, nie jest to jedno polecenie, ale „jedna linijka”. Zgadzam się, tak, bardzo brzydki i problem z utrzymaniem, ale fajnie się z nim bawię.
Izaak
1

Chociaż nie powiem nic nowego i użyję również tacdo odwrócenia tablicy, ale warto wspomnieć o rozwiązaniu jednowierszowym przy użyciu wersji bash 4.4:

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

Testowanie:

$ 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

Pamiętaj, że nazwa var wewnątrz odczytu jest nazwą oryginalnej tablicy, więc nie jest wymagana tablica pomocnicza do przechowywania w temp.

Alternatywne wdrożenie poprzez dostosowanie 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: Myślę, że powyższe rozwiązania nie będą działać w bashponiższej wersji z 4.4powodu różnych readimplementacji wbudowanych funkcji bash.

George Vasiliou
źródło
IFSWersja działa, ale także drukowania: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Korzystanie z bash 4.4-5. Musisz usunąć ;declare -p arrayna końcu pierwszego wiersza, a następnie
zadziała
1
@nath declare -pto tylko szybki sposób, aby bash wydrukował prawdziwą tablicę (indeks i zawartość). Nie potrzebujesz tego declare -ppolecenia w swoim prawdziwym skrypcie. Jeśli coś pójdzie nie tak z przypisaniami tablic, możesz skończyć w przypadku, że ${array[0]}="1 2 3 4 5 6 10 11 12"= wszystkie wartości zapisane w tym samym indeksie - używając echa nie zobaczysz żadnej różnicy. W przypadku szybkiego wydruku tablic użycie declare -p arrayzwróci rzeczywiste indeksy tablic i odpowiednią wartość w każdym indeksie.
George Vasiliou,
@nath Nawiasem mówiąc, read -d'\n'metoda nie działała dla Ciebie?
George Vasiliou,
read -d'\n'działa w porządku.
nath
Ach, mam cię! PRZEPRASZAMY :-)
nath
1

Aby odwrócić dowolną tablicę (która może zawierać dowolną liczbę elementów o dowolnych wartościach):

Z zsh:

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

W wersji bash4.4+, biorąc pod uwagę, że bashzmienne i tak nie mogą zawierać bajtów NUL, możesz używać GNU tac -s ''na elementach drukowanych jako rekordy rozdzielane NUL:

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

POSIXly, aby odwrócić tablicę POSIX powłoki ( $@, wykonaną z $1, $2...)

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

Rozwiązanie Pure Bash działałoby jako jedno-liniowy.

$: for (( i=${#array[@]}-1; i>=0; i-- ))
>  do rev[${#rev[@]}]=${array[i]}
>  done
$: echo  "${rev[@]}"
7 6 5 4 3 2 1
Paul Hodges
źródło
niezłe!!! DZIĘKI; tutaj jedna linijka do skopiowania :-) `array = (1 2 3 4 5 6 7); for ((i = $ {# array [@]} - 1; i> = 0; i--)); wykonaj rev [$ {# rev [@]}] = $ {array [i]}; gotowy; echo "$ {rev [@]}" '
nath
Robienie rev+=( "${array[i]}" )wydaje się prostsze.
Izaak
Sześć z jednego, pół tuzina drugiego. Nie rezygnuję z tej składni, ale nie mam ku temu powodu - tylko uprzedzenia i preferencje. Ty robisz
Paul Hodges
-1

możesz również rozważyć użycie seq

array=(1 2 3 4 5 6 7)

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

we Freebsd możesz pominąć parametr przyrostu -1:

for i in $(seq $((${#array[@]} - 1)) 0); do
    echo ${array[$i]}
done
M. Modugno
źródło
Zauważ, że to nie odwraca tablicy, a jedynie drukuje ją w odwrotnej kolejności.
roaima,
Zgadzam się, moim celem było również rozważenie dostępu do indeksów jako alternatywy.
M. Modugno