Bash - bir diziyi ters çevir


16

Bir diziyi tersine çevirmenin basit bir yolu var mı?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

böylece alacağım: 7 6 5 4 3 2 1
yerine:1 2 3 4 5 6 7

Yanıtlar:


15

Soruyu yazılı olarak yanıtladım ve bu kod diziyi tersine çevirir. (Diziyi tersine çevirmeden öğeleri ters sırada yazdırmak, yalnızca forson öğeden sıfıra geri sayım yapan bir döngüdür.) Bu standart bir "ilk ve son değiştirme" algoritmasıdır.

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

Tek ve eşit uzunlukta diziler için çalışır.


Lütfen bunun seyrek diziler için çalışmadığını unutmayın.
Isaac

@Isaac, bunları işlemeniz gerekirse StackOverflow üzerinde bir çözüm var.
roaima

Burada çözüldü .
Isaac

18

Başka bir alışılmadık yaklaşım:

#!/bin/bash

array=(1 2 3 4 5 6 7)

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

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

echo "${array[@]}"

Çıktı:

7 6 5 4 3 2 1

Eğer extdebugetkinleştirildiğinde, dizi BASH_ARGVters sırada tüm konumsal parametreler işlevde içerir.


Bu harika bir numara!
Valentin Bajrami

15

Alışılmadık yaklaşım (hepsi saf değil bash):

  • bir dizideki tüm öğeler yalnızca bir karakterse (sorudaki gibi) kullanabilirsiniz rev:

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

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • ve eğer kullanabiliyorsanız zsh:

    echo ${(Oa)array}

Sadece yukarı arıyordum tactersi olarak, cat, TEŞEKKÜRLER hatırlamak oldukça iyi!
Nath

3
Ben fikrini seviyorum rağmen , iki basamaklı sayılar için doğru çalışmayacak revbahsetmek gerekir rev. Örneğin 12 rev kullanımının bir dizi öğesi olarak yazdırılacaktır 21. Bir deneyin ;-)
George Vasiliou

@GeorgeVasiliou Evet, bu yalnızca tüm öğeler bir karakter (sayılar, harfler, noktalama işaretleri, ...) olduğunda işe yarar. Bu yüzden ikinci, daha genel bir çözüm verdim.
jimmij

8

Aslında tersi başka bir dizide istiyorsanız:

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
}

Sonra:

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

verir:

4 3 2 1

Bu, bir dizi dizininin eksik olduğu durumları, örneğin array=([1]=1 [2]=2 [4]=4)0'dan en yüksek dizine döngü yapmak ek, boş, elemanlar ekleyebilir.


Bunun için teşekkürler, oldukça iyi çalışıyor, ancak bazı nedenlerden dolayı shellcheckiki uyarı yazdırıyor: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.ve bunun için:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
nath

1
@nath dolaylı olarak kullanılıyorlar, declarehat bunun için.
muru

Zeki, ancak declare -n4.3'ten önceki bash sürümlerinde işe yaramadığını unutmayın .
G-Man,

8

