Баш – развернуть массив

Вопрос или проблема

Есть ли простой способ развернуть массив?

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

Еще один нестандартный подход:

#!/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 содержит в функции все позиционные параметры в обратном порядке.

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

  • если все элементы в массиве состоят из одного символа (как в вопросе), вы можете использовать rev:

     echo "${array[@]}" | rev
    
  • в противном случае, если ни один из элементов массива не содержит новой строки:

     printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
    
  • и если вы можете использовать zsh:

     echo ${(Oa)array}
    

Я ответил на вопрос, как написано, и этот код разворачивает массив. (Вывод элементов в обратном порядке без разворачивания массива — это просто цикл for, который начинается с последнего элемента и заканчивается на нуле.) Это стандартный алгоритм «поменять местами первый и последний».

array=(1 2 3 4 5 6 7)

min=0
max=$(( ${#array[@]} -1 ))

while [[ min -lt max ]]
do
    # Поменять местами текущие первый и последний элементы
    x="${array[$min]}"
    array[$min]="${array[$max]}"
    array[$max]="$x"
    
    # Ближе друг к другу
    (( min++, max-- ))
done

echo "${array[@]}"

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

Если вы действительно хотите получить разворот в другом массиве:

reverse() {
    # первый аргумент — это массив для разворота
    # второй — это выходной массив
    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 до максимального индекса может добавить дополнительные, пустые, элементы.

Чтобы поменять местами элементы массива на месте (даже с разреженными массивами) (с тех пор как bash 3.0):

#!/bin/bash
# Объявить разреженный массив для тестирования:
array=([5]=101 [6]=202 [10]=303 [11]=404 [20]=505 [21]=606 [40]=707)
echo "Исходные значения массива"
declare -p array

swaparray(){ local temp; temp="${array[$1]}"
             array[$1]="${array[$2]}"
             array[$2]="$temp"
           }

ind=("${!array[@]}")                         # неразреженный массив индексов.

min=-1; max="${#ind[@]}"                     # ограничения до одного перед реальными ограничениями.
while [[ min++ -lt max-- ]]                  # приближаемся к друг другу на каждом цикле.
do
    swaparray "${ind[min]}" "${ind[max]}"    # Обменяйте первый и последний
done

echo "Окончательный массив, поменянный местами"
declare -p array
echo "Конечные значения массива"
echo "${array[@]}"

При выполнении:

./script
Исходные значения массива
declare -a array=([5]="101" [6]="202" [10]="303" [11]="404" [20]="505" [21]="606" [40]="707")

Окончательный массив, поменянный местами
declare -a array=([5]="707" [6]="606" [10]="505" [11]="404" [20]="303" [21]="202" [40]="101")

Конечные значения массива
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

Для bash начиная с 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

Ужасно, не поддерживаемо, но однострочно:

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

Чистое решение bash, будет работать как однострочное.

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

Чтобы развернуть произвольный массив (который может содержать любое количество элементов с любыми значениями):

С zsh:

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

С bash 4.4+, учитывая, что переменные bash не могут содержать байты NUL, вы можете использовать GNU tac -s '' для элементов, распечатанных как записи, разделенные NUL:

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

Однако учтите, что массивы bash были вдохновлены массивами ksh, а не массивами csh/zsh, и больше похожи на ассоциативные массивы с ключами, ограниченными положительными целыми числами (так называемые разреженные массивы), и этот метод не сохраняет ключи массивов. Например, для массива, такого как:

array=( [3]=a [12]=b [42]=c )

Вы получите

array_reversed=( [0]=c [1]=b [2]=a )

Согласно стандартам POSIX, чтобы развернуть единственный массив POSIX shell ($@, состоящий из $1, $2…) на месте:

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

#!/bin/bash
(a=(1 2 3 4 5) r=(); for e in "${a[@]}"; do r=("$e" "${r[@]}"); done; declare -p a r)

распечатывает

declare -a a=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5")
declare -a r=([0]="5" [1]="4" [2]="3" [3]="2" [4]="1")

Попробуйте это

#!/bin/bash

array=(1 2 3 4 5 6)
index=$((${#array[@]}-1))

for e in "${array[@]}"; do
  result[$((index--))]="$e"
done

echo "${result[@]}"

Две разные версии

Вдохновлено ответом Кираса и wiki.wooledge.org.
Очень быстро, так как нет циклов и не происходит форков!
На не слишком большом объеме данных!!, в соответствии с комментарием Стивена Шазела, если вы планируете манипулировать большими объемами данных,
лучше воспользоваться специализированными инструментами!

А здесь, заключено в одну единственную функцию.

Вывести поданный массив в обратном порядке:

printReverseArray () {
    if shopt -q extdebug; then
        printf "%s " "${BASH_ARGV[@]}"
    else
        shopt -s extdebug
        "${FUNCNAME}" "$@"
        shopt -u extdebug
    fi
}

Пример запуска:

printReverseArray world! good Hello
Hello good world!

printReverseArray baz "Foo bar"
Foo bar baz

Перевернуть переменную массива:

reverseArray() { 
    if shopt -q extdebug; then
        _ArrayToReverse=("${BASH_ARGV[@]}")
    else
        local -n _ArrayToReverse=$1
        shopt -s extdebug
        "${FUNCNAME}" "${_ArrayToReverse[@]}"
        shopt -u extdebug
    fi
}

Тогда

myArray=({a..d}{1,2})
echo ${myArray[@]}
a1 a2 b1 b2 c1 c2 d1 d2

reverseArray myArray
echo ${myArray[@]}
d2 d1 c2 c1 b2 b1 a2 a1

Согласно TIMTOWDI (Есть больше одного способа сделать это), вот мое решение, разворачивающее массив a в r:

#!/bin/bash
set -u
a=(1 2 3)
t=("${a[@]}")
declare -p a t
r=()
while [ "${#t[@]}" -gt 0 ]
do
    r+=("${t[-1]}")
    unset 't[-1]'
done
echo "${r[@]}"
declare -p r

При выполнении с BASH “4.4.23(1)-release (x86_64-suse-linux-gnu)” я получил:

+ set -u
+ a=(1 2 3)
+ t=("${a[@]}")
+ declare -p a t
declare -a a=([0]="1" [1]="2" [2]="3")
declare -a t=([0]="1" [1]="2" [2]="3")
+ r=()
+ '[' 3 -gt 0 ']'
+ r+=("${t[-1]}")
+ unset 't[-1]'
+ '[' 2 -gt 0 ']'
+ r+=("${t[-1]}")
+ unset 't[-1]'
+ '[' 1 -gt 0 ']'
+ r+=("${t[-1]}")
+ unset 't[-1]'
+ '[' 0 -gt 0 ']'
+ echo 3 2 1
3 2 1
+ declare -p r
declare -a r=([0]="3" [1]="2" [2]="1")

Следуя ответу Кираса и комментарию ingydotnet здесь, мне пришлось удалить последний элемент array в

Linux xmg 6.1.0-28-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.119-1 (2024-11-22) x86_64 GNU/Linux
GNU bash, Version 5.2.15(1)-release (x86_64-pc-linux-gnu)

поскольку это было имя скрипта:

reverseArrayTest.sh

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "length=${#array[@]}"
echo "array=${array[@]}"

reverseArray() {
    shopt -s extdebug;
    array=("${BASH_ARGV[@]}")
    unset array[-1] # удалить последний (т.е. имя скрипта) элемент
    unwantedArray=("${BASH_ARGV[@]}")
    }

reverseArray "${array[@]}"

echo "length=${#array[@]}"
echo "array=${array[@]}"

echo "unwantedLength=${#unwantedArray[@]}"
echo "unwantedArray=${unwantedArray[@]}"

Вывод

length=7
array=1 2 3 4 5 6 7
length=7
array=7 6 5 4 3 2 1
unwantedLength=8
unwantedArray=7 6 5 4 3 2 1 reverseArrayTest.sh

Смотрите также BASH_ARGV в bash, Shell Variables:

Когда выполняется подпрограмма, переданные параметры помещаются в BASH_ARGV. Оболочка устанавливает BASH_ARGV только тогда, когда находится в режиме расширенной отладки (см. описание параметра extdebug для встроенной команды shopt ниже).

и также:

Установка extdebug после того, как оболочка начала выполнять скрипт [….] может привести к несогласованным значениям.

Для последнего смотрите также мой комментарий к ответу Кираса.

Bash

array=(1 2 3 4 5 6 7)
echo "${array[@]} " | tac -s ' '

Или

array=(1 2 3 4 5 6 7)
reverse=$(echo "${array[@]} " | tac -s ' ')
echo ${reverse[@]}

Результат

7 6 5 4 3 2 1

Версия

$ tac --version
tac (GNU coreutils) 8.28

Вы также можете рассмотреть возможность использования 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

Ответ или решение

Как перевернуть массив в Bash

Переворот массива в Bash можно выполнить несколькими способами, и в данной статье мы рассмотрим как стандартные, так и более оригинальные методы.

Пример исходного массива

Для начала, давайте создадим простой массив:

#!/bin/bash

array=(1 2 3 4 5 6 7)
echo "${array[@]}"

Вывод этого скрипта будет:

1 2 3 4 5 6 7

Наша цель — получить:

7 6 5 4 3 2 1

Способы переворота массива

  1. Использование цикла

    Для переворота массива можно использовать цикл for и вспомогательную переменную:

    #!/bin/bash
    
    array=(1 2 3 4 5 6 7)
    reversed=()
    
    for (( i=${#array[@]}-1; i>=0; i-- )); do
       reversed+=("${array[i]}")
    done
    
    echo "${reversed[@]}"

    В этом случае мы проходим по элементам массива в обратном порядке и добавляем их в новый массив reversed. Результат:

    7 6 5 4 3 2 1
  2. Метод временной переменной

    Можно также использовать временные переменные:

    #!/bin/bash
    
    array=(1 2 3 4 5 6 7)
    temp=("${array[@]}")
    reversed=()
    
    while [ ${#temp[@]} -gt 0 ]; do
       reversed+=("${temp[-1]}")
       unset 'temp[-1]' # Удаляем последний элемент
    done
    
    echo "${reversed[@]}"

    Этот подход также дает аналогичный результат.

  3. Операции с массивами

    Кроме того, можно использовать более сложные операции с массивами и обращаться к элементам, основываясь на их индексах:

    #!/bin/bash
    
    array=(1 2 3 4 5 6 7)
    result=()
    index=$((${#array[@]}-1))
    
    for e in "${array[@]}"; do
       result[$((index--))]="$e"
    done
    
    echo "${result[@]}"

    В этом коде мы используем индекс для добавления каждого элемента в новый массив result.

  4. Использование tac

    Если элементы массива содержат строки, вы можете использовать утилиту tac для их переворота:

    #!/bin/bash
    
    array=(1 2 3 4 5 6 7)
    reversed=$(echo "${array[@]}" | tac -s ' ')
    echo "$reversed"

    Это особенно удобно, когда вы работаете с текстовыми данными.

  5. Непрямые методы

    Если вы хотите использовать подход с BASH_ARGV, вы можете включить опцию extdebug:

    #!/bin/bash
    shopt -s extdebug
    
    reverse() {
       local arr=("${BASH_ARGV[@]}")
       echo "${arr[@]}"
    }
    
    reverse "${array[@]}"

    Здесь массив будет перевернут автоматически, поскольку BASH_ARGV хранит параметры в обратном порядке.

Резюме

Существует множество способов перевернуть массив в Bash, от простых до более сложных. Выбор метода зависит от ваших потребностей, структуры данных и предпочтений. Если массив небольшой, подойдут наиболее простые и безопасные решения. Для более крупных массивов стоит обратить внимание на производительность и возможности параллельной обработки.

Таким образом, использование средств Bash для работы с массивами предоставляет множество гибких и эффективных методов, которые могут быть адаптированы к специфическим требованиям вашего рабочего процесса.

Оцените материал
Добавить комментарий

Капча загружается...