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

Это работает для массивов нечетной и четной длины.

roaima
источник
Пожалуйста, обратите внимание, что это не работает для разреженных массивов.
Исаак
@ 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}
jimmij
источник
просто смотрю вверх tac, как противоположность catдовольно хорошо помню, СПАСИБО!
натй
3
Хотя мне нравится идея rev, я должен отметить, что revэто не будет работать правильно для чисел с двумя цифрами. Например, элемент массива с 12 использованием rev будет напечатан как 21. Попробуй ;-)
Джордж Василиу
@GeorgeVasiliou Да, это будет работать, только если все элементы состоят из одного символа (цифры, буквы, знаки препинания, ...). Вот почему я дал и второе, более общее решение.
Джимми
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.
nath
1
@nath они используются косвенно, для этого и нужна declareстрока.
Муру
Умно, но обратите внимание, что, declare -nпохоже, не работает в версиях Bash до 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 р. Отрицательные подписки на индексированные массивы теперь обрабатываются как смещения от максимального назначенного индекса +1., Но Bash-хакеры сообщают неверно 4.1 Массивные индексированные массивы могут быть доступны с конца с использованием отрицательных индексов
Исаак
3

Гадкий, не поддерживаемый, но однострочный

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
user23013
источник
Не проще, но короче 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

Помните, что имя переменной внутри read - это имя исходного массива, поэтому для временного хранения вспомогательный массив не требуется.

Альтернативная реализация путем корректировки 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в конце первой строки, тогда это работает ...
nath
1
@nath declare -p- это просто быстрый способ заставить bash печатать реальный массив (индекс и содержимое). Вам не нужна эта 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]}; сделано; echo "$ {rev [@]}" `
nath
Делать 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
М. Модуньо
источник
Обратите внимание, что это не переворачивает массив, а просто распечатывает его в обратном порядке.
Ройма
Согласитесь, моя точка зрения также заключалась в том, чтобы рассматривать доступ к индексам в качестве альтернативы.
М. Модуньо