Dizi konumlarını yerinde değiştirmek için (seyrek dizilerle bile) (bash 3.0'dan beri):

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

Yürütme sırasında:

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

Eski bash için, bir döngü (bash (2.04'ten beri)) kullanmanız $ave arka boşluktan kaçınmak için kullanmanız gerekir:

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

2.03'ten beri bash için:

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

Ayrıca (bitsel olumsuzlama operatörünü kullanarak) (bash 4.2+'den beri):

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

Bir dizinin öğelerini negatif aboneliklerle sondan geriye doğru adreslemek 4.3'ten önceki bash sürümlerinde çalışmıyor gibi görünüyor.
G-Man,

1
Aslında, negatif sayıların adreslenmesi 4.2-alfa olarak değiştirildi. Ve negatif değerlere sahip komut dosyası bu sürümden çalışır. @ G-Man s. Dizinlenmiş dizilere negatif abonelikler, artık atanan maksimum dizin + 1'den ofsetler olarak kabul ediliyor, ancak Bash-hackerlar hatalı bildiriyor 4.1 sayısal olarak dizinlenmiş dizilere negatif dizinler kullanılarak
Isaac

3

Çirkin, sürdürülemez, ancak bir astar:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"

Değil basit, ama daha kısa: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
Isaac

Ve seyrek diziler için bile:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Isaac

@Isaac Ama ne yazık ki, seyrek dizi sürümü için artık bir astar ve sadece çirkin ve bakımı mümkün değil. (Yine de küçük diziler için borulardan daha hızlı olmalıdır.)
user23013

Teknik olarak bir "tek astar"; tek bir komut değil, evet, ama bir "tek astar" dır. Katılıyorum, evet, çok çirkin ve bir bakım sorunu, ama oynamak eğlenceli.
Isaac

1

Yeni bir şey anlatmayacağım ve ben de tacdiziyi tersine çevirmek için kullanacak olsa da, ben bash 4.4 sürümünü kullanarak feryat tek satır çözüm bahsetmeye layık olsa da:

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

Test yapmak:

$ 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

Okuma içindeki var adının orijinal dizi olarak ad olduğunu unutmayın, bu nedenle geçici depolama için yardımcı dizi gerekmez.

IFS'yi ayarlayarak alternatif uygulama:

$ 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")

Not: Yukarıdaki çözümlerin farklı bash yerleşik işlev uygulaması nedeniyle bashferyat versiyonunda çalışmayacağını düşünüyorum .4.4read


IFSVersiyon çalışıyor ama o da yazdırıyor: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Bash kullanma 4.4-5. ;declare -p arrayİlk satırın sonunda kaldırmanız gerekiyor , o zaman işe yarıyor ...
nath

1
@nath declare -p, bash baskısını gerçek dizi (dizin ve içerik) yapmanın hızlı bir yoludur. declare -pGerçek komut dosyanızda bu komuta ihtiyacınız yoktur . Dizilerinizin atamalarında bir şeyler ters giderse ${array[0]}="1 2 3 4 5 6 10 11 12", aynı dizinde depolanan tüm değerlerin = yankı kullanarak bir fark görmeyeceksiniz. Hızlı bir dizi çıktısı için declare -p array, kullanarak gerçek dizi indeksleri ve her dizindeki karşılık gelen değer döndürülür.
George Vasiliou

@nath Bu arada, read -d'\n'yöntem sizin için işe yaramadı mı?
George Vasiliou

read -d'\n'iyi çalışıyor.
Nath

ahhh seni yakaladı! SORRY :-)
nath

1

Rasgele bir diziyi tersine çevirmek için (herhangi bir değere sahip çok sayıda öğe içerebilir):

İle zsh:

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

İle bash4.4+, göz önüne alındığında bashdeğişkenler GNU'yu kullanabilirsiniz, NUL zaten bayt içeremez tac -s ''NUL kayıtlarını ayrılmış olarak basılmış elemanlarında:

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

POSIXly, POSIX kabuk dizisini tersine çevirmek için ( $@, yapılmış $1, $2...):

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

1

Saf bash çözümü, tek astar olarak çalışır.

$: for (( i=${#array[@]}-1; i>=0; i-- ))
>  do rev[${#rev[@]}]=${array[i]}
>  done
$: echo  "${rev[@]}"
7 6 5 4 3 2 1

güzel!!! TEŞEKKÜR; burada kopyalanacak tek astar :-) `` dizi = (1 2 3 4 5 6 7); ((i = $ {# dizi [@]} - 1; i> = 0; i--)); rev [$ {# rev [@]}] = $ {dizi [i]} yapın; yapılan; echo "$ {rev [@]}" ``
nath

Yapmak rev+=( "${array[i]}" )daha basit görünüyor.
Isaac

Birinden altı, diğerinden yarım düzine. Ben bu sözdiziminden değilim, ama bunun için bir nedenim yok - sadece önyargı ve tercih. Sen yapıyorsun.
Paul Hodges

-1

ayrıca kullanmayı düşünebilirsiniz seq

array=(1 2 3 4 5 6 7)

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

freebsd'de -1 artış parametresini atlayabilirsiniz:

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

Bunun diziyi tersine çevirmediğini, yalnızca ters sırada yazdırdığını unutmayın.
roaima

Katılıyorum, benim de endeks erişimi alternatif olarak düşünmek oldu ..
M. Modugno

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.