Вопрос или проблема
Есть ли простой способ развернуть массив?
#!/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
Способы переворота массива
-
Использование цикла
Для переворота массива можно использовать цикл
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
-
Метод временной переменной
Можно также использовать временные переменные:
#!/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[@]}"
Этот подход также дает аналогичный результат.
-
Операции с массивами
Кроме того, можно использовать более сложные операции с массивами и обращаться к элементам, основываясь на их индексах:
#!/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
. -
Использование
tac
Если элементы массива содержат строки, вы можете использовать утилиту
tac
для их переворота:#!/bin/bash array=(1 2 3 4 5 6 7) reversed=$(echo "${array[@]}" | tac -s ' ') echo "$reversed"
Это особенно удобно, когда вы работаете с текстовыми данными.
-
Непрямые методы
Если вы хотите использовать подход с
BASH_ARGV
, вы можете включить опциюextdebug
:#!/bin/bash shopt -s extdebug reverse() { local arr=("${BASH_ARGV[@]}") echo "${arr[@]}" } reverse "${array[@]}"
Здесь массив будет перевернут автоматически, поскольку
BASH_ARGV
хранит параметры в обратном порядке.
Резюме
Существует множество способов перевернуть массив в Bash, от простых до более сложных. Выбор метода зависит от ваших потребностей, структуры данных и предпочтений. Если массив небольшой, подойдут наиболее простые и безопасные решения. Для более крупных массивов стоит обратить внимание на производительность и возможности параллельной обработки.
Таким образом, использование средств Bash для работы с массивами предоставляет множество гибких и эффективных методов, которые могут быть адаптированы к специфическим требованиям вашего рабочего процесса.