Bash - зворотний масив


16

Чи є простий спосіб повернути масив?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

тому я отримав би: 7 6 5 4 3 2 1
замість:1 2 3 4 5 6 7

Відповіді:


15

Я відповів на запитання як написаний, і цей код повертає масив. (Друк елементів у зворотному порядку без повернення масиву - це лише forцикл, відлік від останнього елемента до нуля.) Це стандартний алгоритм "підміняти перший і останній".

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

Він працює для масивів непарної і парної довжини.


Зверніть увагу, що це не працює для розріджених масивів.
Ісаак

@Isaac в StackOverflow є рішення, якщо вам потрібно впоратися з ними.
roaima

Тут вирішено .
Ісаак

18

Ще один нетрадиційний підхід:

#!/bin/bash

array=(1 2 3 4 5 6 7)

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

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

echo "${array[@]}"

Вихід:

7 6 5 4 3 2 1

Якщо extdebugце ввімкнено, масив BASH_ARGVмістить у функції всі позиційні параметри у зворотному порядку.


Це приголомшливий трюк!
Валентин Байрамі

15

Нетрадиційний підхід (все не чисто bash):

  • якщо всі елементи масиву є лише одним символом (як у запитанні), ви можете використовувати rev:

    echo "${array[@]}" | rev
  • інакше:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • і якщо ви можете використовувати zsh:

    echo ${(Oa)array}

щойно дивився вгору tac, як протилежність catдосить добре запам’ятати, ДЯКУЮ!
натй

3
Хоча ідея мені подобається rev, мені потрібно зазначити, що revвона не працюватиме правильно для чисел з двозначними цифрами. Наприклад, елемент масиву з 12 використанням rev буде надрукований як 21. Спробуйте ;-)
Джордж Васильоу

@GeorgeVasiliou Так, це спрацює лише в тому випадку, якщо всі елементи будуть одним символом (цифри, літери, пунктуації, ...). Тому я дав і друге, більш загальне рішення.
jimmij

8

Якщо ви насправді хочете реверс в іншому масиві:

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
}

Потім:

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

Дає:

4 3 2 1

Це має правильно обробляти випадки, коли індекс масиву відсутній, скажімо, у вас був array=([1]=1 [2]=2 [4]=4), і в цьому випадку циклічне від 0 до найвищого індексу може додавати додаткові, порожні, елементи.


Спасибі за цього, він працює досить добре, хоча з деяких причин shellcheckдрукує два попередження: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.і:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
Нат

1
@nath вони опосередковано використовуються, саме для цього і використовується declareлінія.
муру

Розумний, але зауважте, що, declare -nздається, не працює у баш-версіях до 4.3.
G-Man каже: "Відновіть Моніку"

8

Щоб поміняти місцями масиви на місцях (навіть із розрідженими масивами) (починаючи з 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[@]}"

На виконання:

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

Для старшого bash вам потрібно використовувати цикл (у bash (з 2.04)) та використовуючи, $aщоб уникнути пробілу:

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

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

Також (за допомогою оператора заборгованого побітного відхилення) (починаючи з 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

Адресація елементів масиву з кінця назад з негативними підписами, здається, не працює у версіях bash до 4.3.
G-Man каже: "Відновіть Моніку"

1
Власне, адресація негативних чисел була змінена в 4.2-альфа. І сценарій із запереченими значеннями працює з цієї версії. @ G-Man p. Негативні підписки на індексовані масиви, які тепер трактуються як компенсації від максимально призначеного індексу + 1., але Bash-хакери повідомляють про неправильно 4.1 числових індексованих масивах можна отримати доступ з кінця за допомогою негативних індексів
Ісаак

3

Некрасивий, незрозумілий, але однолінійний:

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

Чи не простіше, але коротше: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
Ісаак

І навіть для розріджених масивів:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Ісаак

@Isaac Але, на жаль, більше не є одне вкладише, а лише потворне і нездійсненне для розрідженої версії масиву. (Хоча все-таки швидше, ніж труби для малих масивів.)
user23013

Ну, технічно це "однолінійний"; не одна команда, так, але "один вкладиш" це. Я погоджуюся, так, дуже некрасиво і проблема обслуговування, але весело грати.
Ісаак

1

Хоча я не збираюсь розповідати щось нове, і я також буду використовувати tacдля зворотного перегляду масиву, я хотів би сказати, що це було б згадати нижче одне рядкове рішення з використанням bash версії 4.4:

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

Тестування:

$ 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

Зверніть увагу, що ім'я var усередині read - це ім'я як оригінальний масив, тому для зберігання temp не потрібен допоміжний масив.

Альтернативна реалізація шляхом коригування 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: Я думаю, що вищезгадані рішення не працюватимуть у bashнижченаведеній версії 4.4через різну readреалізацію вбудованої функції bash.


IFSВерсія працює , але це також друк: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Використання bash 4.4-5. Ви повинні видалити ;declare -p arrayв кінці першого рядка, то він працює ...
натй

1
@nath declare -p- це лише швидкий спосіб зробити баш-друк реального масиву (індекс та вміст). Ця declare -pкоманда не потрібна у вашому реальному сценарії. Якщо у ваших призначеннях масивів щось піде не так, ви можете виявити випадок, що ${array[0]}="1 2 3 4 5 6 10 11 12"= всі значення, що зберігаються в одному індексі - за допомогою echo ви не побачите різниці. Для швидкого роздруковування масиву використання declare -p arrayповерне вам справжні масиви та відповідне значення у кожному індексі.
Георгій Васильоу

@nath До речі, read -d'\n'метод для вас не працював?
Георгій Васильоу

read -d'\n'працює чудово.
натй

ааа, тебе! SORRY :-)
натй

1

Щоб повернути довільний масив (який може містити будь-яку кількість елементів з будь-якими значеннями):

З zsh:

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

З bash4.4+, враховуючи, що bashзмінні ніяк не можуть містити балів NUL, ви можете використовувати GNU tac -s ''для елементів, надрукованих як записи з обмеженою NUL:

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

POSIXly, щоб повернути масив оболонки POSIX ( $@зроблений з $1, $2...):

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

1

Чистий розчин bash, працював би як однолінійний.

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

хороший!!! ДЯКУЮ; ось один вкладиш для копіювання :-) `array = (1 2 3 4 5 6 7); for ((i = $ {# array [@]} - 1; i> = 0; i--)); do rev [$ {# rev [@]}] = $ {array [i]}; зроблено; відлуння "$ {оборотів [@]}" `
натх

Робити це rev+=( "${array[i]}" )здається простіше.
Ісаак

Шість одного, півдесятка іншого. Я не заперечую за цим синтаксисом, але не маю для цього причин - просто упередження та уподобання. Ти робиш.
Пол Ходжес

-1

Ви також можете розглянути можливість використання seq

array=(1 2 3 4 5 6 7)

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

у freebsd ви можете опустити параметр збільшення -1:

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

Зауважте, що це масив не повертає, він просто виводить його у зворотному порядку.
roaima

Погодьтеся, мій пункт також був розглянути доступ до індексів як альтернативу ..
М. Модуньо

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.