ตรวจสอบว่าอาร์เรย์ว่างเปล่าใน Bash


110

ฉันมีอาร์เรย์ที่เต็มไปด้วยข้อความแสดงข้อผิดพลาดที่แตกต่างกันเมื่อสคริปต์ของฉันทำงาน

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

ฉันได้ลองปฏิบัติกับมันเหมือน VAR ปกติแล้วใช้ -z เพื่อตรวจสอบ แต่ดูเหมือนจะไม่ทำงาน มีวิธีการตรวจสอบว่าอาร์เรย์ว่างเปล่าหรือไม่ใน Bash หรือไม่?

คำตอบ:


143

หากว่าอาร์เรย์ของคุณเป็น$errorsเพียงแค่ตรวจสอบเพื่อดูว่าจำนวนองค์ประกอบเป็นศูนย์

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi

10
โปรดทราบว่า=เป็นผู้ประกอบการสตริง มันทำงานได้ดีในกรณีนี้ แต่ฉันจะใช้ตัวดำเนินการทางคณิตศาสตร์ที่เหมาะสม-eqแทน (ในกรณีที่ฉันต้องการเปลี่ยนเป็น-geหรือ-ltอื่น ๆ )
musiphil

6
ไม่ทำงานกับset -u: "ตัวแปรที่ไม่ได้ผูกไว้" - ถ้าอาร์เรย์ว่างเปล่า
อิกอร์

@Igor: ทำงานให้ฉันใน Bash 4.4 set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. ถ้าฉันunset fooแล้วมันพิมพ์ออกfoo: unbound variableมา แต่มันต่างกัน: ตัวแปรอาร์เรย์ไม่มีอยู่เลยแทนที่จะมีอยู่แล้วและว่างเปล่า
Peter Cordes

ทดสอบด้วย Bash 3.2 (OSX) เมื่อใช้set -u- ตราบใดที่คุณประกาศตัวแปรก่อนสิ่งนี้จะทำงานได้อย่างสมบูรณ์
zeroimpl

15

โดยทั่วไปฉันใช้การขยายเลขคณิตในกรณีนี้:

if (( ${#a[@]} )); then
    echo not empty
fi

ดีและสะอาด! ฉันชอบมัน. ฉันยังทราบด้วยว่าหากองค์ประกอบแรกของอาร์เรย์ไม่ว่างเปล่า(( ${#a} ))(ความยาวขององค์ประกอบแรก) จะใช้งานได้เช่นกัน อย่างไรก็ตามนั่นจะล้มเหลวในa=('')ขณะ(( ${#a[@]} ))ที่คำตอบที่ได้รับจะประสบความสำเร็จ
cxw

8

คุณสามารถพิจารณาอาร์เรย์เป็นตัวแปรอย่างง่ายได้ ด้วยวิธีการเพียงแค่ใช้

if [ -z "$array" ]; then
    echo "Array empty"
else
    echo "Array non empty"
fi

หรือใช้ด้านอื่น ๆ

if [ -n "$array" ]; then
    echo "Array non empty"
else
    echo "Array empty"
fi

array=('' foo)ปัญหาเกี่ยวกับการแก้ปัญหาที่คือว่าถ้าอาร์เรย์มีการประกาศเช่นนี้ การตรวจสอบเหล่านี้จะรายงานอาร์เรย์ว่าว่างเปล่าในขณะที่ไม่ชัดเจน (ขอบคุณ @musiphil!)

การใช้งาน[ -z "$array[@]" ]ไม่ใช่วิธีแก้ปัญหาที่ชัดเจน การไม่ระบุวงเล็บปีกกาพยายามตีความ$arrayว่าเป็นสตริง ( [@]ในกรณีนั้นคือสตริงตัวอักษรแบบง่าย) และดังนั้นจึงมักจะรายงานว่าเป็นเท็จ: "สตริงตัวอักษร[@]ว่างเปล่าหรือไม่" ไม่ชัดเจน


7
[ -z "$array" ]หรือ[ -n "$array" ]ไม่ทำงาน ลองarray=('' foo); [ -z "$array" ] && echo emptyและมันจะพิมพ์emptyแม้ว่าarrayจะไม่ชัดเจน
musiphil

2
[[ -n "${array[*]}" ]]สอดแทรกอาร์เรย์ทั้งหมดเป็นสตริงซึ่งคุณตรวจสอบความยาวที่ไม่เป็นศูนย์ หากคุณคิดว่าarray=("" "")จะว่างเปล่าแทนที่จะมีสององค์ประกอบที่ว่างเปล่าสิ่งนี้อาจมีประโยชน์
Peter Cordes

@PeterCordes ฉันไม่คิดว่าจะทำงานได้ นิพจน์ประเมินค่าเป็นอักขระเว้นวรรคเดียวและ[[ -n " " ]]เป็น "จริง" ซึ่งน่าเสียดาย ความคิดเห็นของคุณเป็นสิ่งที่ฉันต้องการจะทำ
Michael

@Michael: อึคุณพูดถูก ใช้งานได้กับอาเรย์ 1 องค์ประกอบของสตริงว่างไม่ใช่ 2 องค์ประกอบ ฉันยังตรวจสอบทุบตีเก่าและยังคงมีความผิดพลาด; เหมือนที่คุณพูดset -xแสดงให้เห็นว่ามันขยายตัวอย่างไร ฉันเดาว่าฉันไม่ได้ทดสอบความคิดเห็นนั้นก่อนโพสต์ >. <คุณสามารถทำให้มันทำงานได้โดยการตั้งค่าIFS=''(บันทึก / เรียกคืนมันรอบ ๆ คำสั่งนี้) เนื่องจากส่วน"${array[*]}"ขยายจะแยกองค์ประกอบด้วยอักขระตัวแรกของ IFS (หรือเว้นวรรคหากไม่ได้ตั้งค่า) แต่ " ถ้า IFS เป็นโมฆะพารามิเตอร์จะเข้าร่วมโดยไม่ต้องคั่นตัวคั่น " (docs สำหรับ $ * params ตำแหน่ง แต่ฉันถือว่าเหมือนกันสำหรับอาร์เรย์)
Peter Cordes

@Michael: เพิ่มคำตอบที่ทำสิ่งนี้
Peter Cordes

3

ฉันตรวจสอบด้วยbash-4.4.0:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]} ]]; then
        echo not empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

และbash-4.1.5:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]:+${array[@]}} ]]; then
        echo non-empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

