วิธีการจัดเรียงอาร์เรย์ใน Bash


139

ฉันมีอาร์เรย์ใน Bash ตัวอย่างเช่น:

array=(a c b f 3 5)

ฉันต้องการเรียงลำดับของอาร์เรย์ ไม่เพียงแสดงเนื้อหาในวิธีเรียงลำดับ แต่เพื่อรับอาร์เรย์ใหม่ที่มีองค์ประกอบเรียงลำดับ อาร์เรย์ที่เรียงลำดับใหม่สามารถเป็นอาร์เรย์ใหม่ที่สมบูรณ์หรืออาร์เรย์เก่าได้

คำตอบ:


208

คุณไม่ต้องการรหัสมากขนาดนั้นทั้งหมด:

IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS

รองรับช่องว่างในองค์ประกอบ (ตราบใดที่ยังไม่ได้ขึ้นบรรทัดใหม่) และทำงานใน Bash 3.x

เช่น:

$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]

หมายเหตุ: @sorontar ได้ชี้ให้เห็นว่าจำเป็นต้องมีการดูแลหากองค์ประกอบมีอักขระตัวแทนเช่น*หรือ?:

ส่วน sort = ($ (... )) กำลังใช้ตัวดำเนินการ "split and glob" คุณควรปิด glob: set -fหรือset -o noglobหรือshopt -op noglobองค์ประกอบของ array เหมือน*จะถูกขยายไปยังรายการไฟล์

เกิดอะไรขึ้น:

ผลที่ได้คือสุดยอดหกสิ่งที่เกิดขึ้นในคำสั่งนี้:

  1. IFS=$'\n'
  2. "${array[*]}"
  3. <<<
  4. sort
  5. sorted=($(...))
  6. unset IFS

ก่อนอื่น IFS=$'\n'

นี่เป็นส่วนสำคัญของการดำเนินงานของเราที่มีผลต่อผลลัพธ์ของ 2 และ 5 ด้วยวิธีต่อไปนี้:

ได้รับ:

  • "${array[*]}" ขยายไปทุกองค์ประกอบคั่นด้วยอักขระตัวแรกของ IFS
  • sorted=() สร้างองค์ประกอบโดยแยกตัวละครทุกตัวของ IFS

IFS=$'\n' ตั้งค่าสิ่งต่าง ๆเพื่อให้องค์ประกอบถูกขยายโดยใช้บรรทัดใหม่เป็นตัวคั่นจากนั้นสร้างขึ้นในภายหลังในลักษณะที่แต่ละบรรทัดกลายเป็นองค์ประกอบ (เช่นแยกในบรรทัดใหม่)

การเพิ่มบรรทัดใหม่นั้นมีความสำคัญเนื่องจากเป็นวิธีsortการทำงาน (การเรียงลำดับต่อบรรทัด) การแยกโดยเฉพาะบรรทัดใหม่นั้นไม่สำคัญเท่าไร แต่จำเป็นต้องเก็บรักษาองค์ประกอบที่มีช่องว่างหรือแท็บ

ค่าเริ่มต้นของIFSเป็นช่องว่าง , แท็บตามด้วยการขึ้นบรรทัดใหม่และจะไม่เหมาะสำหรับการดำเนินงานของเรา

ถัดไปsort <<<"${array[*]}"ส่วนที่

<<<เรียกว่าที่นี่สตริงจะใช้เวลาการขยายตัวของตามที่อธิบายไว้ข้างต้นและฟีดมันเข้าไปในเข้ามาตรฐานของ"${array[*]}"sort

จากตัวอย่างของเราให้sortป้อนสตริงต่อไปนี้:

a c
b
f
3 5

ตั้งแต่sort ประเภทจะผลิต:

3 5
a c
b
f

ถัดไปsorted=($(...))ส่วนที่

$(...)ส่วนหนึ่งที่เรียกว่าคำสั่งเปลี่ยนตัวทำให้เนื้อหา ( sort <<<"${array[*]}) เพื่อเรียกใช้คำสั่งเป็นปกติในขณะที่การส่งผลให้ การส่งออกมาตรฐานเป็นตัวอักษรที่จะไปที่ที่เคย$(...)เป็น

ในตัวอย่างของเราสิ่งนี้สร้างสิ่งที่คล้ายกับการเขียนเพียง:

sorted=(3 5
a c
b
f
)

sorted จากนั้นกลายเป็นอาร์เรย์ที่สร้างขึ้นโดยแยกตัวอักษรนี้ในทุกบรรทัดใหม่

ในที่สุด, unset IFS

สิ่งนี้จะรีเซ็ตค่าเป็นค่าIFSเริ่มต้นและเป็นเพียงแนวทางปฏิบัติที่ดี

เพื่อให้แน่ใจว่าเราจะไม่สร้างปัญหากับสิ่งใดที่ต้องอาศัยในIFSภายหลังในสคริปต์ของเรา (ไม่เช่นนั้นเราจำเป็นต้องจำไว้ว่าเราได้เปลี่ยนสิ่งต่าง ๆ - สิ่งที่อาจเป็นไปไม่ได้สำหรับสคริปต์ที่ซับซ้อน)


2
@ โดยที่ไม่มีIFSมันจะแยกองค์ประกอบของคุณออกเป็นชิ้นเล็กชิ้นน้อยถ้าพวกเขามีพื้นที่ว่างในพวกเขา ลองเช่นกับIFS=$'\n' ละเว้นดู!
antak

3
ดีมาก. คุณสามารถอธิบายให้ผู้ใช้ bash โดยเฉลี่ยทราบได้อย่างไรว่าโซลูชันนี้ทำงานอย่างไร
u32004

2
ตอนนี้ด้วยIFSมันจะแยกองค์ประกอบของคุณออกเป็นชิ้นเล็กชิ้นน้อยถ้าพวกมันมีช่องว่างชนิดใดชนิดหนึ่งโดยเฉพาะ ดี; ไม่สมบูรณ์แบบ :-)
การชดเชย จำกัด

