Bash - membalikkan array

16

Apakah ada cara sederhana untuk membalikkan array?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

jadi saya akan mendapatkan: 7 6 5 4 3 2 1
bukannya:1 2 3 4 5 6 7

nath
sumber

Jawaban:

15

Saya telah menjawab pertanyaan seperti yang tertulis, dan kode ini membalikkan array. (Mencetak elemen dalam urutan terbalik tanpa membalikkan array hanyalah sebuah forloop menghitung mundur dari elemen terakhir ke nol.) Ini adalah algoritma "swap first and last" standar.

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

Ini berfungsi untuk array dengan panjang ganjil dan genap.

roaima
sumber
Harap perhatikan bahwa ini tidak berfungsi untuk array yang jarang.
Isaac
@Isaac ada solusi di StackOverflow jika Anda perlu mengatasinya.
roaima
Dipecahkan di sini .
Isaac
18

Pendekatan lain yang tidak konvensional:

#!/bin/bash

array=(1 2 3 4 5 6 7)

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

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

echo "${array[@]}"

Keluaran:

7 6 5 4 3 2 1

Jika extdebugdiaktifkan, array BASH_ARGVberisi semua parameter posisi dalam fungsi dengan urutan terbalik.

Cyrus
sumber
Ini adalah trik yang luar biasa!
Valentin Bajrami
15

Pendekatan tidak konvensional (semuanya tidak murni bash):

  • jika semua elemen dalam array hanya satu karakter (seperti dalam pertanyaan) Anda dapat menggunakan rev:

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

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • dan jika Anda dapat menggunakan zsh:

    echo ${(Oa)array}
jimmij
sumber
baru saja melihat ke atas tac, sebagai lawan dari catcukup baik untuk diingat, TERIMA KASIH!
nath
3
Meskipun saya suka ide itu rev, saya perlu menyebutkan bahwa revtidak akan berfungsi dengan benar untuk angka dengan dua digit. Misalnya elemen array 12 menggunakan rev akan dicetak sebagai 21. Cobalah ;-)
George Vasiliou
@ GeorgeVasiliou Ya, itu hanya akan berfungsi jika semua elemen adalah satu karakter (angka, huruf, tanda baca, ...). Itu sebabnya saya juga memberikan solusi kedua, yang lebih umum.
jimmij
8

Jika Anda benar-benar ingin membalikkan dalam array lain:

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
}

Kemudian:

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

Memberi:

4 3 2 1

Ini harus menangani kasus dengan benar di mana indeks array tidak ada, misalnya Anda miliki array=([1]=1 [2]=2 [4]=4), dalam hal ini pengulangan dari 0 ke indeks tertinggi dapat menambahkan elemen tambahan, kosong.

muru
sumber
Terima kasih untuk yang satu ini, bekerja dengan cukup baik, meskipun untuk beberapa alasan shellcheckmencetak dua peringatan: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.dan untuk:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
nath
1
@nath mereka digunakan secara tidak langsung, itulah gunanya declarebaris.
muru
Pintar, tetapi perhatikan bahwa declare -ntampaknya tidak berfungsi dalam versi bash sebelum 4.3.
G-Man Mengatakan 'Reinstate Monica'
8

Untuk menukar posisi array di tempat (bahkan dengan array jarang) (sejak 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[@]}"

Pada eksekusi:

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

Untuk bash yang lebih lama, Anda perlu menggunakan loop (dalam bash (sejak 2.04)) dan menggunakan $auntuk menghindari ruang trailing:

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

Untuk bash sejak 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

