重击-反转数组


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

Answers:


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)

@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在函数中以相反顺序包含所有位置参数。


这是一个很棒的把戏!
Valentin Bajrami

15

非常规方法(都不都是纯方法bash):

  • 如果数组中的所有元素只是一个字符(如问题中所示),则可以使用rev

    echo "${array[@]}" | rev
  • 除此以外:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • 并且如果可以使用zsh

    echo ${(Oa)array}

刚刚抬起头来taccat很容易记住,谢谢!
nath

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

1
@nath被间接使用,这就是该declare行的用途。
muru

聪明,但是请注意,declare -n在4.3之前的bash版本中似乎不起作用。
G-Man说'Resstate Monica''Sep

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起的bash:

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

从头到尾使用负下标来寻址数组的元素似乎在4.3之前的bash版本中不起作用。
G-Man说'Resstate Monica''Sep

1
实际上,负数在4.2-alpha中已更改。值取反的脚本可以在该版本中使用。@ G-Man p。 索引数组的负下标,现在视为与最大分配索引的偏移量+1。但是Bash黑客错误地报告了4.1 可以使用负索引从最后访问数字索引数组
Isaac

3

丑陋,难以维护,但单线:

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

不简单,但更短:eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'"
艾萨克

甚至对于稀疏数组:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Isaac

@Isaac但不幸的是,对于稀疏数组版本,它不再是单行的,而是丑陋且难以维护的。(尽管它仍然应该比小型阵列的管道要快。)
user23013 '19

从技术上讲,它是“单线”;不是一个命令,是的,而是“一个班轮”。我同意,是的,非常丑陋且存在维护问题,但玩起来很有趣。
艾萨克(Isaac)

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名称是作为原始数组的名称,因此临时存储不需要辅助数组。

通过调整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内置函数实现,我认为上述解决方案在bash波纹管版本中不起作用。4.4read


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'工作良好。
nath

啊,明白了!
抱歉

1

反转任意数组(可以包含任意数量的带有任何值的元素):

zsh

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

bash4.4+版本中,假设bash变量无论如何都不能包含NUL字节,则可以tac -s ''在以NUL分隔记录打印的元素上使用GNU :

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); 为((i = $ {#array [@]}-1; i> = 0; i--));做rev [$ {#rev [@]}] = $ {array [i]}; 完成 回声“$ {REV [@]}”`
纳特

这样做rev+=( "${array[i]}" )似乎更简单。
以撒

六个,六个的六个。我不是那种语法的代名词,但没有理由-只是偏见和偏爱。你做你
Paul Hodges

-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

同意,我的观点也考虑指数接入作为一种替代..
M.莫杜尼奥

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.