7
คือunset IFSจำเป็น? ฉันคิดว่าIFS=การเพิ่มคำสั่งเป็นการกำหนดขอบเขตการเปลี่ยนแปลงคำสั่งนั้นเท่านั้นกลับไปเป็นค่าก่อนหน้าโดยอัตโนมัติในภายหลัง
Mark H

10
@ MarkH มันจำเป็นเพราะsorted=()ไม่ใช่คำสั่ง แต่เป็นการมอบหมายตัวแปรตัวที่สอง
antak

35

คำตอบเดิม:

array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)

เอาท์พุท:

$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f

หมายเหตุรุ่นนี้มีค่าที่มีอักขระพิเศษหรือช่องว่าง ( ยกเว้น newlines)

Note readarray รองรับใน bash 4+


แก้ไขตามคำแนะนำโดย @Dimitre ฉันได้อัปเดตเป็น:

readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)

ซึ่งมีประโยชน์ในการทำความเข้าใจองค์ประกอบการเรียงลำดับด้วยอักขระบรรทัดใหม่ฝังอย่างถูกต้อง น่าเสียดายที่การส่งสัญญาณอย่างถูกต้องโดย @ruakh ไม่ได้หมายความว่าผลลัพธ์ของการแก้ไขreadarrayจะถูกต้องเพราะreadarrayไม่มีตัวเลือกให้ใช้NULแทนการขึ้นบรรทัดใหม่เป็นตัวคั่นบรรทัด


5
เป็นเรื่องดีที่ควรสังเกตด้วยว่า readarray มีให้ตั้งแต่รุ่น 4 ของ bash อาจสั้นลงเล็กน้อย:readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Dimitre Radoulov

1
@Dimitre: ฉันเอาข้อเสนอแนะของคุณและแก้ไขการจัดการช่องว่างเพื่อทำงานกับอะไร (ใช้ nullchar-delimiters ภายใน) ไชโย
sehe

1
ใช่sort -zเป็นการปรับปรุงที่มีประโยชน์ฉันคิดว่า-zตัวเลือกคือส่วนขยายการเรียง GNU
Dimitre Radoulov

2
หากคุณต้องการจัดการกับการขึ้นบรรทัดใหม่ที่ฝังตัวคุณสามารถม้วนการอ่านใหม่ของคุณเอง ตัวอย่างเช่นsorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z). วิธีนี้ใช้ได้ผลกับคุณที่กำลังใช้ bash v3 แทน bash v4 เนื่องจากการอ่านไม่พร้อมใช้งานใน bash v3
Bob Bell

1
@ user1527227 การเปลี่ยนเส้นทางการป้อนข้อมูลของมัน ( <) ร่วมกับกระบวนการทดแทน <(...)หรือเพื่อนำมาใช้อย่างสังหรณ์ใจ: เพราะ(printf "bla")ไม่ใช่ไฟล์
sehe

33

