Bash - đảo ngược một mảng


16

Có một cách đơn giản để đảo ngược một mảng?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

vì vậy tôi sẽ nhận được: 7 6 5 4 3 2 1
thay vì:1 2 3 4 5 6 7

Câu trả lời:


14

Tôi đã trả lời câu hỏi như đã viết và mã này đảo ngược mảng. (In các phần tử theo thứ tự ngược mà không đảo ngược mảng chỉ là một forvòng lặp đếm ngược từ phần tử cuối cùng về không.) Đây là thuật toán "trao đổi trước và cuối" tiêu chuẩn.

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

Nó hoạt động cho các mảng có chiều dài lẻ và chẵn.


Vui lòng lưu ý rằng điều này không hoạt động đối với các mảng thưa thớt.
Isaac

@Isaac có một giải pháp trên StackOverflow nếu bạn cần xử lý chúng.
roaima

Giải quyết ở đây .
Isaac

17

Một cách tiếp cận khác thường:

#!/bin/bash

array=(1 2 3 4 5 6 7)

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

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

echo "${array[@]}"

Đầu ra:

7 6 5 4 3 2 1

Nếu extdebugđược bật, mảng BASH_ARGVchứa trong một hàm tất cả các tham số vị trí theo thứ tự ngược lại.


Đây là một mẹo tuyệt vời!
Valentin Bajrami

14

Cách tiếp cận độc đáo (tất cả không thuần túy bash):

  • nếu tất cả các phần tử trong một mảng chỉ là một ký tự (như trong câu hỏi), bạn có thể sử dụng rev:

    echo "${array[@]}" | rev
  • nếu không thì:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • và nếu bạn có thể sử dụng zsh:

    echo ${(Oa)array}

Chỉ cần nhìn lên tac, như đối diện của catkhá tốt để nhớ, CẢM ƠN!
Nath

3
Mặc dù tôi thích ý tưởng này rev, tôi cần phải đề cập rằng nó revsẽ không hoạt động chính xác cho các số có hai chữ số. Ví dụ, một phần tử mảng 12 sử dụng rev sẽ được in dưới dạng 21. Hãy dùng thử ;-)
George Vasiliou

@GeorgeVasiliou Có, điều đó sẽ chỉ hoạt động nếu tất cả các yếu tố là một ký tự (số, chữ cái, dấu chấm câu, ...). Đó là lý do tại sao tôi cũng đưa ra giải pháp thứ hai, tổng quát hơn.
jimmij

8

Nếu bạn thực sự muốn đảo ngược trong một mảng khác:

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
}

Sau đó:

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

Cung cấp:

4 3 2 1

Điều này sẽ xử lý chính xác các trường hợp thiếu chỉ mục mảng, giả sử bạn đã có array=([1]=1 [2]=2 [4]=4), trong trường hợp đó, việc lặp từ 0 đến chỉ mục cao nhất có thể thêm các phần tử bổ sung, trống ,.


Cám ơn thế này, nó hoạt động khá tốt, mặc dù đối với một số lý do shellcheckin hai cảnh báo: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.và:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
Nath

1
@nath chúng được sử dụng gián tiếp, đó là những gì declaredòng dành cho.
muru

Thông minh, nhưng lưu ý rằng declare -ndường như không hoạt động trong các phiên bản bash trước 4.3.
G-Man nói 'Phục hồi Monica'

8

Để hoán đổi vị trí mảng tại chỗ (ngay cả với mảng thưa thớt) (kể từ 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[@]}"

Thực hiện:

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

Đối với bash cũ hơn, bạn cần sử dụng một vòng lặp (trong bash (kể từ 2.04)) và sử dụng $ađể tránh không gian dấu:

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

Đối với bash kể từ 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