ในกรณีหลังคุณต้องการโครงสร้างต่อไปนี้:

${array[@]:+${array[@]}}

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

-e

ออกทันทีหากไปป์ไลน์ (ดู Pipelines) ซึ่งอาจประกอบด้วยคำสั่งง่าย ๆ เดียว (ดู Simple Commands) รายการ (ดูรายการ) หรือคำสั่งผสม (ดู Compound Commands) ส่งคืนสถานะที่ไม่เป็นศูนย์ เชลล์ไม่ได้ออกหากคำสั่งที่ล้มเหลวเป็นส่วนหนึ่งของรายการคำสั่งทันทีหลังจากนั้นสักครู่หรือจนกว่าคำสำคัญซึ่งเป็นส่วนหนึ่งของการทดสอบในคำสั่ง if ส่วนหนึ่งของคำสั่งใด ๆ ที่ดำเนินการใน && หรือ | | รายการยกเว้นคำสั่งที่ตามหลัง && หรือสุดท้าย | คำสั่งใด ๆ ในไปป์ไลน์ แต่สุดท้ายหรือหากสถานะการส่งคืนคำสั่งกลับด้านด้วย! หากคำสั่งผสมอื่นที่ไม่ใช่เชลล์ย่อยส่งคืนสถานะที่ไม่เป็นศูนย์เนื่องจากคำสั่งล้มเหลวขณะที่ -e กำลังถูกละเว้นเชลล์จะไม่ออก กับดักบน ERR หากตั้งค่าจะถูกดำเนินการก่อนที่เชลล์จะออก

ตัวเลือกนี้ใช้กับสภาพแวดล้อมของเชลล์และแต่ละสภาพแวดล้อมของ subshell แยกต่างหาก (ดูที่ Command Execution Environment) และอาจทำให้ subshells ออกก่อนที่จะดำเนินการคำสั่งทั้งหมดใน subshell

หากคำสั่งผสมหรือฟังก์ชั่นเปลือกดำเนินการในบริบทที่ -e จะถูกละเว้นไม่มีคำสั่งดำเนินการภายในคำสั่งผสมหรือฟังก์ชั่นร่างกายจะได้รับผลกระทบจากการตั้งค่า -e แม้ว่าการตั้งค่า -e และคำสั่งส่งกลับ สถานะความล้มเหลว หากคำสั่งผสมหรือฟังก์ชันเชลล์ตั้งค่า -e ขณะดำเนินการในบริบทที่ละเว้น -e การตั้งค่านั้นจะไม่มีผลกระทบใด ๆ จนกว่าคำสั่งผสมหรือคำสั่งที่มีการเรียกใช้ฟังก์ชันจะเสร็จสิ้น

-ยู

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