นี่คือการใช้งาน Bash quicksort อย่างแท้จริง:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
qsort() {
   local pivot i smaller=() larger=()
   qsort_ret=()
   (($#==0)) && return 0
   pivot=$1
   shift
   for i; do
      if (( i < pivot )); then
         smaller+=( "$i" )
      else
         larger+=( "$i" )
      fi
   done
   qsort "${smaller[@]}"
   smaller=( "${qsort_ret[@]}" )
   qsort "${larger[@]}"
   larger=( "${qsort_ret[@]}" )
   qsort_ret=( "${smaller[@]}" "$pivot" "${larger[@]}" )
}

ใช้เป็นเช่น

$ array=(a c b f 3 5)
$ qsort "${array[@]}"
$ declare -p qsort_ret
declare -a qsort_ret='([0]="3" [1]="5" [2]="a" [3]="b" [4]="c" [5]="f")'

การใช้งานนี้ซ้ำ ... ดังนั้นนี่คือ quicksort ซ้ำ:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
qsort() {
   (($#==0)) && return 0
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if [[ "${qsort_ret[i]}" < "$pivot" ]]; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}

ในทั้งสองกรณีคุณสามารถเปลี่ยนลำดับที่คุณใช้: ฉันใช้การเปรียบเทียบสตริง แต่คุณสามารถใช้การเปรียบเทียบทางคณิตศาสตร์เปรียบเทียบเวลาการแก้ไขไฟล์ wrt ฯลฯ เพียงแค่ใช้การทดสอบที่เหมาะสม คุณสามารถทำให้มันเป็นแบบทั่วไปมากขึ้นและให้มันใช้อาร์กิวเมนต์แรกที่ใช้ทดสอบฟังก์ชั่นเช่น

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
# First argument is a function name that takes two arguments and compares them
qsort() {
   (($#<=1)) && return 0
   local compare_fun=$1
   shift
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if "$compare_fun" "${qsort_ret[i]}" "$pivot"; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}

จากนั้นคุณสามารถมีฟังก์ชั่นการเปรียบเทียบนี้:

compare_mtime() { [[ $1 -nt $2 ]]; }

และใช้:

$ qsort compare_mtime *
$ declare -p qsort_ret

เพื่อให้ไฟล์ในโฟลเดอร์ปัจจุบันเรียงตามเวลาการแก้ไข (ใหม่ที่สุดก่อน)

บันทึก. ฟังก์ชั่นเหล่านี้ล้วนเป็น Bash! ไม่มียูทิลิตี้ภายนอกและไม่มี subshells! สัญลักษณ์เหล่านี้ปลอดภัยสำหรับคุณที่มีสัญลักษณ์ (ช่องว่างอักขระขึ้นบรรทัดใหม่ตัวอักษรแบบกลม ฯลฯ )


1
ความรุ่งโรจน์สำหรับการทุบตีที่น่าประทับใจที่ให้ความยืดหยุ่นอย่างมากกับองค์ประกอบอินพุตและเกณฑ์การจัดเรียง หากการเรียงตามบรรทัดด้วยตัวเลือกการเรียงที่sortมีเพียงพอโซลูชันsort+ read -aจะเริ่มต้นเร็วขึ้นพูดรายการ 20 รายการและองค์ประกอบที่คุณจัดการกับยิ่งขึ้น เช่นใน iMac ปลายปี 2555 ที่ใช้ OSX 10.11.1 พร้อมฟิวชั่นไดรฟ์: อาร์เรย์ 100 องค์ประกอบ: แคลิฟอร์เนีย 0.03 วินาที ( qsort()) เทียบกับแคลิฟอร์เนีย 0.005 วินาที ( sort+ read -a); อาร์เรย์ 1000 องค์ประกอบ: แคลิฟอร์เนีย 0.375 วินาที ( qsort()) เทียบกับแคลิฟอร์เนีย 0.014 วินาที ( sort+ read -a)
mklement0

ดี ฉันจำเรียงลำดับอย่างรวดเร็วจากวันวิทยาลัย แต่จะค้นคว้าเรียงลำดับฟอง สำหรับความต้องการในการเรียงลำดับของฉันฉันมีองค์ประกอบที่หนึ่งและสองในการสร้างคีย์ตามด้วยองค์ประกอบข้อมูลหนึ่ง (ซึ่งฉันอาจขยายในภายหลัง) รหัสของคุณสามารถปรับปรุงได้ด้วยจำนวนองค์ประกอบสำคัญ (parm1) และจำนวนองค์ประกอบข้อมูล (parm2) สำหรับ OP พารามิเตอร์จะเป็น 1 และ 0 สำหรับฉันพารามิเตอร์จะเป็น 2 และ 1 ไม่ว่าคำตอบของคุณจะเป็นคำตอบใดก็ตาม
WinEunuuchs2Unix

1
ด้วยชุดข้อมูลของจำนวนเต็มสตริงที่ไม่ได้อ่านผมพบว่าif [ "$i" -lt "$pivot" ]; thenจำเป็นต้องใช้มิฉะนั้นการคืนค่า "2" <"10" กลับคืนเป็นจริง ฉันเชื่อว่านี่เป็น POSIX กับ Lexicographical หรือบางทีอาจจะInline การเชื่อมโยง
2PagePro

27

หากคุณไม่จำเป็นต้องจัดการอักขระเชลล์พิเศษในองค์ประกอบอาร์เรย์:

array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))

ด้วยbashคุณจะต้องใช้โปรแกรมการเรียงลำดับภายนอก

ด้วยzshไม่จำเป็นต้องใช้โปรแกรมภายนอกและจัดการอักขระพิเศษได้อย่างง่ายดาย:

% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}" 
3
5
a a
b
c
f

kshมีset -sการจัดเรียงASCIIbetically


ข้อมูลพื้นหลังที่ดีมาก ฉันเกือบจะขอตัวอย่างว่า ksh จะใช้ค่าสถานะ set -s ได้อย่างไร แต่แล้วอีกครั้งคำถามนั้นอยู่ที่ bash ดังนั้นจะค่อนข้างนอกหัวข้อ
sehe

สิ่งนี้ควรทำงานกับการปรับใช้KornShellส่วนใหญ่(เช่นksh88และpdksh ): set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" และแน่นอนคำสั่ง set จะรีเซ็ตพารามิเตอร์ตำแหน่งปัจจุบันหากมี
Dimitre Radoulov

คุณเป็นแหล่งรวมของความรู้เกี่ยวกับเชลล์ ฉันแน่ใจว่าคุณต้องมีหน่วยความจำการถ่ายภาพหรือบางสิ่งบางอย่างเพราะความแตกต่างที่ลึกซึ้งนี้ทำให้สมาชิกส่วนใหญ่ของเผ่าพันธุ์มนุษย์ส่วนมาก :) +1 สำหรับชุดข้อมูลที่สมบูรณ์
sehe

10

tl; dr :

จัดเรียงอาร์เรย์a_inและเก็บผลลัพธ์ไว้ในa_outองค์ประกอบ (ต้องไม่มีบรรทัดใหม่ฝังอยู่[1] ):

Bash v4 +:

readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

Bash v3:

IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)

ข้อดีกว่าวิธีการแก้ปัญหาของ antak :

  • คุณไม่จำเป็นต้องกังวลเกี่ยวกับการวนรอบโดยไม่ตั้งใจ (การตีความโดยไม่ตั้งใจขององค์ประกอบอาเรย์เป็นรูปแบบชื่อไฟล์) ดังนั้นจึงไม่จำเป็นต้องมีคำสั่งพิเศษเพื่อปิดการใช้งานการวนรอบ ( set -fและset +fเพื่อเรียกคืนภายหลัง)

  • คุณไม่จำเป็นต้องกังวลเกี่ยวกับการรีเซ็ตด้วยIFS [2]unset IFS


อ่านเพิ่มเติม: คำอธิบายและรหัสตัวอย่าง

ข้างต้นรวมรหัส Bash กับยูทิลิตี้ภายนอกsortสำหรับโซลูชันที่ทำงานกับองค์ประกอบบรรทัดเดียวโดยพลการและการเรียงลำดับศัพท์หรือตัวเลข (ตัวเลือกตามฟิลด์) :

  • ประสิทธิภาพการทำงาน : สำหรับองค์ประกอบประมาณ 20 รายการขึ้นไปจะเร็วกว่าโซลูชัน Bash บริสุทธิ์ - อย่างมีนัยสำคัญและมากขึ้นดังนั้นเมื่อคุณได้รับเกิน 100 องค์ประกอบ
    (เกณฑ์ที่แน่นอนจะขึ้นอยู่กับอินพุตเครื่องและแพลตฟอร์มเฉพาะของคุณ)

    • เหตุผลที่มันเป็นไปอย่างรวดเร็วก็คือว่ามันหลีกเลี่ยงลูปทุบตี
  • printf '%s\n' "${a_in[@]}" | sort ทำการเรียงลำดับ (โดยค่าเริ่มต้น - ดูsortข้อมูลจำเพาะ POSIX ของ ):

    • "${a_in[@]}"ขยายไปยังองค์ประกอบของอาเรย์อย่างปลอดภัยa_inเป็นอาร์กิวเมนต์ส่วนบุคคลสิ่งที่พวกเขามี (รวมถึงช่องว่าง)

    • printf '%s\n' จากนั้นพิมพ์แต่ละอาร์กิวเมนต์ - เช่นแต่ละองค์ประกอบอาร์เรย์ - บนบรรทัดของตัวเองตามที่เป็น

  • บันทึกการใช้การทดแทนกระบวนการ ( <(...))เพื่อจัดเตรียมเอาต์พุตที่เรียงลำดับเป็นอินพุตไปยังread/ readarray(ผ่านการเปลี่ยนเส้นทางไปยัง stdin, <) เนื่องจากread/ readarrayต้องทำงานในเชลล์ปัจจุบัน (ต้องไม่ทำงานในเชลล์ย่อย ) เพื่อให้ตัวแปรเอาต์พุตa_outสามารถมองเห็นได้ เป็นเชลล์ปัจจุบัน (สำหรับตัวแปรที่จะยังคงกำหนดไว้ในส่วนที่เหลือของสคริปต์)

  • การอ่านsortเอาต์พุตของตัวแปรอาเรย์ :

    • Bash v4 +: readarray -t a_outอ่านแต่ละบรรทัดผลลัพธ์โดยsortเข้าไปในองค์ประกอบของตัวแปรอาเรa_outย์โดยไม่รวมส่วนท้าย\nในแต่ละองค์ประกอบ ( -t)

    • Bash v3: readarrayไม่มีอยู่ดังนั้นreadต้องใช้:
      IFS=$'\n' read -d '' -r -a a_outบอกreadให้อ่าน-aตัวแปรอาเรย์ ( ) a_outอ่านอินพุตทั้งหมดข้ามบรรทัด ( -d '') แต่แยกมันออกเป็นองค์ประกอบอาเรย์โดยขึ้นบรรทัดใหม่ ( IFS=$'\n'. $'\n'ซึ่งสร้างบรรทัดใหม่ตามตัวอักษร (LF) ) เป็นสตริงที่อ้างถึง ANSI C )
      ( -rตัวเลือกที่ควรนำมาใช้จริงกับreadปิดใช้งานการจัดการ\อักขระที่ไม่คาดคิด)

