ทดสอบว่าองค์ประกอบอยู่ในอาร์เรย์ในทุบตี


17

มีวิธีที่ดีในการตรวจสอบว่าอาร์เรย์มีองค์ประกอบในทุบตี (ดีกว่าวนลูปผ่าน)?

อีกวิธีหนึ่งในการตรวจสอบว่าจำนวนหรือสตริงเท่ากับค่าคงที่ที่กำหนดไว้ล่วงหน้าหรือไม่?

คำตอบ:


24

ใน Bash 4 คุณสามารถใช้อาเรย์แบบเชื่อมโยง:

# set up array of constants
declare -A array
for constant in foo bar baz
do
    array[$constant]=1
done

# test for existence
test1="bar"
test2="xyzzy"

if [[ ${array[$test1]} ]]; then echo "Exists"; fi    # Exists
if [[ ${array[$test2]} ]]; then echo "Exists"; fi    # doesn't

ในการตั้งค่าอาร์เรย์ในตอนแรกคุณสามารถทำการมอบหมายโดยตรงได้เช่นกัน:

array[foo]=1
array[bar]=1
# etc.

หรือด้วยวิธีนี้:

array=([foo]=1 [bar]=1 [baz]=1)

ที่จริงแล้วการทดสอบ [[]] ไม่ทำงานในกรณีที่ค่าว่างเปล่า เช่น "array ['test'] = ''" ในกรณีนี้มีคีย์ 'test' อยู่และคุณสามารถดูรายการได้ด้วย $ {! array [@]} แต่ "[[$ {array ['test']}]]; echo $?" echoes 1 ไม่ใช่ 0
haridsv

1
${array[$test1]}ง่าย แต่มีปัญหา: มันจะไม่ทำงานถ้าคุณใช้set -uในสคริปต์ของคุณ (ซึ่งแนะนำ) เพราะคุณจะได้รับ "ตัวแปรไม่ได้ผูกไว้"
tokland

@tokland: ใครแนะนำมัน? ฉันทำไม่ได้อย่างแน่นอน
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

@DennisWilliamson: ตกลงมีบางคนแนะนำ แต่ฉันคิดว่ามันจะดีถ้ามีทางออกที่ทำงานโดยไม่คำนึงถึงคุณค่าของธงเหล่านี้
tokland

บางโพสต์เกี่ยวกับset -u: davidpashley.com/articles/writing-robust-shell-scripts.html , blog.hashbang0.com/2010/05/18/robust-bash-scripts-part-one , openews.net/2012/bash- สคริปต์การเขียนที่แข็งแกร่ง
tokland

10

มันเป็นคำถามเก่า แต่ฉันคิดว่าวิธีแก้ปัญหาที่ง่ายที่สุดยังไม่ปรากฏ: test ${array[key]+_}แต่ผมคิดว่าสิ่งที่เป็นทางออกที่ง่ายที่สุดไม่เคยปรากฏเลย: ตัวอย่าง:

declare -A xs=([a]=1 [b]="")
test ${xs[a]+_} && echo "a is set"
test ${xs[b]+_} && echo "b is set"
test ${xs[c]+_} && echo "c is set"

ขาออก:

a is set
b is set

เพื่อดูว่างานนี้ตรวจสอบนี้


2
คู่มือข้อมูลแนะนำให้คุณใช้envเพื่อหลีกเลี่ยงความคลุมเครือในนามแฝง progs และฟังก์ชั่นอื่น ๆ ที่อาจใช้ชื่อ "test" env test ${xs[a]+_} && echo "a is set"ดังกล่าวข้างต้น นอกจากนี้คุณยังสามารถรับฟังก์ชั่นนี้ได้โดยใช้วงเล็บสองตัวเคล็ดลับเดียวกันจากนั้นตรวจสอบค่าว่าง:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski

นอกจากนี้คุณยังสามารถใช้งานได้ง่ายขึ้น[[ ${xs[b]+set} ]]
Arne L.

5

มีวิธีทดสอบว่าองค์ประกอบของอาร์เรย์ที่เชื่อมโยงมีอยู่ (ไม่ได้ตั้งค่า) ซึ่งแตกต่างจากที่ว่างเปล่า:

isNotSet() {
    if [[ ! ${!1} && ${!1-_} ]]
    then
        return 1
    fi
}

จากนั้นใช้:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi

เพียงแค่ทราบ: ประกาศ - ไม่ทำงานบนทุบตี 3.2.39 (เดเบียนเลนนี่) แต่ทำงานบนทุบตี 4.1.5 (บีบเดเบียน)
Paweł Polewicz

มีการแนะนำอาร์เรย์ที่เกี่ยวข้องใน Bash 4
Diego F. Durán

1
ทราบว่า=if ! some_check then return 1 some_checkดังนั้น: isNotSet() { [[ ... ]] }. ตรวจสอบวิธีการแก้ปัญหาของฉันด้านล่างคุณสามารถทำได้ง่ายๆ
tokland

3

