Bash - ย้อนกลับอาร์เรย์


คำตอบ:


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 มีวิธีแก้ไขปัญหา 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}

เพิ่งเงยหน้าขึ้นมองtacเป็นสิ่งcatที่ดีที่จะจำขอบคุณ!
ภูมิพลอดุลยเดช

3
แม้ว่าฉันชอบความคิดของrevฉันต้องพูดถึงว่าrevจะไม่ทำงานอย่างถูกต้องสำหรับตัวเลขที่มีตัวเลขสองหลัก ยกตัวอย่างเช่นองค์ประกอบอาร์เรย์ของใช้รอบจะถูกพิมพ์ออกมาเป็น12 21ลองดู ;-)
George Vasiliou

@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.
ภูมิพลอดุลยเดช

1
@nath พวกเขาใช้ทางอ้อมนั่นคือสิ่งที่ใช้declareทำ
muru

ฉลาด แต่ทราบว่าdeclare -nดูเหมือนจะไม่ทำงานในเวอร์ชันทุบตีก่อน 4.3
G-Man กล่าวว่า 'Reinstate Monica'

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

สำหรับ 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

นอกจากนี้ (ใช้ตัวดำเนินการปฏิเสธบิต) (ตั้งแต่ทุบตี 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
G-Man กล่าวว่า 'Reinstate Monica'

1
ที่จริงแล้วตัวเลขที่อยู่ติดลบนั้นเปลี่ยนไปใน 4.2-alpha และสคริปต์ที่มีค่าที่ไม่ได้ใช้งานจะทำงานได้จากเวอร์ชันนั้น @ G-Man p. ตัวห้อยเชิงลบไปยังอาร์เรย์ที่จัดทำดัชนีตอนนี้ถือว่าเป็นออฟเซ็ตจากดัชนีสูงสุดที่กำหนด + 1แต่แฮ็คเกอร์รายงานอย่างไม่ถูกต้อง 4.1 อาร์เรย์ที่ทำดัชนีตัวเลขสามารถเข้าถึงได้จากจุดสิ้นสุดโดยใช้ดัชนีเชิงลบ
Isaac

3

น่าเกลียดไม่ย่อท้อ แต่ซับเดียว:

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

ไม่ง่าย eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'"แต่สั้น:
Isaac

และแม้กระทั่งสำหรับอาร์เรย์เบาบาง:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
ไอแซค

@Isaac แต่ไม่มีซับในแล้วอีกต่อไปและน่าเกลียดและไม่น่าสนใจสำหรับรุ่นอาเรย์เท่านั้น (ควรจะเร็วกว่าท่อสำหรับอาร์เรย์ขนาดเล็ก)
user23013

ในทางเทคนิคมันเป็น "หนึ่งซับ"; ไม่ใช่คำสั่งเดียวใช่ แต่เป็น "หนึ่งซับ" มันเป็น ฉันเห็นด้วยใช่น่าเกลียดมากและปัญหาการบำรุงรักษา แต่สนุกกับการเล่น
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เวอร์ชันร้อง4.4เนื่องจากreadการใช้ฟังก์ชั่น bash builtin ที่แตกต่างกัน


IFSรุ่นทำงาน declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12")แต่ก็ยังพิมพ์: 4.4-5ใช้ทุบตี คุณได้เอา;declare -p arrayในตอนท้ายของบรรทัดแรกแล้วมันทำงาน ...
ภูมิพลอดุลยเดช

1
@nath declare -pเป็นเพียงวิธีที่รวดเร็วในการทำให้ bash พิมพ์อาร์เรย์จริง (ดัชนีและเนื้อหา) คุณไม่ต้องการdeclare -pคำสั่งนี้ในสคริปต์จริงของคุณ หากมีบางอย่างผิดพลาดในการมอบหมายอาร์เรย์ของคุณคุณอาจสิ้นสุดในกรณีที่${array[0]}="1 2 3 4 5 6 10 11 12"= ค่าทั้งหมดที่เก็บไว้ในดัชนีเดียวกันโดยใช้ echo คุณจะไม่เห็นความแตกต่าง สำหรับการพิมพ์อาเรย์อย่างรวดเร็วโดยใช้declare -p arrayจะส่งคืนดัชนีจริงและค่าที่เกี่ยวข้องในแต่ละดัชนี
George Vasiliou

@nath วิธีread -d'\n'การที่ไม่ได้ผลสำหรับคุณหรือไม่
George Vasiliou

read -d'\n'ทำงานได้ดี
ภูมิพลอดุลยเดช

อ่ามีคุณ! ขอโทษ :-)
ภูมิพลอดุลยเดช

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

วิธีทุบตีบริสุทธิ์จะทำงานเป็นหนึ่งซับ

$: 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]}; ทำ; echo "$ {รอบ [@]}" `
ภูมิพลอดุลยเดช

การทำrev+=( "${array[i]}" )ดูเหมือนง่ายขึ้น
Isaac

หกหนึ่งครึ่งโหล ฉันไม่ได้ใช้ไวยากรณ์นั้น แต่ไม่มีเหตุผลเลย - แค่มีอคติและความชอบ คุณทำคุณ
พอลฮอดจ์ส

-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

เห็นด้วยประเด็นของฉันคือพิจารณาการเข้าถึงดัชนีเป็นทางเลือก ..
ม. Modugno

-2

ทุบตี

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.