Ngoài ra (sử dụng toán tử phủ định bitwise) (kể từ 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

Việc giải quyết các phần tử của một mảng từ cuối trở về sau với các chỉ số âm dường như không hoạt động trong các phiên bản bash trước 4.3.
G-Man nói 'Phục hồi Monica'

1
Trên thực tế, việc giải quyết các số âm đã được thay đổi trong 4.2-alpha. Và tập lệnh với các giá trị phủ định hoạt động từ phiên bản đó. @ G-Man p. Đăng ký phủ định cho các mảng được lập chỉ mục, hiện được coi là phần bù từ chỉ số được gán tối đa + 1. nhưng tin tặc Bash báo cáo không chính xác 4 mảng được lập chỉ mục số có thể được truy cập từ cuối bằng cách sử dụng các chỉ mục tiêu cực
Isaac

3

Xấu xí, không thể nhầm lẫn, nhưng một lót:

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

Không đơn giản, nhưng ngắn hơn : eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
Isaac

Và ngay cả đối với các mảng thưa thớt:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Isaac

@Isaac Nhưng không còn là một lớp lót và chỉ xấu xí và không thể nhầm lẫn cho phiên bản mảng thưa thớt, thật không may. (Tuy nhiên vẫn phải nhanh hơn đường ống cho các mảng nhỏ.)
user23013

Vâng, về mặt kỹ thuật, nó là một "lớp lót"; không phải là một lệnh, vâng, nhưng nó là "một lót". Tôi đồng ý, vâng, rất xấu và một vấn đề bảo trì, nhưng vui chơi với.
Isaac

1

Mặc dù tôi sẽ không nói điều gì mới và tôi cũng sẽ sử dụng tacđể đảo ngược mảng, nhưng mặc dù điều đó sẽ đáng để đề cập đến giải pháp dòng đơn dưới đây bằng bash phiên bản 4.4:

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

Kiểm tra:

$ 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

Lưu ý rằng tên var bên trong read là tên như mảng ban đầu, vì vậy không có mảng trợ giúp nào được yêu cầu để lưu trữ tạm thời.

Thực hiện thay thế bằng cách điều chỉnh 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: Tôi nghĩ các giải pháp trên sẽ không hoạt động trong bashphiên bản dưới đây4.4 do readtriển khai chức năng dựng sẵn bash khác nhau .


Các IFSphiên bản hoạt động nhưng nó cũng được in: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Sử dụng bash 4.4-5. Bạn có để loại bỏ ;declare -p arrayvào cuối dòng đầu tiên, sau đó nó hoạt động ...
Nath

1
@nath declare -pchỉ là một cách nhanh chóng để làm cho bash in mảng thực (chỉ mục và nội dung). Bạn không cần declare -plệnh này trong kịch bản thực sự của bạn. Nếu có lỗi xảy ra trong các bài tập mảng của bạn, bạn có thể kết thúc trong trường hợp ${array[0]}="1 2 3 4 5 6 10 11 12"= tất cả các giá trị được lưu trong cùng một chỉ mục - sử dụng echo bạn sẽ thấy không có sự khác biệt. Đối với một bản in mảng nhanh bằng cách sử dụng declare -p arraysẽ trả về cho bạn các mảng mảng thực và giá trị tương ứng trong mỗi chỉ mục.
George Vasiliou

@nath Nhân tiện, read -d'\n'phương pháp không hiệu quả với bạn?
George Vasiliou

read -d'\n'hoạt động tốt
Nath

ahhh có bạn SORRY :-)
Nath

1

Để đảo ngược một mảng tùy ý (có thể chứa bất kỳ số phần tử nào với bất kỳ giá trị nào):

Với zsh:

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

Với bash4.4+, do bashcác biến không thể chứa byte NUL, bạn có thể sử dụng GNU tac -s ''trên các phần tử được in dưới dạng bản ghi phân cách NUL:

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

POSIXly, để đảo ngược mảng vỏ POSIX ( $@, được làm bằng $1, $2...):

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

1

Giải pháp bash tinh khiết, sẽ làm việc như một lớp lót.

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

tốt đẹp !!! CÁM ƠN; ở đây một lớp lót để sao chép :-) `mảng = (1 2 3 4 5 6 7); for ((i = $ {# mảng [@]} - 1; i> = 0; i--)); do rev [$ {# rev [@]}] = $ {mảng [i]}; làm xong; tiếng vang "$ {rev [@]}" `
'19

Làm rev+=( "${array[i]}" )có vẻ đơn giản hơn.
Isaac

Sáu của một, một nửa tá khác. Tôi không từ bỏ cú pháp đó, nhưng không có lý do cho nó - chỉ là định kiến ​​và ưu tiên. Bạn làm bạn.
Paul Hodges

-1

bạn cũng có thể cân nhắc sử dụng seq

array=(1 2 3 4 5 6 7)

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

trong freebsd bạn có thể bỏ qua tham số tăng -1:

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

Lưu ý rằng điều này không đảo ngược mảng, nó chỉ in ra theo thứ tự ngược lại.
roaima

Đồng ý, quan điểm của tôi cũng là xem xét các chỉ số truy cập thay thế ..
M. Modugno

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.