คุณสามารถดูว่ามีรายการอยู่หรือไม่โดยการไพพ์เนื้อหาของอาร์เรย์ไปยัง grep

 printf "%s\n" "${mydata[@]}" | grep "^${val}$"

นอกจากนี้คุณยังสามารถรับดัชนีของรายการด้วย grep -n ซึ่งส่งคืนหมายเลขบรรทัดของการแข่งขัน (อย่าลืมลบ 1 เพื่อรับดัชนี zero-based) ซึ่งจะรวดเร็วพอสมควรยกเว้นอาร์เรย์ที่มีขนาดใหญ่มาก

# given the following data
mydata=(a b c "hello world")

for val in a c hello "hello world"
do
           # get line # of 1st matching entry
    ix=$( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 )

    if [[ -z $ix ]]
    then
        echo $val missing
    else
         # subtract 1.  Bash arrays are zero-based, but grep -n returns 1 for 1st line, not 0 
        echo $val found at $(( ix-1 ))
    fi
done

a found at 0
c found at 2
hello missing
hello world found at 3

คำอธิบาย:

  • $( ... ) เหมือนกับการใช้ backticks เพื่อดักจับเอาต์พุตของคำสั่งลงในตัวแปร
  • printf เอาต์พุต mydata หนึ่งองค์ประกอบต่อบรรทัด
  • (ทุกคำพูดที่จำเป็นพร้อมกับ@แทนที่จะ*. หลีกเลี่ยงการแยก "hello world" ออกเป็น 2 บรรทัด)
  • grepค้นหาสตริงที่แน่นอน: ^และ$จับคู่การเริ่มต้นและท้ายบรรทัด
  • grep -n ส่งคืนบรรทัด # ในรูปแบบ 4: hello world
  • grep -m 1 ค้นหาคู่แรกเท่านั้น
  • cut แยกเพียงหมายเลขบรรทัด
  • ลบ 1 จากหมายเลขบรรทัดที่ส่งคืน

คุณสามารถพับการลบลงในคำสั่งได้ แต่ทดสอบ -1 สำหรับการขาดหายไป:

ix=$(( $( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 ) - 1 ))

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) คำนวณเลขจำนวนเต็ม

1

ฉันไม่คิดว่าคุณสามารถทำได้อย่างถูกต้องโดยไม่ต้องวนลูปจนกว่าคุณจะมีข้อมูลที่ จำกัดมากในอาเรย์

นี่คือตัวแปรง่ายตัวหนึ่งที่บอกได้อย่างถูกต้องว่า"Super User"มีอยู่ในอาร์เรย์ แต่มันก็ยังบอกว่า"uper Use"อยู่ในอาเรย์

MyArray=('Super User' 'Stack Overflow' 'Server Fault' 'Jeff' );
FINDME="Super User"

FOUND=`echo ${MyArray[*]} | grep "$FINDME"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

#
# If you where to add anchors < and > to the data it could work
# This would find "Super User" but not "uper Use"
#

MyArray2=('<Super User>' '<Stack Overflow>' '<Server Fault>' '<Jeff>' );

FOUND=`echo ${MyArray2[*]} | grep "<$FINDME>"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

ปัญหาคือไม่มีวิธีง่าย ๆ ในการเพิ่มจุดยึด (ที่ฉันคิด) นอกเหนือจากการวนซ้ำผ่านอาร์เรย์ หากคุณไม่สามารถเพิ่มพวกเขาก่อนที่จะใส่ลงในอาร์เรย์ ...


มันเป็นทางออกที่ดีเมื่อค่าคงที่เป็นตัวอักษรและตัวเลข (ด้วยgrep "\b$FINDME\b") อาจจะทำงานกับค่าคงที่ไม่ใช่ตัวอักษรและตัวเลขที่ไม่มีช่องว่างด้วย"(^| )$FINDME(\$| )"(หรือบางอย่างเช่นนั้น ... ฉันไม่เคยเรียนรู้สิ่งที่ grep grep ใช้ของ regexp)
Tgr

1
#!/bin/bash
function in_array {
  ARRAY=$2
  for e in ${ARRAY[*]}
  do
    if [[ "$e" == "$1" ]]
    then
      return 0
    fi
  done
  return 1
}

my_array=(Drupal Wordpress Joomla)
if in_array "Drupal" "${my_array[*]}"
  then
    echo "Found"
  else
    echo "Not found"
fi

1
คุณช่วยอธิบายได้ไหมว่าทำไมคุณถึงแนะนำวิธีนี้ OP ถามว่ามีวิธีที่จะทำมันได้โดยไม่ต้องวนลูปผ่านอาร์เรย์in_arrayซึ่งเป็นสิ่งที่คุณกำลังทำอยู่ใน ไชโย
bertieb

อย่างน้อยวงนั้นจะถูกกำหนดในฟังก์ชั่นซึ่งอาจดีพอสำหรับหลาย ๆ กรณี (ที่มีชุดข้อมูลขนาดเล็ก) และไม่ต้องการ bash 4+ น่า${ARRAY[@]}จะใช้
โทเบียส
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.