รหัสตัวอย่างข้อเขียน:

#!/usr/bin/env bash

# Define input array `a_in`:
# Note the element with embedded whitespace ('a c')and the element that looks like
# a glob ('*'), chosen to demonstrate that elements with line-internal whitespace
# and glob-like contents are correctly preserved.
a_in=( 'a c' b f 5 '*' 10 )

# Sort and store output in array `a_out`
# Saving back into `a_in` is also an option.
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Bash 4.x: use the simpler `readarray -t`:
# readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

# Print sorted output array, line by line:
printf '%s\n' "${a_out[@]}"

เนื่องจากการใช้งานsortโดยไม่ใช้ตัวเลือกนี้ทำให้การเรียงคำศัพท์ (เรียงลำดับตัวเลขก่อนตัวอักษรและลำดับตัวเลขได้รับการปฏิบัติด้วยคำศัพท์ไม่ใช่เป็นตัวเลข):

*
10
5
a c
b
f

หากคุณต้องการเรียงลำดับตัวเลขตามฟิลด์ที่ 1 คุณจะใช้sort -k1,1nแทนเพียงsortซึ่งให้ผล (ไม่ใช่ตัวเลขเรียงก่อนตัวเลขและเรียงลำดับตัวเลขอย่างถูกต้อง):

*
a c
b
f
5
10

[1] การจับองค์ประกอบที่มีการขึ้นบรรทัดใหม่ฝังตัวใช้ที่แตกต่างกันดังต่อไปนี้ (ทุบตี v4 + กับGNU ):sort คำตอบที่เป็นประโยชน์ของMichałGórnyมีวิธีการแก้ปัญหา Bash v3
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)