หากคุณไม่ต้องการสิ่งนั้นอย่าลังเลที่จะละเว้น:+${array[@]}ส่วนหนึ่ง

โปรดทราบว่าสิ่งสำคัญคือต้องใช้[[โอเปอเรเตอร์ที่นี่เมื่อ[คุณได้รับ:

$ cat 1.sh
#!/usr/bin/env bash
set -eu
array=(a b c d)
if [ "${array[@]}" ]; then
    echo non-empty
else
    echo empty
fi

$ ./1.sh
_/1.sh: line 4: [: too many arguments
empty

ด้วย-uคุณควรใช้${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski

@JakubBochenski คุณกำลังพูดถึงทุบตีรุ่นไหน? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri

ปัญหาในตัวอย่างเดียวคือปัญหา@อย่างแน่นอน คุณสามารถใช้ส่วน*ขยายของอาร์เรย์[ "${array[*]}" ]ได้หรือไม่ แต่ถึงกระนั้น[[ก็ยังทำงานได้ดี พฤติกรรมของทั้งสองนี้สำหรับอาเรย์ที่มีสตริงว่างหลายอันน่าแปลกใจเล็กน้อย ทั้งสอง[ ${#array[*]} ]และ[[ "${array[@]}" ]]เป็นเท็จสำหรับarray=()และarray=('')แต่เป็นจริงสำหรับarray=('' '')(สองสตริงว่างหรือมากกว่า) [ ${#array[@]} -gt 0 ]หากคุณอยากหนึ่งหรือเปล่าสตริงทุกให้จริงคุณสามารถใช้ หากคุณต้องการให้พวกเขาทั้งหมดผิดคุณอาจจะ//ออกไป
eisd

@eisd ฉันสามารถใช้งาน[ "${array[*]}" ]ได้ แต่ถ้าฉันต้องวิ่งเข้าไปในนิพจน์ดังกล่าวมันคงยากกว่าที่ฉันจะเข้าใจว่ามันทำอะไร ตั้งแต่[...]ทำงานในแง่ของสตริงในผลลัพธ์ของการแก้ไข ซึ่งตรงข้ามกับ[[...]]สิ่งที่สามารถตระหนักถึงสิ่งที่ถูกแก้ไข นั่นคือสามารถรู้ได้ว่ามันผ่านอาร์เรย์แล้ว [[ ${array[@]} ]]อ่านให้ฉันเป็น "ตรวจสอบว่าอาร์เรย์ไม่ว่างเปล่า" ในขณะ[ "${array[*]}" ]ที่ "ตรวจสอบว่าผลลัพธ์ของการแก้ไขขององค์ประกอบอาร์เรย์ทั้งหมดเป็นสตริงที่ไม่ว่าง"
x-yuri

... สำหรับพฤติกรรมที่มีสองสตริงว่างมันไม่น่าแปลกใจเลยสำหรับฉัน สิ่งที่น่าแปลกใจคือพฤติกรรมกับสตริงว่างหนึ่งอัน แต่มีเหตุผลพอสมควร เกี่ยวกับ[ ${#array[*]} ]คุณอาจหมายถึง[ "${array[*]}" ]ตั้งแต่อดีตเป็นจริงสำหรับองค์ประกอบจำนวนใด ๆ เนื่องจากจำนวนองค์ประกอบเป็นสตริงที่ไม่ว่างเสมอ เกี่ยวกับองค์ประกอบสององค์ประกอบหลังนิพจน์ภายในวงเล็บปีกกาจะขยายออก' 'ซึ่งเป็นสตริงที่ไม่ว่างเปล่า สำหรับ[[ ${array[@]} ]]พวกเขาเพียงแค่คิด (และถูกต้อง) ว่าอาร์เรย์ของสององค์ประกอบใด ๆ นั้นไม่ว่างเปล่า
x-yuri

2

หากคุณต้องการตรวจจับอาร์เรย์ด้วยองค์ประกอบที่ว่างเปล่าเช่นarr=("" "")ที่ว่างเปล่าเช่นเดียวกับarr=()

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

แต่การขยายตัวขององค์ประกอบที่แยกจากกันโดยตัวอักษรตัวแรกของ"${arr[*]}" IFSดังนั้นคุณต้องบันทึก / กู้คืน IFS และทำIFS=''เพื่อให้งานนี้หรืออื่น ๆ ตรวจสอบว่าความยาวสตริง == # ขององค์ประกอบอาร์เรย์ - 1 (อาร์เรย์ของnองค์ประกอบที่มีn-1ตัวคั่น) หากต้องการจัดการกับข้อผิดพลาดนั้นเป็นเรื่องง่ายที่สุดในการลดความต่อเนื่อง 1

arr=("" "")

## Assuming default non-empty IFS
## TODO: also check for ${#arr[@]} -eq 0
concat="${arr[*]} "      # n-1 separators + 1 space + array elements
[[ "${#concat}" -ne "${#arr[@]}" ]]  && echo not empty array || echo empty array

กรณีทดสอบด้วย set -x

### a non-empty element
$ arr=("" "x")
  + arr=("" "x")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat=' x '
  + [[ 3 -ne 2 ]]
  + echo not empty array
not empty array

### 2 empty elements
$ arr=("" "")
  + arr=("" "")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat='  '
  + [[ 2 -ne 2 ]]
  + echo empty array
empty array

น่าเสียดายที่สิ่งนี้ล้มเหลวสำหรับarr=(): [[ 1 -ne 0 ]]. ดังนั้นคุณจะต้องตรวจสอบอาร์เรย์ที่ว่างเปล่าจริงก่อนแยกกัน


IFS=''หรือมีการ อาจเป็นไปได้ว่าคุณต้องการบันทึก / กู้คืน IFS แทนที่จะใช้ subshell เนื่องจากคุณไม่สามารถรับผลลัพธ์จาก subshell ได้อย่างง่ายดาย

# inside a () subshell so we don't modify our own IFS
(IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)

ตัวอย่าง:

$ arr=("" "")
$ (IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)
   + IFS=
   + [[ -n '' ]]
   + echo empty array
empty array

ไม่ทำงานกับarr=()- มันยังคงเป็นเพียงสตริงที่ว่างเปล่า


ฉัน upvoted แต่ฉันเริ่มใช้[[ "${arr[*]}" = *[![:space:]]* ]]เนื่องจากฉันสามารถนับบนอักขระที่ไม่ใช่ WS อย่างน้อยหนึ่งตัว
Michael

@Michael: yup arr=(" ")ว่าเป็นตัวเลือกที่ดีถ้าคุณไม่จำเป็นต้องปฏิเสธ
Peter Cordes

0

ในกรณีของฉันคำตอบที่สองไม่เพียงพอเพราะอาจมีช่องว่าง ฉันมาพร้อมกับ:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi

echo | wcดูเหมือนไม่มีประสิทธิภาพโดยไม่จำเป็นเมื่อเทียบกับการใช้เชลล์บิวด์อิน
Peter Cordes

ไม่แน่ใจว่าฉันเข้าใจ @PeterCordes ฉันสามารถแก้ไขคำตอบที่สอง[ ${#errors[@]} -eq 0 ];ในวิธีการแก้ปัญหาช่องว่างได้หรือไม่ ฉันก็ชอบตัวบิวท์อินด้วย
Micha

ช่องว่างทำให้เกิดปัญหาได้อย่างไร ขยายจำนวนและทำงานได้ดีแม้หลังจากที่$# opts+=("")เช่นและฉันได้รับunset opts; opts+=("");opts+=(" "); echo "${#opts[@]}" 2คุณสามารถแสดงตัวอย่างของสิ่งที่ไม่ได้ผลหรือไม่
Peter Cordes

นานมาแล้ว IIRC แหล่งกำเนิดต้องพิมพ์อย่างน้อย "" เสมอ ดังนั้นสำหรับ opts = "" หรือ opts = ("") ฉันต้องการ 0 ไม่ใช่ 1 โดยไม่สนใจบรรทัดใหม่หรือสตริงว่าง
Micha

ตกลงดังนั้นคุณต้องปฏิบัติopts=("")เช่นเดียวกับopts=()? นั่นไม่ใช่อาร์เรย์ว่างเปล่า opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo emptyแต่คุณสามารถตรวจสอบอาร์เรย์ว่างเปล่าหรือองค์ประกอบแรกที่ว่างเปล่ากับ โปรดทราบว่าคำตอบปัจจุบันของคุณระบุว่า "ไม่มีตัวเลือก" สำหรับopts=("" "-foo")ซึ่งเป็นการหลอกลวงโดยสิ้นเชิงและสิ่งนี้จะจำลองพฤติกรรมดังกล่าว คุณสามารถ[[ -z "${opts[*]}" ]]เดาได้ว่าจะสอดแทรกองค์ประกอบอาร์เรย์ทั้งหมดลงในสตริงแบบแบนซึ่ง-zตรวจสอบความยาวที่ไม่เป็นศูนย์ หากตรวจสอบองค์ประกอบแรกก็เพียงพอ-z "$opts"แล้ว
Peter Cordes

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