ตรวจสอบว่าอาร์เรย์ Bash มีค่าหรือไม่


443

ใน Bash วิธีที่ง่ายที่สุดในการทดสอบว่าอาร์เรย์มีค่าที่แน่นอนคืออะไร

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

function contains() {
    local n=$#
    local value=${!n}
    for ((i=1;i < $#;i++)) {
        if [ "${!i}" == "${value}" ]; then
            echo "y"
            return 0
        fi
    }
    echo "n"
    return 1
}

A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
    echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
    echo "contains three"
fi

ฉันไม่แน่ใจว่ามันเป็นทางออกที่ดีที่สุด แต่ดูเหมือนว่าจะทำงาน

คำตอบ:


458

วิธีการนี้มีข้อได้เปรียบที่ไม่จำเป็นต้องวนซ้ำองค์ประกอบทั้งหมด (อย่างน้อยก็ไม่ชัดเจน) แต่เนื่องจากarray_to_string_internal()ในarray.cยังคงวนลูปมากกว่าองค์ประกอบของอาร์เรย์และเชื่อมต่อพวกมันเข้ากับสตริงมันอาจจะไม่ได้มีประสิทธิภาพมากกว่าโซลูชั่นลูปที่เสนอ แต่มันอ่านได้มากกว่า

if [[ " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array contains value
fi

if [[ ! " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array doesn't contain value
fi

โปรดทราบว่าในกรณีที่ค่าที่คุณค้นหาเป็นหนึ่งในคำในองค์ประกอบอาร์เรย์ที่มีช่องว่างมันจะให้ผลบวกเท็จ ตัวอย่างเช่น

array=("Jack Brown")
value="Jack"

regex จะเห็น "Jack" ว่ากำลังอยู่ในอาร์เรย์แม้ว่าจะไม่ใช่ ดังนั้นคุณจะต้องเปลี่ยนIFSและตัวคั่นใน regex ของคุณหากคุณยังต้องการใช้วิธีนี้เช่นนี้

IFS=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS
value="Jack"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "true"
else
    echo "false"
fi

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

เห็นได้ชัดว่าสิ่งนี้สามารถใช้เป็นคำแถลงการทดสอบได้ด้วยทำให้สามารถแสดงเป็นซับในได้

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"

1
ฉันเพิ่มช่องว่างเมื่อเริ่มต้นของการจับคู่ค่า regex แรกเพื่อที่จะจับคู่กับคำเท่านั้นไม่ใช่สิ่งที่ลงท้ายด้วยคำ ใช้งานได้ดี อย่างไรก็ตามฉันไม่เข้าใจว่าทำไมคุณถึงใช้เงื่อนไขข้อที่สองงานชิ้นแรกจะไม่ทำงานเพียงลำพัง
JStrahl

1
@AwQiruiGuo ฉันไม่แน่ใจว่าฉันกำลังติดตาม คุณกำลังพูดเกี่ยวกับอาร์เรย์ด้วยตัวอักษรดอลลาร์? ถ้าเป็นเช่นนั้นให้แน่ใจว่าได้หลีกหนีเงินดอลลาร์ในมูลค่าที่คุณเทียบกับแบ็กสแลช
คีแกน

10
Oneliner: [[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
ericson.cepeda

3
Shellcheck บ่นเกี่ยวกับวิธีแก้ปัญหานี้ SC2199 และ SC2076 ฉันไม่สามารถแก้ไขคำเตือนโดยไม่ทำลายการทำงาน มีความคิดเห็นเกี่ยวกับสิ่งอื่นนอกจากการปิดใช้งาน shellcheck สำหรับบรรทัดนั้นหรือไม่
Ali Essam

4
SC2076ifเป็นเรื่องง่ายที่จะแก้ไขเพียงแค่เอาคำพูดคู่ใน ฉันไม่คิดว่าจะมีวิธีหลีกเลี่ยงSC2199ด้วยวิธีนี้ คุณจะต้องวนลูปอย่างชัดเจนแม้ว่าอาเรย์ดังแสดงในโซลูชันอื่น ๆ หรือละเว้นคำเตือน
คีแกน

388

ด้านล่างเป็นฟังก์ชั่นเล็ก ๆ สำหรับการบรรลุเป้าหมายนี้ สตริงการค้นหาเป็นอาร์กิวเมนต์แรกและส่วนที่เหลือเป็นองค์ประกอบอาร์เรย์:

containsElement () {
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

การทดสอบการทำงานของฟังก์ชันนั้นอาจมีลักษณะดังนี้:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1

5
ทำงานได้ดี! "${array[@]}"ฉันเพียงแค่ต้องจำที่จะผ่านอาร์เรย์เป็นด้วยคำพูด: มิฉะนั้นองค์ประกอบที่มีช่องว่างจะทำให้ฟังก์ชันการทำงานหยุดชะงัก
Juve

26
ดี ฉันจะเรียกมันว่า elementIn () เพราะมันจะตรวจสอบว่าอาร์กิวเมนต์แรกมีอยู่ในครั้งที่สองหรือไม่ containElements () ดูเหมือนว่าอาร์เรย์จะไปก่อน สำหรับมือใหม่อย่างฉันตัวอย่างของวิธีการใช้ฟังก์ชั่นที่ไม่ได้เขียนถึง stdout ในคำสั่ง "if" จะช่วยได้: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; ขอบคุณสำหรับความช่วยเหลือของคุณ!
GlenPeterson

5
@Bluz && construct เป็นตัวดำเนินการบูลีนและ การใช้ตัวดำเนินการบูลีนจะสร้างคำสั่งบูลีนบูลีนลอจิกบอกว่าข้อความทั้งหมดจะเป็นจริงได้หากข้อความทั้งก่อนและหลัง && ประเมินเป็นจริง สิ่งนี้ใช้เป็นทางลัดที่ติดตั้งไว้และถ้าบล็อกการทดสอบจะถูกประเมินและหากเป็นเท็จไม่จำเป็นต้องประเมินผลตอบแทนเนื่องจากไม่เกี่ยวข้องกับคำสั่งทั้งหมดเมื่อการทดสอบล้มเหลวและไม่ทำงาน หากการทดสอบประสบความสำเร็จแล้วคำว่า su ของคำสั่งบูลีนจะต้องใช้ผลลัพธ์ของการส่งคืนเพื่อพิจารณารหัสเพื่อให้ทำงานได้
peteches

4
@James โดยการประชุมว่ารหัสความสำเร็จใน bash คือ "0" และข้อผิดพลาดคือทุกอย่าง> = 1 นี่คือสาเหตุที่ผลตอบแทน 0 เมื่อสำเร็จ :)
tftd

11
@Stelios shiftเลื่อนรายการอาร์กิวเมนต์ 1 ไปทางซ้าย (ปล่อยอาร์กิวเมนต์แรก) และforไม่มีการinวนซ้ำโดยปริยายเหนือรายการอาร์กิวเมนต์
Christian

58
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found

69
โปรดทราบว่าสิ่งนี้จะไม่ซ้ำไปตามแต่ละองค์ประกอบในอาเรย์แยกจากกัน ... แต่มันก็แค่เชื่อมอาเรย์และตรงกับ "สอง" เป็นสตริงย่อย สิ่งนี้อาจทำให้เกิดพฤติกรรมที่ไม่พึงประสงค์หากมีการทดสอบว่าคำว่า "สอง" ที่แน่นอนเป็นองค์ประกอบในอาร์เรย์หรือไม่
MartyMacGyver

ฉันคิดว่าสิ่งนี้จะได้ผลสำหรับฉันในการเปรียบเทียบประเภทไฟล์ แต่พบว่าเมื่อตัวนับเพิ่มขึ้นมันนับค่ามากเกินไป ... boo!
Mike Q

17
ไม่ถูกต้อง! เหตุผล: case "${myarray[@]}" in *"t"*) echo "found" ;; esacผลลัพธ์:found
Sergej Jevsejev

@MartyMacGyver คุณช่วยโปรดดูคำตอบนี้ได้จากstackoverflow.com/a/52414872/1619950
Aleksandr Podkutin

45
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

สำหรับสตริง:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done

ที่กล่าวว่าคุณสามารถใช้การจัดทำดัชนีสำหรับวงและหลีกเลี่ยงการถูกฆ่าเมื่อองค์ประกอบอาร์เรย์มี IFS: สำหรับ ((i = 0; i <$ {# array [@]}; i ++))
mkb

@Matt: คุณต้องระวังในการใช้${#}เนื่องจาก Bash รองรับอาร์เรย์เบาบาง
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

@ เปาโลถ้าอาร์เรย์ของคุณมีช่องว่างแล้วเพียงแค่เปรียบเทียบมันเป็นสตริง ช่องว่างเป็นสตริงเช่นกัน
Scott

@Paolo: คุณสามารถสร้างฟังก์ชั่นนั้นได้ แต่อาร์เรย์ไม่สามารถส่งผ่านเป็นอาร์กิวเมนต์ได้ดังนั้นคุณจะต้องถือว่ามันเป็นโลก
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

เดนนิสพูดถูก จากคู่มืออ้างอิง bash: "หากคำนี้เป็นเครื่องหมายคำพูดสองเท่า ... $ {name [@]} ขยายองค์ประกอบของชื่อแต่ละคำเป็นคำที่แยกต่างหาก"
mkb

37

โซลูชันเดียว

printf '%s\n' ${myarray[@]} | grep -P '^mypattern$'

คำอธิบาย

printfคำสั่งพิมพ์องค์ประกอบของอาร์เรย์แต่ละบรรทัดที่แยกต่างหาก

grepคำสั่งใช้ตัวอักษรพิเศษ^และ$เพื่อหาบรรทัดที่มีว่ารูปแบบให้เป็นmypattern(ไม่มากไม่น้อยกว่า)


การใช้

ในการใส่if ... thenคำสั่งลงในข้อความ:

if printf '%s\n' ${myarray[@]} | grep -q -P '^mypattern$'; then
    # ...
fi

ฉันเพิ่มการ-qตั้งค่าสถานะลงในgrepนิพจน์เพื่อไม่ให้พิมพ์ออกมาตรงกัน มันจะรักษาการมีอยู่ของการแข่งขันว่า "เป็นจริง"


ทางออกที่ดี! ใน GNU grep ยังมี "--line-regexp" ซึ่งสามารถแทนที่ "-P" และ ^ และ $ ในรูปแบบ: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8

19

หากคุณต้องการประสิทธิภาพคุณไม่ต้องการวนลูปทั้งหมดของคุณทุกครั้งที่ค้นหา

ในกรณีนี้คุณสามารถสร้างอาเรย์แบบเชื่อมโยง (ตารางแฮชหรือพจนานุกรม) ที่แสดงถึงดัชนีของอาเรย์นั้น นั่นคือแผนที่แต่ละองค์ประกอบอาร์เรย์เป็นดัชนีในอาร์เรย์:

make_index () {
  local index_name=$1
  shift
  local -a value_array=("$@")
  local i
  # -A means associative array, -g means create a global variable:
  declare -g -A ${index_name}
  for i in "${!value_array[@]}"; do
    eval ${index_name}["${value_array[$i]}"]=$i
  done
}

จากนั้นคุณสามารถใช้สิ่งนี้:

myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"

และทดสอบการเป็นสมาชิกเช่น:

member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND

หรือยัง:

if [ "${myarray_index[$member]}" ]; then 
  echo FOUND
fi

ขอให้สังเกตว่าการแก้ปัญหานี้ทำสิ่งที่ถูกต้องแม้ว่าจะมีช่องว่างในค่าที่ทดสอบหรือในค่าอาร์เรย์

ในฐานะโบนัสคุณจะได้รับดัชนีของค่าภายในอาร์เรย์ด้วย:

echo "<< ${myarray_index[$member]} >> is the index of $member"

+1 สำหรับแนวคิดที่คุณควรใช้อาเรย์แบบเชื่อมโยง ฉันคิดว่ารหัสสำหรับmake_indexเป็นสิ่งที่ถูกประดิษฐ์ขึ้นมาอีกเล็กน้อยเนื่องจากการอ้อมไป คุณสามารถใช้ชื่ออาเรย์คงที่กับรหัสที่ง่ายกว่ามาก
musiphil

17

ฉันมักจะใช้:

inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)

ค่าที่ไม่ใช่ศูนย์หมายถึงพบคู่ที่ตรงกัน


จริงนี่เป็นทางออกที่ง่ายที่สุด - ควรทำเครื่องหมายคำตอบในความคิดของฉัน อย่างน้อยมี upvote ของฉัน! [:
ToVine

2
ที่จะไม่ทำงานสำหรับเข็มที่คล้ายกัน ตัวอย่างเช่นhaystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan

1
จริงแท้แน่นอน. การเข้าร่วมกับตัวคั่นที่ไม่ปรากฏในองค์ประกอบใด ๆ และการเพิ่มลงในเข็มจะช่วยได้ อาจจะเป็นสิ่งที่ชอบ ... (ยังไม่ทดลอง)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti

2
การใช้ grep -x จะหลีกเลี่ยงผลบวกปลอม: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher

อาจinarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)เป็นเพราะ -x ทำให้ grep พยายามจับคู่สตริงอินพุตทั้งหมด
MI Wright

17

อีกหนึ่งซับโดยไม่มีฟังก์ชั่น:

(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"

ขอบคุณ @Qwerty สำหรับหัวขึ้นเกี่ยวกับช่องว่าง!

ฟังก์ชั่นที่เกี่ยวข้อง:

find_in_array() {
  local word=$1
  shift
  for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
  return 1
}

ตัวอย่าง:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"

1
ทำไมเราต้องมี subshell ที่นี่?
codeforester

1
@ codeforester อันนี้เก่า ... แต่ตามที่เขียนไว้คุณต้องการมันเพื่อที่จะแยกจากมันนั่นคือสิ่งที่exit 0ทำ (หยุดโดยเร็วหากพบ)
estani

จุดสิ้นสุดของหนึ่งซับควร|| echo not foundแทน|| not foundหรือเชลล์จะพยายามดำเนินการคำสั่งด้วยชื่อที่ไม่ได้มีอาร์กิวเมนต์พบถ้าค่าที่ร้องขอไม่ได้อยู่ในอาร์เรย์
zoke

11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

ตอนนี้จัดการกับอาร์เรย์ที่ว่างเปล่าอย่างถูกต้อง


สิ่งนี้แตกต่างจากคำตอบของ @ patrik อย่างไร ความแตกต่างเดียวที่ฉันเห็นคือ"$e" = "$1"(แทนที่จะ"$e" == "$1") ซึ่งดูเหมือนว่าเป็นข้อบกพร่อง
CivFan

1
มันไม่ใช่. @ Patrik ได้รวมความคิดเห็นของฉันในคำตอบเดิมของเขากลับมาแล้ว (แก้ไข # 4) หมายเหตุ: "e" == "$1"ชัดเจนกว่าทางวากยสัมพันธ์
ยานน์

@CivFan ในรูปแบบปัจจุบันนี้จะสั้นกว่าคำตอบของ Patrik เนื่องจาก $ {@: 2} ที่สง่างามและเอกสาร $ $ ด้วยตนเอง ฉันจะเพิ่มการอ้างอิงที่ไม่จำเป็นใน [[]]
Hontvári Levente

9

นี่คือผลงานเล็ก ๆ :

array=(word "two words" words)  
search_string="two"  
match=$(echo "${array[@]:0}" | grep -o $search_string)  
[[ ! -z $match ]] && echo "found !"  

หมายเหตุ: วิธีนี้จะไม่แยกความแตกต่างของคำว่า "สองคำ" แต่ไม่จำเป็นต้องใช้ในคำถาม


อันนี้ช่วยฉันได้มาก ขอบคุณ!
Ed Manet

คำถามไม่ได้บอกอย่างชัดเจนว่าคุณต้องให้คำตอบที่ถูกต้อง แต่ฉันคิดว่านั่นเป็นนัยในคำถาม ... อาร์เรย์ไม่ได้มีค่า "สอง"
tetsujin

ด้านบนจะรายงานการแข่งขันของ 'rd'
Noel Yap

6

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

array=(word "two words" words)
if [[ ${array[@]} =~ words ]]
then
    echo "Checking"
    for element in "${array[@]}"
    do
        if [[ $element == "words" ]]
        then
            echo "Match"
        fi
    done
fi

สิ่งนี้จะส่งออก "กำลังตรวจสอบ" และ "จับคู่" ด้วยarray=(word "two words" something)มันจะออกเฉพาะ "การตรวจสอบ" ด้วยarray=(word "two widgets" something)จะไม่มีการส่งออก


ทำไมไม่เพียงแค่แทนที่wordsด้วย regex ^words$ที่จับคู่กับสตริงทั้งหมดเท่านั้นซึ่งไม่จำเป็นต้องตรวจสอบแต่ละรายการอย่างสมบูรณ์?
Dejay Clayton

@DejayClayton: เพราะpattern='^words$'; if [[ ${array[@]} =~ $pattern ]]จะไม่ตรงกันเพราะมันตรวจสอบอาร์เรย์ทั้งหมดในครั้งเดียวราวกับว่ามันเป็นสเกลาร์ การตรวจสอบรายบุคคลในคำตอบของฉันจะต้องทำก็ต่อเมื่อมีเหตุผลที่จะดำเนินการตามการแข่งขันคร่าวๆ
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

อาฉันเห็นสิ่งที่คุณพยายามจะทำ ฉันเสนอคำตอบที่แตกต่างซึ่งมีประสิทธิภาพและความปลอดภัยมากกว่า
Dejay Clayton

6

นี่ใช้งานได้สำหรับฉัน:

# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
    # odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
    local list=$1[@]
    local elem=$2

    # echo "list" ${!list}
    # echo "elem" $elem

    for i in "${!list}"
    do
        # echo "Checking to see if" "$i" "is the same as" "${elem}"
        if [ "$i" == "${elem}" ] ; then
            # echo "$i" "was the same as" "${elem}"
            return 0
        fi
    done

    # echo "Could not find element"
    return 1
}

ตัวอย่างการโทร:

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi

5
a=(b c d)

if printf '%s\0' "${a[@]}" | grep -Fqxz c
then
  echo 'array “a” contains value “c”'
fi

หากคุณต้องการคุณสามารถใช้ตัวเลือกยาวที่เทียบเท่าได้:

--fixed-strings --quiet --line-regexp --null-data

1
สิ่งนี้ใช้ไม่ได้กับ BSD-grep บน Mac เนื่องจากไม่มี --null-data :(
จะ

4

การยืมตัวจากคำตอบของเดนนิสวิลเลียมสันคำตอบต่อไปนี้ได้รวมเอาอาร์เรย์การอ้างถึงเชลล์ที่ปลอดภัยและการแสดงออกปกติเพื่อหลีกเลี่ยงความต้องการ: วนซ้ำวนซ้ำ; ใช้ท่อหรือกระบวนการย่อยอื่น ๆ หรือใช้ยูทิลิตีแบบ non-bash

declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[@]}"

if [[ "${array_str},," =~ ,,words,, ]]
then
   echo 'Matches'
else
   echo "Doesn't match"
fi

โค้ดด้านบนใช้งานได้โดยใช้นิพจน์ทั่วไปของ Bash เพื่อจับคู่กับเนื้อหาอาร์เรย์ มีหกขั้นตอนสำคัญเพื่อให้แน่ใจว่าการจับคู่นิพจน์ทั่วไปไม่สามารถถูกหลอกได้ด้วยการรวมค่าที่ฉลาดภายในอาเรย์:

  1. สร้างสตริงเปรียบเทียบโดยใช้ทุบตีในตัวของprintfเปลือก %qquoting, การอ้างถึงเชลล์จะทำให้แน่ใจว่าอักขระพิเศษกลายเป็น "เชลล์ปลอดภัย" ด้วยการถูกใช้เครื่องหมายแบ็กสแลช\ด้วยการหนีไปกับทับขวา
  2. เลือกอักขระพิเศษเพื่อใช้เป็นตัวคั่นค่า คั่นจะต้องมีหนึ่งในตัวละครพิเศษที่จะกลายเป็นหนีเมื่อใช้%q; นั่นเป็นวิธีเดียวที่จะรับประกันได้ว่าค่าภายในอาร์เรย์ไม่สามารถสร้างขึ้นด้วยวิธีที่ชาญฉลาดเพื่อหลอกจับคู่นิพจน์ทั่วไป ฉันเลือกจุลภาค,เพราะตัวละครนั้นปลอดภัยที่สุดเมื่อประเมินหรือนำไปใช้ในทางที่ผิดโดยไม่คาดคิด
  3. รวมองค์ประกอบอาร์เรย์ทั้งหมดไว้ในสตริงเดียวโดยใช้สองอินสแตนซ์ของอักขระพิเศษเพื่อใช้เป็นตัวคั่น การใช้เครื่องหมายจุลภาคเป็นตัวอย่างที่ผมใช้เป็นอาร์กิวเมนต์ไป,,%q printfสิ่งนี้มีความสำคัญเนื่องจากอักขระพิเศษสองตัวสามารถปรากฏขึ้นติดกันได้เมื่อปรากฏเป็นตัวคั่นเท่านั้น อินสแตนซ์อื่น ๆ ทั้งหมดของอักขระพิเศษจะถูกหลีกเลี่ยง
  4. ผนวกสองอินสแตนซ์ต่อท้ายของตัวคั่นไปยังสตริงเพื่อให้ตรงกับองค์ประกอบสุดท้ายของอาร์เรย์ ดังนั้นแทนที่จะเปรียบเทียบกับการเปรียบเทียบกับ${array_str}${array_str},,
  5. หากสตริงเป้าหมายที่คุณกำลังค้นหาจัดหาให้โดยตัวแปรผู้ใช้คุณต้องหลีกเลี่ยงอินสแตนซ์ทั้งหมดของอักขระพิเศษด้วยแบ็กสแลช มิฉะนั้นการจับคู่นิพจน์ทั่วไปจะเสี่ยงต่อการถูกหลอกโดยองค์ประกอบอาเรย์ที่สร้างขึ้นอย่างชาญฉลาด
  6. ทำการนิพจน์ปกติของ Bash จับคู่กับสตริง

ฉลาดมาก. ฉันสามารถเห็นได้ว่าปัญหาที่อาจเกิดขึ้นส่วนใหญ่ได้รับการป้องกัน แต่ฉันต้องการทดสอบเพื่อดูว่ามีมุมกรณีใดหรือไม่ นอกจากนี้ฉันต้องการเห็นตัวอย่างของการจัดการจุด 5 บางสิ่งบางอย่างเช่นprintf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]บางที
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino

3

คำตอบเล็ก ๆ ของ @ ghostdog74 เกี่ยวกับการใช้caseตรรกะเพื่อตรวจสอบว่าอาร์เรย์มีค่าเฉพาะ:

myarray=(one two three)
word=two
case "${myarray[@]}" in  ("$word "*|*" $word "*|*" $word") echo "found" ;; esac

หรือextglobเปิดใช้ตัวเลือกคุณสามารถทำสิ่งนี้ได้:

myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac

นอกจากนี้เราสามารถทำได้ด้วย ifคำสั่ง:

myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi

2

ให้:

array=("something to search for" "a string" "test2000")
elem="a string"

จากนั้นตรวจสอบอย่างง่ายของ:

if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[@]/#/${c}} ${c}" =~ $p ]]; then
  echo "$elem exists in array"
fi

ที่ไหน

c is element separator
p is regex pattern

(เหตุผลในการกำหนด p แยกต่างหากแทนที่จะใช้นิพจน์ภายใน [[]] โดยตรงคือเพื่อรักษาความเข้ากันได้สำหรับ bash 4)


รักการใช้คำว่า "ง่าย" ที่นี่ ... 😂
Christian

2

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

$find="myword"
$array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
  echo "Array contains myword";
fi

สิ่งนี้จะไม่ทำให้เกิดขึ้นwordหรือvalคำที่ตรงกันทั้งหมดเท่านั้น มันจะแตกถ้าแต่ละค่าอาร์เรย์มีหลายคำ


1

ฉันมักจะเขียนโปรแกรมอรรถประโยชน์เหล่านี้เพื่อใช้งานในชื่อของตัวแปรมากกว่าค่าตัวแปรหลักเพราะ bash ไม่สามารถส่งผ่านตัวแปรได้โดยการอ้างอิง

นี่คือเวอร์ชันที่ทำงานกับชื่อของอาร์เรย์:

function array_contains # array value
{
    [[ -n "$1" && -n "$2" ]] || {
        echo "usage: array_contains <array> <value>"
        echo "Returns 0 if array contains value, 1 otherwise"
        return 2
    }

    eval 'local values=("${'$1'[@]}")'

    local element
    for element in "${values[@]}"; do
        [[ "$element" == "$2" ]] && return 0
    done
    return 1
}

ด้วยสิ่งนี้ตัวอย่างของคำถามจะกลายเป็น:

array_contains A "one" && echo "contains one"

เป็นต้น


ใครสามารถโพสต์ตัวอย่างของสิ่งนี้ใช้ภายในถ้าโดยเฉพาะอย่างยิ่งวิธีที่คุณผ่านในอาร์เรย์ ฉันพยายามตรวจสอบเพื่อดูว่าอาร์กิวเมนต์ของสคริปต์ถูกส่งผ่านโดยถือ params เป็นอาร์เรย์ แต่ไม่ต้องการทำงาน params = ("$ @") check = array_contain $ {params} 'SKIPDIRCHECK' ถ้า [[$ {check} == 1]]; จากนั้น .... แต่เมื่อรันสคริปต์ด้วย 'asas' เป็นอาร์กิวเมนต์มันจะพูดว่า asas: ไม่พบคำสั่ง : /
Steve Childs

1

การใช้ grepและprintf

จัดรูปแบบสมาชิกอาเรย์แต่ละตัวในบรรทัดใหม่จากนั้นgrepตามด้วยบรรทัด

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
ตัวอย่าง:
$ array=("word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true

โปรดทราบว่าสิ่งนี้ไม่มีปัญหากับ delimeter และช่องว่าง


1

การตรวจสอบหนึ่งบรรทัดโดยไม่มี 'grep' และลูป

if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
  echo "array contains '$item'"
else
  echo "array does not contain '$item'"
fi

วิธีการนี้ไม่ใช้โปรแกรมอรรถประโยชน์ภายนอกเช่นgrepหรือลูป

เกิดอะไรขึ้นที่นี่คือ:

  • เราใช้ตัวจับคู่สตริงย่อยเพื่อค้นหารายการของเราในอาร์เรย์ที่ต่อกันเป็นสตริง
  • เราตัดค่าบวกที่เป็นเท็จที่เป็นไปได้ออกโดยใส่รายการค้นหาของเราระหว่างตัวคั่นคู่
  • เราใช้อักขระที่ไม่สามารถพิมพ์ได้เป็นตัวคั่นเพื่อให้อยู่ในด้านที่ปลอดภัย
  • เราบรรลุตัวคั่นของเราที่ถูกใช้สำหรับการต่อข้อมูลอาร์เรย์ด้วยเช่นกันโดยการแทนที่ IFSค่าตัวแปร
  • เราทำการIFSเปลี่ยนค่านี้ชั่วคราวโดยการประเมินนิพจน์เงื่อนไขของเราใน sub-shell (ภายในวงเล็บหนึ่งคู่)

กำจัด dlm ใช้ IFS โดยตรง
Robin A. Meade

นี่คือคำตอบที่ดีที่สุด ฉันชอบมันมากฉันเขียนฟังก์ชั่นโดยใช้เทคนิคนี้
Robin A. Meade

1

ใช้การขยายพารามิเตอร์:

$ {พารามิเตอร์: + word} หากพารามิเตอร์เป็นโมฆะหรือไม่มีการตั้งค่าจะไม่มีการแทนที่สิ่งใดมิฉะนั้นการขยายตัวของคำจะถูกแทนที่

declare -A myarray
myarray[hello]="world"

for i in hello goodbye 123
do
  if [ ${myarray[$i]:+_} ]
  then
    echo ${!myarray[$i]} ${myarray[$i]} 
  else
    printf "there is no %s\n" $i
  fi
done

${myarray[hello]:+_}ใช้งานได้ดีสำหรับอาร์เรย์ associaive แต่ไม่ใช่สำหรับอาร์เรย์ที่จัดทำดัชนีตามปกติ คำถามคือเกี่ยวกับการค้นหาค่าในอาเรย์ไม่ใช่ตรวจสอบว่ามีคีย์ของอาเรย์เชื่อมโยงอยู่หรือไม่
Eric

0

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

array=("word" "two words") # let's look for "two words"

ใช้grepและprintf:

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

ใช้for:

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

เพื่อไม่ให้เพิ่มผลลัพธ์ || <run_your_if_notfound_command_here>


0

นี่คือสิ่งที่ฉันทำ

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

function array_contains { # arrayname value
  local -A _arr=()
  local IFS=
  eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
  return $(( 1 - 0${_arr[$2]} ))
}

สิ่งนี้ทำงานได้โดยการสร้างอาเรย์แบบเชื่อมโยงชั่วคราว_arrซึ่งมีดัชนีมาจากค่าของอาเรย์อินพุต (โปรดทราบว่าอาร์เรย์ที่เชื่อมโยงมีอยู่ใน bash 4 ขึ้นไปดังนั้นฟังก์ชันนี้จะไม่ทำงานใน bash รุ่นก่อนหน้านี้) เราตั้งค่า$IFSให้หลีกเลี่ยงการแยกคำในช่องว่าง

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

$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$

โปรดทราบว่าทุกสิ่งที่ฟังก์ชั่นนี้ใช้นั้นมีอยู่ในตัวเพื่อทุบตีดังนั้นจึงไม่มีท่อภายนอกลากคุณลงแม้ในการขยายคำสั่ง

และถ้าคุณไม่ชอบการใช้eval... ดีคุณมีอิสระที่จะใช้วิธีการอื่น :-)


เกิดอะไรขึ้นถ้าอาร์เรย์มีวงเล็บเหลี่ยม
gniourf_gniourf

@gniourf_gniourf - ดูเหมือนว่าจะดีถ้าวงเล็บเหลี่ยมมีความสมดุล แต่ฉันเห็นได้ว่าเป็นปัญหาหากอาร์เรย์ของคุณมีค่าด้วยวงเล็บเหลี่ยมที่ไม่สมดุล ในกรณีนั้นฉันจะขอevalคำแนะนำในตอนท้ายของคำตอบ :)
ghoti

มันไม่ใช่ว่าฉันไม่ชอบeval(ผมไม่มีอะไรกับมันแตกต่างจากคนส่วนใหญ่ที่ร้องไห้evalเป็นความชั่วร้ายส่วนใหญ่ไม่เข้าใจสิ่งที่ชั่วร้ายเกี่ยวกับเรื่องนี้) เพียงว่าคำสั่งของคุณเสีย บางที%qแทน%sจะดีกว่า
gniourf_gniourf

1
@gniourf_gniourf: ฉันแค่หมายถึง "วิธีอื่น" บิต (และฉันทั้งหมดกับคุณอีกครั้งevalชัดเจน) แต่คุณถูกต้องอย่างแน่นอน%qดูเหมือนจะช่วยได้โดยไม่ทำลายอะไรที่ฉันเห็น (ฉันไม่ทราบว่า% q จะหนีจากวงเล็บเหลี่ยมด้วย) ปัญหาอื่นที่ฉันเห็นและแก้ไขคือเกี่ยวกับช่องว่าง ด้วยa=(one "two " three)คล้ายกับปัญหาของคีแกน: ไม่เพียง แต่ได้array_contains a "two "รับผลลบที่ผิดพลาดเท่านั้น แต่ยังarray_contains a twoได้ผลบวกที่ผิดด้วย IFSพอที่ง่ายต่อการแก้ไขโดยการตั้งค่า
ghoti

เกี่ยวกับช่องว่าง (whitespaces) ใช่ไหมเพราะมีคำพูดที่หายไป? นอกจากนี้ยังแบ่งกับตัวละคร glob ฉันคิดว่าคุณต้องการนี้แทน: และคุณสามารถคลองeval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") ) local IFS=ยังคงมีปัญหากับฟิลด์ว่างในอาเรย์เนื่องจาก Bash จะปฏิเสธที่จะสร้างคีย์ว่างในอาเรย์แบบเชื่อมโยง วิธี hacky รวดเร็วในการแก้ไขก็คือการย่อหน้าตัวละครหุ่นพูดx: และeval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") ) return $(( 1 - 0${_arr[x$2]} ))
gniourf_gniourf

-1

ฉันใช้เทคนิคนิพจน์ทั่วไปที่แนะนำแล้ว:

values=(foo bar)
requestedValue=bar

requestedValue=${requestedValue##[[:space:]]}
requestedValue=${requestedValue%%[[:space:]]}
[[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"

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

มันสะอาดและสง่างามฉันเชื่อว่าแม้ว่าฉันจะไม่แน่ใจว่ามันมีประสิทธิภาพมากแค่ไหนหากค่าที่คุณสนับสนุนมีขนาดใหญ่เป็นพิเศษ


-1

นี่คือปัญหาของฉัน นี่คือเวอร์ชั่นย่อ:

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}

และรุ่นยาวซึ่งฉันคิดว่าเป็นเรื่องง่ายในสายตา

# With added utility function.
function arrayToLines() {
        local array=${!1}
        printf "%s\n" ${array[@]}
}

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        arrayToLines haystack[@] | grep -q "^$needle$"
}

ตัวอย่าง:

test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False

ฉันไม่ได้ใช้ทุบตีมานานแล้วตอนนี้ฉันมีเวลายากที่จะเข้าใจคำตอบหรือแม้แต่สิ่งที่ฉันเขียนเอง :) ฉันไม่อยากจะเชื่อเลยว่าคำถามนี้ยังคงได้รับการทำกิจกรรมอยู่ตลอดเวลา :)
Paolo Tedesco

เกี่ยวกับtest_arr=("hello" "world" "two words")อะไร
Qwerty

-1

ฉันมีกรณีที่ฉันต้องตรวจสอบว่ามี ID อยู่ในรายการ ID ที่สร้างโดยสคริปต์ / คำสั่งอื่น สำหรับฉันทำงานต่อไปนี้:

# the ID I was looking for
ID=1

# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "

# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)

# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
    echo "not found"
fi
# etc.

คุณสามารถย่อให้สั้นลงได้เช่นกัน:

if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
    echo "not found"
fi

ในกรณีของฉันฉันใช้ jq เพื่อกรอง JSON สำหรับรายการ ID และต้องตรวจสอบภายหลังว่า ID ของฉันอยู่ในรายการนี้หรือไม่และนี่ใช้งานได้ดีที่สุดสำหรับฉัน จะไม่ทำงานสำหรับอาร์เรย์ที่สร้างขึ้นด้วยตัวเองLIST=("1" "2" "4")แต่ใช้กับเอาต์พุตสคริปต์ที่แยกบรรทัดใหม่


ป.ล. : ไม่สามารถแสดงความคิดเห็นคำตอบเพราะฉันค่อนข้างใหม่ ...


-2

รหัสต่อไปนี้จะตรวจสอบว่าค่าที่กำหนดอยู่ในอาร์เรย์และส่งกลับค่าออฟเซ็ตที่เป็นศูนย์หรือไม่:

A=("one" "two" "three four")
VALUE="two"

if [[ "$(declare -p A)" =~ '['([0-9]+)']="'$VALUE'"' ]];then
  echo "Found $VALUE at offset ${BASH_REMATCH[1]}"
else
  echo "Couldn't find $VALUE"
fi

การแข่งขันเสร็จสิ้นในค่าที่สมบูรณ์ดังนั้นการตั้งค่า VALUE = "สาม" จะไม่ตรงกัน


-2

นี่อาจเป็นสิ่งที่ควรตรวจสอบหากคุณไม่ต้องการทำซ้ำ:

#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
 echo "Value was found"
fi
exit

ตัวอย่างดัดแปลงมาจาก: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ ฉันคิดว่ามันค่อนข้างฉลาด

แก้ไข: คุณอาจทำเพียง:

if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi

แต่หลังใช้งานได้เฉพาะถ้าอาร์เรย์มีค่าที่ไม่ซ้ำกัน การค้นหา 1 ใน "143" จะให้ผลบวกเป็นบวก


-2

สายไปหน่อย แต่คุณสามารถใช้สิ่งนี้:

#!/bin/bash
# isPicture.sh

FILE=$1
FNAME=$(basename "$FILE") # Filename, without directory
EXT="${FNAME##*.}" # Extension

FORMATS=(jpeg JPEG jpg JPG png PNG gif GIF svg SVG tiff TIFF)

NOEXT=( ${FORMATS[@]/$EXT} ) # Formats without the extension of the input file

# If it is a valid extension, then it should be removed from ${NOEXT},
#+making the lengths inequal.
if ! [ ${#NOEXT[@]} != ${#FORMATS[@]} ]; then
    echo "The extension '"$EXT"' is not a valid image extension."
    exit
fi

-2

ฉันมากับอันนี้ซึ่งทำงานได้เฉพาะใน zsh แต่ฉันคิดว่าวิธีการทั่วไปดี

arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
    echo "found!"
else
    echo "not found!"
fi

คุณจะออกรูปแบบของคุณจากแต่ละองค์ประกอบเท่านั้นถ้ามันเริ่มต้น${arr[@]/#pattern/}หรือสิ้นสุด${arr[@]/%pattern/}กับมัน การแทนที่ทั้งสองนี้ทำงานในการทุบตี แต่ทั้งสองอย่างในเวลาเดียวกัน${arr[@]/#%pattern/}ใช้ได้เฉพาะใน zsh

ถ้าอาเรย์ที่ถูกแก้ไขนั้นมีค่าเท่ากับของแท้แสดงว่ามันไม่มีองค์ประกอบ

แก้ไข:

อันนี้ใช้ได้ในทุบตี:

 function contains () {
        local arr=(${@:2})
        local el=$1
        local marr=(${arr[@]/#$el/})
        [[ "${#arr[@]}" != "${#marr[@]}" ]]
    }

หลังจากการทดแทนมันจะเปรียบเทียบความยาวของอาร์เรย์ทั้งสอง Obly ถ้า array มีองค์ประกอบการแทนที่จะถูกลบอย่างสมบูรณ์และจำนวนจะแตกต่างกัน

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