[2] ในขณะที่IFS มีการตั้งค่าใน v3 ตัวแปรทุบตีการเปลี่ยนแปลงจะกำหนดขอบเขตคำสั่ง
ในทางตรงกันข้ามสิ่งต่อไปนี้IFS=$'\n' ในคำตอบ antak เป็นการมอบหมายมากกว่าคำสั่งซึ่งในกรณีที่IFSเปลี่ยนแปลงทั่วโลก


8

ในการเดินทางโดยรถไฟ 3 ชั่วโมงจากมิวนิคไปแฟรงค์เฟิร์ต (ซึ่งฉันมีปัญหาในการเข้าถึงเพราะ Oktoberfest เริ่มในวันพรุ่งนี้) ฉันกำลังคิดถึงโพสต์แรกของฉัน การใช้อาร์เรย์ทั่วโลกเป็นแนวคิดที่ดีกว่าสำหรับฟังก์ชั่นการจัดเรียงทั่วไป ฟังก์ชั่นต่อไปนี้จะจัดการกับสตริง arbitary (ขึ้นบรรทัดใหม่, ช่องว่าง ฯลฯ ):

declare BSORT=()
function bubble_sort()
{   #
    # @param [ARGUMENTS]...
    #
    # Sort all positional arguments and store them in global array BSORT.
    # Without arguments sort this array. Return the number of iterations made.
    #
    # Bubble sorting lets the heaviest element sink to the bottom.
    #
    (($# > 0)) && BSORT=("$@")
    local j=0 ubound=$((${#BSORT[*]} - 1))
    while ((ubound > 0))
    do
        local i=0
        while ((i < ubound))
        do
            if [ "${BSORT[$i]}" \> "${BSORT[$((i + 1))]}" ]
            then
                local t="${BSORT[$i]}"
                BSORT[$i]="${BSORT[$((i + 1))]}"
                BSORT[$((i + 1))]="$t"
            fi
            ((++i))
        done
        ((++j))
        ((--ubound))
    done
    echo $j
}

bubble_sort a c b 'z y' 3 5
echo ${BSORT[@]}

ภาพพิมพ์นี้:

3 5 a b c z y

เอาต์พุตเดียวกันถูกสร้างจาก

BSORT=(a c b 'z y' 3 5) 
bubble_sort
echo ${BSORT[@]}

โปรดทราบว่าอาจทุบตีใช้ตัวชี้สมาร์ทภายในดังนั้นการดำเนินการแลกเปลี่ยนอาจมีราคาถูก (แม้ว่าฉันจะสงสัย) อย่างไรก็ตามbubble_sortแสดงให้เห็นว่าฟังก์ชั่นขั้นสูงเช่นmerge_sortนี้ยังอยู่ในการเข้าถึงของภาษาเชลล์


5
เรียงลำดับฟอง? ว้าว .. โอบามากล่าวว่า "การเรียงลำดับฟองจะเป็นวิธีที่ผิดไป" -> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino

1
ดูเหมือนว่าในขณะที่ O-guy ต้องการที่จะฉลาดเขาไม่ได้รู้สึกว่านี่ไม่ใช่คำถามที่มีโอกาส 50/50 บรรพบุรุษในตำแหน่ง O-guy มาบอกเขาว่า B-guy เคยทำได้ดีกว่านี้มาก (Reynoldsburg, Ohio, Oct 2000): "ฉันคิดว่าถ้าคุณรู้ว่าสิ่งที่คุณเชื่อมันทำให้ตอบคำถามได้ง่ายขึ้นมาก ฉันไม่สามารถตอบคำถามของคุณได้ " ดังนั้น B-guy นี้รู้อะไรบางอย่างเกี่ยวกับตรรกะบูลีน O-guy ไม่ได้
Andreas Spindler

ฟังก์ชั่นนี้สามารถทำให้พกพาได้ง่ายขึ้นโดยการทำให้ BSORT เป็นอาร์เรย์ในตัวที่มี nameref ในทุก ๆ สิ่งที่จะถูกจัดเรียง เช่นlocal -n BSORT="$1"ที่จุดเริ่มต้นของฟังก์ชั่น จากนั้นคุณสามารถเรียกใช้bubble_sort myarrayในการจัดเรียงmyArray
johnraff

7

โซลูชันอื่นที่ใช้ภายนอกsortและ copes ด้วยอักขระพิเศษใด ๆ (ยกเว้น NULs :)) ควรทำงานกับ bash-3.2 และ GNU หรือ BSD sort(เศร้า POSIX ไม่รวม-z)

local e new_array=()
while IFS= read -r -d '' e; do
    new_array+=( "${e}" )
done < <(printf "%s\0" "${array[@]}" | LC_ALL=C sort -z)

ก่อนอื่นให้ดูที่การเปลี่ยนเส้นทางอินพุตที่ส่วนท้าย เราใช้printfบิวด์อินเพื่อเขียนองค์ประกอบอาเรย์สิ้นสุดโดยไม่มีค่า การอ้างถึงทำให้แน่ใจว่าองค์ประกอบของอาร์เรย์ถูกส่งผ่านตามที่เป็นอยู่และข้อมูลจำเพาะของเชลล์printfทำให้มันใช้ส่วนสุดท้ายของสตริงการจัดรูปแบบสำหรับพารามิเตอร์ที่เหลืออีกครั้ง นั่นคือมันเทียบเท่ากับสิ่งที่ชอบ:

for e in "${array[@]}"; do
    printf "%s\0" "${e}"
done

sortรายการองค์ประกอบโมฆะเป็นสิ้นสุดจะผ่านไปแล้ว -zตัวเลือกที่ทำให้เกิดการอ่านองค์ประกอบโมฆะเป็นสิ้นสุด, การจัดเรียงพวกเขาและเอาท์พุทโมฆะเป็นสิ้นสุดเช่นกัน หากคุณจำเป็นต้องได้รับเพียงองค์ประกอบที่ไม่ซ้ำกันคุณสามารถส่งผ่านเพราะมันเป็นแบบพกพามากกว่า-u เพื่อความมั่นใจว่ามีเสถียรภาพการจัดเรียงเป็นอิสระจากสถานที่เกิดเหตุ - บางครั้งมีประโยชน์สำหรับสคริปต์ หากคุณต้องการที่จะเคารพสถานที่ให้ลบออกuniq -zLC_ALL=Csort

<()สร้างได้ให้คำอธิบายในการอ่านจากท่อไม้ไผ่และ<เปลี่ยนเส้นทางเข้ามาตรฐานของwhileวงไป หากคุณต้องการเข้าถึงอินพุตมาตรฐานภายในไพพ์คุณอาจใช้ตัวอธิบายอื่น - แบบฝึกหัดสำหรับผู้อ่าน :)

ตอนนี้กลับไปที่จุดเริ่มต้น readในตัวอ่านเอาท์พุทจาก stdin เปลี่ยนเส้นทาง การตั้งค่าที่ว่างเปล่าIFSปิดใช้งานการแยกคำที่ไม่จำเป็นที่นี่ - เป็นผลให้readอ่าน 'บรรทัด' ทั้งการป้อนข้อมูลไปยังตัวแปรเดียวที่ให้ไว้ -rตัวเลือกปิดการประมวลผลการหลบหนีที่ไม่พึงประสงค์ที่นี่เช่นกัน ในที่สุด-d ''ตั้งค่าตัวคั่นบรรทัดเป็น NUL นั่นคือบอกreadให้อ่านสตริงที่สิ้นสุดด้วยศูนย์

eเป็นผลให้วงจะถูกดำเนินการครั้งเดียวสำหรับศูนย์สิ้นสุดองค์ประกอบอาร์เรย์ทุกเนื่องกับค่าที่ถูกเก็บไว้ใน ตัวอย่างเพิ่งวางไอเท็มในอาร์เรย์อื่น แต่คุณอาจต้องการประมวลผลโดยตรง :)

แน่นอนว่าเป็นเพียงหนึ่งในหลาย ๆ วิธีในการบรรลุเป้าหมายเดียวกัน อย่างที่ฉันเห็นมันง่ายกว่าการใช้อัลกอริทึมการเรียงลำดับแบบสมบูรณ์ในการทุบตีและในบางกรณีมันจะเร็วขึ้น มันจัดการตัวละครพิเศษทั้งหมดรวมถึงการขึ้นบรรทัดใหม่และควรทำงานกับระบบทั่วไปส่วนใหญ่ ที่สำคัญที่สุดคืออาจสอนสิ่งใหม่และยอดเยี่ยมเกี่ยวกับ bash :)


ทางออกที่ดีและคำอธิบายที่เป็นประโยชน์มากขอบคุณ One extension: หากไม่มีการตั้งค่าให้ IFS ว่างเปล่าช่องว่างนำหน้าจะถูกกำจัดด้วยเช่นกันแม้ว่าจะไม่มีการแบ่งคำ
Dirk Herrmann

แทนที่จะแนะนำตัวแปรภายในเครื่องeและตั้งค่า IFS ว่างให้ใช้ตัวแปร REPLY
Robin A. Meade

2

ลองนี้:

echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort

ผลลัพธ์จะเป็น:

3
5

ข
ค
ฉ

แก้ไขปัญหา.


3
ควรแก้ไขสิ่งนี้เพื่อใส่ผลลัพธ์ลงในอาร์เรย์ใหม่เพื่อตอบคำถามของเขาอย่างเต็มที่
Peter Oram

2

หากคุณสามารถคำนวณจำนวนเต็มเฉพาะสำหรับแต่ละองค์ประกอบในอาร์เรย์เช่นนี้

tab='0123456789abcdefghijklmnopqrstuvwxyz'

# build the reversed ordinal map
for ((i = 0; i < ${#tab}; i++)); do
    declare -g ord_${tab:i:1}=$i
done

function sexy_int() {
    local sum=0
    local i ch ref
    for ((i = 0; i < ${#1}; i++)); do
        ch="${1:i:1}"
        ref="ord_$ch"
        (( sum += ${!ref} ))
    done
    return $sum
}

sexy_int hello
echo "hello -> $?"
sexy_int world
echo "world -> $?"

จากนั้นคุณสามารถใช้จำนวนเต็มเหล่านี้เป็นดัชนีอาเรย์เนื่องจาก Bash ใช้อาเรย์แบบเบาบางดังนั้นไม่จำเป็นต้องกังวลเกี่ยวกับดัชนีที่ไม่ได้ใช้:

array=(a c b f 3 5)
for el in "${array[@]}"; do
    sexy_int "$el"
    sorted[$?]="$el"
done

echo "${sorted[@]}"
  • ข้อดี. รวดเร็ว
  • จุดด้อย องค์ประกอบที่ซ้ำกันจะถูกรวมเข้าด้วยกันและเป็นไปไม่ได้ที่จะแมปเนื้อหากับจำนวนเต็ม 32 บิตที่ไม่ซ้ำกัน

เทคนิคที่น่าสนใจฉันใช้ตัวแปรเพื่อค้นหาค่าสูงสุด / นาทีโดยไม่มีการเปรียบเทียบ / เรียงลำดับอย่างชัดเจน แต่การเติมแบบไม่มีการถ่วงน้ำหนักโดยไม่คำนึงถึงความยาวจะไม่ทำงาน: "z" เรียงลำดับก่อนหน้า "aaaa" ดังนั้นคุณจึงไม่สามารถใช้คำนี้ในขณะที่คุณแสดงด้านบน
mr.spuratic

2

เรียงลำดับขั้นต่ำ:

#!/bin/bash
array=(.....)
index_of_element1=0

while (( ${index_of_element1} < ${#array[@]} )); do

    element_1="${array[${index_of_element1}]}"

    index_of_element2=$((index_of_element1 + 1))
    index_of_min=${index_of_element1}

    min_element="${element_1}"

        for element_2 in "${array[@]:$((index_of_element1 + 1))}"; do
            min_element="`printf "%s\n%s" "${min_element}" "${element_2}" | sort | head -n+1`"      
            if [[ "${min_element}" == "${element_2}" ]]; then
                index_of_min=${index_of_element2}
            fi
            let index_of_element2++
        done

        array[${index_of_element1}]="${min_element}"
        array[${index_of_min}]="${element_1}"

    let index_of_element1++
done

1
array=(a c b f 3 5)
new_array=($(echo "${array[@]}" | sed 's/ /\n/g' | sort))    
echo ${new_array[@]}

เนื้อหา echo ของ new_array จะเป็น:

3 5 a b c f

1

มีวิธีแก้ไขสำหรับปัญหาช่องว่างและการขึ้นบรรทัดใหม่ตามปกติ:

ใช้ตัวอักษรที่ไม่อยู่ในอาร์เรย์เดิม (ชอบ$'\1'หรือ$'\4'หรือคล้ายกัน)

ฟังก์ชั่นนี้ทำให้งานสำเร็จลุล่วง:

# Sort an Array may have spaces or newlines with a workaround (wa=$'\4')
sortarray(){ local wa=$'\4' IFS=''
             if [[ $* =~ [$wa] ]]; then
                 echo "$0: error: array contains the workaround char" >&2
                 exit 1
             fi

             set -f; local IFS=$'\n' x nl=$'\n'
             set -- $(printf '%s\n' "${@//$nl/$wa}" | sort -n)
             for    x
             do     sorted+=("${x//$wa/$nl}")
             done
       }

นี่จะเรียงลำดับอาเรย์:

$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>

สิ่งนี้จะบ่นว่าอาร์เรย์ต้นทางมีอักขระการแก้ปัญหา:

$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char

ลักษณะ

  • เราตั้งค่าตัวแปรโลคัลสองตัวwa(วิธีแก้ปัญหาถ่าน) และ IFS ว่าง
  • จากนั้น (กับ null ไอเอฟเอ) $*เราทดสอบว่าอาร์เรย์ทั้งหมด
  • [[ $* =~ [$wa] ]]ไม่ได้มีถ่าน woraround ใด ๆ
  • ถ้าเป็นเช่นนั้นให้เพิ่มข้อความและส่งสัญญาณข้อผิดพลาด: exit 1
  • หลีกเลี่ยงการขยายชื่อไฟล์: set -f
  • ตั้งค่าใหม่ของ IFS ( IFS=$'\n') ตัวแปร loop xและ newline var ( nl=$'\n')
  • เราพิมพ์ค่าทั้งหมดของอาร์กิวเมนต์ที่ได้รับ (อาร์เรย์อินพุต$@)
  • "${@//$nl/$wa}"แต่เราแทนที่บรรทัดใหม่โดยถ่านวิธีแก้ปัญหา
  • sort -nส่งค่าเหล่านั้นจะเรียงลำดับ
  • set --และสถานที่กลับทั้งหมดเรียงค่าในข้อโต้แย้งตำแหน่ง
  • จากนั้นเราจะกำหนดแต่ละอาร์กิวเมนต์ทีละหนึ่ง (เพื่อรักษาบรรทัดใหม่)
  • ในวง for x
  • ไปยังอาร์เรย์ใหม่: sorted+=(…)
  • ภายในเครื่องหมายคำพูดเพื่อรักษาบรรทัดใหม่ที่มีอยู่
  • "${x//$wa/$nl}"การฟื้นฟูการแก้ปัญหาการขึ้นบรรทัดใหม่
  • เสร็จแล้ว

1

คำถามนี้มีความเกี่ยวข้องอย่างใกล้ชิด และ BTW นี่คือการผสานใน Bash (ไม่มีกระบวนการภายนอก):

mergesort() {
  local -n -r input_reference="$1"
  local -n output_reference="$2"
  local -r -i size="${#input_reference[@]}"
  local merge previous
  local -a -i runs indices
  local -i index previous_idx merged_idx \
           run_a_idx run_a_stop \
           run_b_idx run_b_stop

  output_reference=("${input_reference[@]}")
  if ((size == 0)); then return; fi

  previous="${output_reference[0]}"
  runs=(0)
  for ((index = 0;;)) do
    for ((++index;; ++index)); do
      if ((index >= size)); then break 2; fi
      if [[ "${output_reference[index]}" < "$previous" ]]; then break; fi
      previous="${output_reference[index]}"
    done
    previous="${output_reference[index]}"
    runs+=(index)
  done
  runs+=(size)

  while (("${#runs[@]}" > 2)); do
    indices=("${!runs[@]}")
    merge=("${output_reference[@]}")
    for ((index = 0; index < "${#indices[@]}" - 2; index += 2)); do
      merged_idx=runs[indices[index]]
      run_a_idx=merged_idx
      previous_idx=indices[$((index + 1))]
      run_a_stop=runs[previous_idx]
      run_b_idx=runs[previous_idx]
      run_b_stop=runs[indices[$((index + 2))]]
      unset runs[previous_idx]
      while ((run_a_idx < run_a_stop && run_b_idx < run_b_stop)); do
        if [[ "${merge[run_a_idx]}" < "${merge[run_b_idx]}" ]]; then
          output_reference[merged_idx++]="${merge[run_a_idx++]}"
        else
          output_reference[merged_idx++]="${merge[run_b_idx++]}"
        fi
      done
      while ((run_a_idx < run_a_stop)); do
        output_reference[merged_idx++]="${merge[run_a_idx++]}"
      done
      while ((run_b_idx < run_b_stop)); do
        output_reference[merged_idx++]="${merge[run_b_idx++]}"
      done
    done
  done
}

declare -ar input=({z..a}{z..a})
declare -a output

mergesort input output

echo "${input[@]}"
echo "${output[@]}"

0

ฉันไม่มั่นใจว่าคุณจะต้องใช้โปรแกรมการเรียงลำดับภายนอกใน Bash

นี่คือการใช้งานของฉันสำหรับอัลกอริทึมเรียงลำดับฟองอย่างง่าย

function bubble_sort()
{   #
    # Sorts all positional arguments and echoes them back.
    #
    # Bubble sorting lets the heaviest (longest) element sink to the bottom.
    #
    local array=($@) max=$(($# - 1))
    while ((max > 0))
    do
        local i=0
        while ((i < max))
        do
            if [ ${array[$i]} \> ${array[$((i + 1))]} ]
            then
                local t=${array[$i]}
                array[$i]=${array[$((i + 1))]}
                array[$((i + 1))]=$t
            fi
            ((i += 1))
        done
        ((max -= 1))
    done
    echo ${array[@]}
}

array=(a c b f 3 5)
echo " input: ${array[@]}"
echo "output: $(bubble_sort ${array[@]})"

สิ่งนี้จะพิมพ์:

 input: a c b f 3 5
output: 3 5 a b c f

O(n^2)ฟองเรียงลำดับคือ ฉันดูเหมือนจะจำอัลกอริทึมการเรียงลำดับส่วนใหญ่ใช้องค์ประกอบO(n lg(n))จนถึงโหลสุดท้ายหรือดังนั้น สำหรับองค์ประกอบสุดท้ายจะใช้การเรียงลำดับการเลือก
jww


-1

sorted=($(echo ${array[@]} | tr " " "\n" | sort))

ในจิตวิญญาณของ bash / linux ฉันจะใช้เครื่องมือบรรทัดคำสั่งที่ดีที่สุดสำหรับแต่ละขั้นตอน sortทำหน้าที่หลัก แต่ต้องการอินพุตแยกจากกันโดยขึ้นบรรทัดใหม่แทนที่จะเป็นช่องว่างดังนั้นไพพ์ไลน์ที่ง่ายมาก ๆ

Echo array content -> แทนที่ space ด้วย newline -> sort

$() คือการสะท้อนผลลัพธ์

($()) คือการใส่ "ผลสะท้อน" ในอาเรย์

หมายเหตุ : ตามที่ @sorontar พูดถึงในความคิดเห็นคำถามอื่น:

ส่วน sort = ($ (... )) กำลังใช้ตัวดำเนินการ "split and glob" คุณควรปิด glob: set -f หรือ set -o noglob หรือ shopt -op noglob หรือองค์ประกอบของ array เช่น * จะถูกขยายเป็นรายการไฟล์


ในวิญญาณของ bash / linux : ฉันคิดว่าคุณไม่เข้าใจวิญญาณเลย รหัสของคุณเสียอย่างสมบูรณ์ (การขยายชื่อพา ธ และการแยกคำ) นี้จะดีกว่า (Bash≥4): มิฉะนั้นmapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort) sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
gniourf_gniourf

antipatterns ที่คุณใช้คือ:: echo ${array[@]} | tr " " "\n"สิ่งนี้จะแตกถ้าฟิลด์ของอาร์เรย์มีช่องว่างและอักขระ glob นอกจากนี้ยังวางไข่ subshell และใช้คำสั่งภายนอกที่ไร้ประโยชน์ และเนื่องจากechoเป็นใบ้มันจะทำลายถ้าอาร์เรย์ของคุณเริ่มต้นด้วย-e, หรือ-E -nใช้แทน: printf '%s\n' "${array[@]}". antipattern อื่น ๆ : ($())คือการใส่ "ผลสะท้อน" ในอาร์เรย์ ไม่แน่นอน! นี่คือ antipattern ที่น่ากลัวที่แตกเพราะการขยายชื่อพา ธ (กลม) และการแยกคำ อย่าใช้สยองขวัญนี้
gniourf_gniourf

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