Juga (menggunakan operator negasi bitwise) (sejak 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
Ishak
sumber
Mengatasi elemen-elemen array dari belakang dengan subskrip negatif tampaknya tidak berfungsi dalam versi bash sebelum 4.3.
G-Man Mengatakan 'Reinstate Monica'
1
Sebenarnya, mengatasi angka negatif telah diubah di 4.2-alpha. Dan skrip dengan nilai yang dinegasikan bekerja dari versi itu. @ G-Man p. Subskrip negatif ke array yang diindeks, sekarang diperlakukan sebagai offset dari indeks maksimum yang diberikan + 1. tetapi Bash-hacker melaporkan secara tidak benar 4.1 array yang diindeks secara numerik dapat diakses dari akhir menggunakan indeks negatif
Isaac
3

Jelek, tidak bisa dipelihara, tapi hanya satu kalimat:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
pengguna23013
sumber
Tidak sederhana, tapi lebih pendek: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
Isaac
Dan bahkan untuk array jarang:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Isaac
@Isaac Tapi tidak lagi satu-liner dan hanya jelek dan tidak dapat dipertahankan untuk versi array yang jarang, sayangnya. (Meskipun demikian, masih harus lebih cepat daripada pipa untuk array kecil.)
user23013
Secara teknis, ini adalah "satu-baris"; bukan perintah satu, ya, tapi "satu liner" itu. Saya setuju, ya, sangat jelek dan masalah pemeliharaan, tapi menyenangkan untuk dimainkan.
Isaac
1

Meskipun saya tidak akan mengatakan sesuatu yang baru dan saya juga akan menggunakan tacuntuk membalikkan array, saya pikir itu akan layak untuk disebutkan di bawah solusi single line menggunakan bash versi 4.4:

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

Pengujian:

$ 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

Ingat bahwa nama var di dalam read adalah nama sebagai array asli, jadi tidak ada array pembantu yang diperlukan untuk penyimpanan temp.

Implementasi alternatif dengan menyesuaikan 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: Saya pikir solusi di atas tidak akan berfungsi dalam bashversi di bawah 4.4karena readimplementasi fungsi bash builtin yang berbeda .

George Vasiliou
sumber
The IFSVersi bekerja tetapi juga mencetak: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Menggunakan bash 4.4-5. Anda harus menghapus ;declare -p arraydi akhir baris pertama, lalu bekerja ...
nath
1
@nath declare -phanyalah cara cepat untuk membuat bash mencetak array asli (indeks dan konten). Anda tidak perlu declare -pperintah ini di skrip asli Anda. Jika ada yang salah dalam tugas array Anda, Anda bisa berakhir dalam kasus yang ${array[0]}="1 2 3 4 5 6 10 11 12"= semua nilai disimpan dalam indeks yang sama - menggunakan gema Anda tidak akan melihat perbedaan. Untuk pencetakan array cepat menggunakan declare -p arrayakan mengembalikan Anda indeces array asli dan nilai yang sesuai di setiap indeks.
George Vasiliou
@nath By the way, read -d'\n'metode ini tidak berhasil untuk Anda?
George Vasiliou
read -d'\n'bekerja dengan baik.
nath
ahhh menangkapmu! MAAF :-)
nath
1

Untuk membalikkan array sewenang-wenang (yang mungkin mengandung sejumlah elemen dengan nilai apa pun):

Dengan zsh:

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

Dengan bash4.4+, mengingat bahwa bashvariabel tidak dapat mengandung byte NUL, Anda dapat menggunakan GNU tac -s ''pada elemen yang dicetak sebagai catatan dibatasi NUL:

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

POSIXly, untuk membalikkan susunan shell POSIX ( $@, terbuat dari $1, $2...):

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

Solusi bash murni, akan berfungsi sebagai one-liner.

$: for (( i=${#array[@]}-1; i>=0; i-- ))
>  do rev[${#rev[@]}]=${array[i]}
>  done
$: echo  "${rev[@]}"
7 6 5 4 3 2 1
Paul Hodges
sumber
bagus !!! TERIMA KASIH; di sini satu liner untuk menyalin :-) `array = (1 2 3 4 5 6 7); untuk ((i = $ {# array [@]} - 1; i> = 0; i--)); lakukan rev [$ {# rev [@]}] = $ {array [i]}; dilakukan; echo "$ {rev [@]}" `
nath
Melakukannya rev+=( "${array[i]}" )tampak lebih sederhana.
Isaac
Enam dari satu, setengah lusin lainnya. Saya tidak mengerti sintaks itu, tetapi tidak punya alasan untuk itu - hanya prasangka dan preferensi. Kamu lakukan kamu
Paul Hodges
-1

Anda juga dapat mempertimbangkan untuk menggunakan seq

array=(1 2 3 4 5 6 7)

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

di freebsd Anda dapat menghilangkan -1 parameter kenaikan:

for i in $(seq $((${#array[@]} - 1)) 0); do
    echo ${array[$i]}
done
M. Modugno
sumber
Perhatikan bahwa ini tidak membalikkan array, hanya mencetaknya dalam urutan terbalik.
roaima
Setuju, poin saya juga untuk mempertimbangkan akses indeks sebagai alternatif ..
M. Modugno