Kiểm tra xem một mảng Bash có chứa một giá trị không


443

Trong Bash, cách đơn giản nhất để kiểm tra nếu một mảng chứa một giá trị nhất định là gì?

Chỉnh sửa : Với sự giúp đỡ từ các câu trả lời và các ý kiến, sau một số thử nghiệm, tôi đã đưa ra điều này:

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

Tôi không chắc đó có phải là giải pháp tốt nhất không, nhưng có vẻ như nó hoạt động.

Câu trả lời:


457

Cách tiếp cận này có ưu điểm là không cần lặp lại tất cả các yếu tố (ít nhất là không rõ ràng). Nhưng vì array_to_string_internal()trong mảng.c vẫn lặp các phần tử mảng và ghép chúng thành một chuỗi, nên có lẽ nó không hiệu quả hơn các giải pháp lặp được đề xuất, nhưng nó dễ đọc hơn.

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

Lưu ý rằng trong trường hợp giá trị bạn đang tìm kiếm là một trong những từ trong thành phần mảng có khoảng trắng, nó sẽ cho kết quả dương tính giả. Ví dụ

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

Regex sẽ thấy "Jack" nằm trong mảng mặc dù không phải vậy. Vì vậy, bạn sẽ phải thay đổi IFSvà các ký tự phân cách trên regex của bạn nếu bạn vẫn muốn sử dụng giải pháp này, như thế này

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

Điều này sẽ in "sai".

Rõ ràng điều này cũng có thể được sử dụng như một tuyên bố thử nghiệm, cho phép nó được thể hiện dưới dạng một lớp lót

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

1
Tôi đã thêm một khoảng trắng ở đầu của kết quả khớp giá trị regex đầu tiên, để nó chỉ khớp với từ đó, không phải là một cái gì đó kết thúc bằng từ này. Công trình tuyệt vời. Tuy nhiên, tôi không hiểu tại sao bạn sử dụng điều kiện thứ hai, liệu đầu tiên có hoạt động tốt không?
JStrahl

1
@AwQiruiGuo Tôi không chắc là tôi đang theo dõi. Bạn đang nói về mảng với đồng đô la? Nếu vậy, chỉ cần đảm bảo thoát đô la trong giá trị bạn khớp với dấu gạch chéo ngược.
Keegan

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

3
Shellcheck phàn nàn về giải pháp này, SC2199 và SC2076. Tôi không thể sửa các cảnh báo mà không phá vỡ chức năng. Bạn có suy nghĩ gì khác ngoài việc vô hiệu hóa shellcheck cho dòng đó không?
Ali Essam

4
SC2076 rất dễ sửa, chỉ cần xóa dấu ngoặc kép trong if. Tôi không nghĩ có cách nào để tránh SC2199 với phương pháp này. Bạn phải lặp lại một cách rõ ràng qua mảng, như được hiển thị trong một số giải pháp khác hoặc bỏ qua cảnh báo.
Keegan

388

Dưới đây là một chức năng nhỏ để đạt được điều này. Chuỗi tìm kiếm là đối số đầu tiên và phần còn lại là các thành phần mảng:

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

Một lần chạy thử chức năng đó có thể trông giống như:

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

5
Hoạt động độc đáo! Tôi chỉ cần nhớ để vượt qua các mảng như với dấu ngoặc kép : "${array[@]}". Nếu không các yếu tố có chứa không gian sẽ phá vỡ chức năng.
Juve

26
Đẹp. Tôi gọi nó là ElementIn () vì nó kiểm tra xem đối số thứ nhất có được chứa trong đối số thứ hai không. chứaElements () âm thanh như mảng sẽ đi đầu tiên. Đối với những người mới như tôi, một ví dụ về cách sử dụng hàm không ghi vào thiết bị xuất chuẩn trong câu lệnh "nếu" sẽ giúp ích: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Cảm ơn sự giúp đỡ của bạn!
GlenPeterson

5
@Bluz cấu trúc && là toán tử boolean AND. Việc sử dụng các toán tử boolean tạo ra một câu lệnh boolean. Logic logic cho biết toàn bộ câu lệnh chỉ có thể đúng nếu cả hai câu lệnh trước và sau khi && đánh giá là đúng. Điều này được sử dụng như một lối tắt bắt nguồn và nếu block. Thử nghiệm được đánh giá và nếu sai, không cần đánh giá lợi nhuận vì nó không liên quan đến toàn bộ câu lệnh một khi thử nghiệm thất bại và do đó không được chạy. Nếu thử nghiệm thành công thì thành công của câu lệnh boolean DOES yêu cầu kết quả trả về phải được xác định để mã được chạy.
peteches

4
@James theo quy ước mã thành công trong bash là "0" và lỗi là tất cả mọi thứ> = 1. Đây là lý do tại sao nó trả về 0 khi thành công. :)
tftd

11
@Stelios shiftchuyển danh sách đối số bằng 1 sang trái (bỏ đối số đầu tiên) và forkhông inlặp lại một cách ngầm định trong danh sách đối số.
Christian

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

69
Lưu ý rằng điều này không lặp lại trên từng phần tử trong mảng một cách riêng biệt ... thay vào đó, nó chỉ đơn giản nối các mảng và khớp với "hai" dưới dạng một chuỗi con. Điều này có thể gây ra hành vi không mong muốn nếu một người đang kiểm tra xem từ "hai" chính xác có phải là một thành phần trong mảng hay không.
MartyMacGyver

Tôi nghĩ rằng điều này sẽ làm việc cho tôi khi so sánh các loại tệp nhưng thấy rằng khi bộ đếm tăng lên, nó đang đếm quá nhiều giá trị ... boo!
Mike Q

17
Sai lầm! Lý do: case "${myarray[@]}" in *"t"*) echo "found" ;; esacđầu ra:found
Sergej Jevsejev

@MartyMacGyver, bạn có thể vui lòng xem phần bổ sung của tôi cho câu trả lời này stackoverflow.com/a/52414872/1619950
Aleksandr Podkutin

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

Đối với chuỗi:

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

Điều đó nói rằng, bạn có thể sử dụng một vòng lặp được lập chỉ mục và tránh bị giết khi một phần tử mảng chứa IFS: for ((i = 0; i <$ {# mảng [@]}; i ++))
mkb

@Matt: Bạn phải cẩn thận ${#}khi sử dụng vì Bash hỗ trợ các mảng thưa thớt.
Tạm dừng cho đến khi có thông báo mới.

@Paolo, nếu mảng của bạn chứa khoảng trắng thì chỉ cần so sánh nó dưới dạng chuỗi. một không gian là một chuỗi là tốt.
Scott

@Paolo: Bạn có thể biến nó thành một hàm, nhưng các mảng không thể được chuyển qua làm đối số nên bạn sẽ phải coi nó là toàn cục.
Tạm dừng cho đến khi có thông báo mới.

Dennis nói đúng. Từ hướng dẫn tham khảo bash: "Nếu từ được trích dẫn kép, ... $ {name [@]} sẽ mở rộng từng thành phần của tên thành một từ riêng biệt"
mkb

37

Giải pháp một dòng

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

Giải trình

Câu printflệnh in từng phần tử của mảng trên một dòng riêng biệt.

Câu greplệnh sử dụng các ký tự đặc biệt ^$để tìm một dòng chứa chính xác mẫu được đưa ra là mypattern(không hơn, không kém).


Sử dụng

Để đưa điều này vào một if ... thentuyên bố:

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

Tôi đã thêm một -qcờ vào grepbiểu thức để nó không in trùng khớp; nó sẽ chỉ coi sự tồn tại của một trận đấu là "đúng".


Giải pháp tốt đẹp! Trên GNU grep, cũng có "--line-regapi" có thể thay thế "-P" và ^ và $ trong mẫu: printf '% s \ n' $ {myarray [@]} | grep -q --line-regapi 'mypotype'
promo8

19

Nếu bạn cần hiệu suất, bạn không muốn lặp lại toàn bộ mảng của mình mỗi khi bạn tìm kiếm.

Trong trường hợp này, bạn có thể tạo một mảng kết hợp (bảng băm hoặc từ điển) đại diện cho một chỉ mục của mảng đó. Tức là nó ánh xạ từng phần tử mảng vào chỉ mục của nó trong mảng:

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
}

Sau đó, bạn có thể sử dụng nó như thế này:

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

Và kiểm tra thành viên như vậy:

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

Hoặc cũng:

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

Lưu ý rằng giải pháp này thực hiện đúng ngay cả khi có khoảng trắng trong giá trị được kiểm tra hoặc trong các giá trị mảng.

Là một phần thưởng, bạn cũng có được chỉ số của giá trị trong mảng với:

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

+1 cho ý tưởng rằng bạn nên sử dụng một mảng kết hợp. Tôi nghĩ rằng mã cho make_indexmột chút giả định hơn do sự thiếu quyết đoán; bạn có thể đã sử dụng một tên mảng cố định với mã đơn giản hơn nhiều.
musiphil

17

Tôi thường chỉ sử dụng:

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

giá trị khác không cho thấy một trận đấu đã được tìm thấy.


Đúng, đây chắc chắn là giải pháp dễ nhất - nên được đánh dấu câu trả lời theo ý kiến ​​của tôi. Ít nhất có upvote của tôi! [:
ToVine

2
Điều đó sẽ không làm việc cho kim tương tự. Ví dụ:haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan

1
Rất đúng. tham gia với một dấu phân cách không có trong bất kỳ phần tử nào và thêm nó vào kim sẽ giúp ích cho điều đó. Có lẽ một cái gì đó như ... (chưa được kiểm tra)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti

2
Sử dụng grep -x sẽ tránh được kết quả dương tính giả: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher

Có lẽ chỉ đơn giản inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)là -x khiến grep thử và khớp toàn bộ chuỗi đầu vào
MI Wright

17

Một lớp lót khác không có chức năng:

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

Cảm ơn @Qwerty cho những người đứng đầu về không gian!

chức năng tương ứng:

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

thí dụ:

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

1
Tại sao chúng ta cần một subshell ở đây?
codeforester 17/03/18

1
@codeforester cái này đã cũ ... nhưng vì nó đã được viết nên bạn cần nó để thoát khỏi nó, đó là những gì exit 0nó làm (dừng càng sớm càng tốt nếu tìm thấy).
estani

Kết thúc của một lớp lót nên || echo not foundthay vì || not foundhoặc shell sẽ cố gắng thực thi một lệnh bằng tên không có đối số được tìm thấy nếu giá trị được yêu cầu không nằm trong mảng.
zoke

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

Bây giờ xử lý các mảng trống chính xác.


Điều này khác với câu trả lời của @ patrik như thế nào? Sự khác biệt duy nhất tôi thấy là "$e" = "$1"(thay vì "$e" == "$1") trông giống như một lỗi.
CivilFan

1
Không phải vậy. @ patrik's đã hợp nhất nhận xét của tôi trong câu trả lời ban đầu của anh ấy sau đó (bản vá số 4). Lưu ý: "e" == "$1"là cú pháp rõ ràng hơn.
Yann

@CivFan Ở dạng hiện tại, nó ngắn hơn câu trả lời của patrik, vì $ {@: 2} thanh lịch và tự ghi lại $ 1. Tôi sẽ thêm rằng trích dẫn là không cần thiết trong [[]].
Hontvári Levente

9

Đây là một đóng góp nhỏ:

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

Lưu ý: cách này không phân biệt trường hợp "hai từ" nhưng điều này không bắt buộc trong câu hỏi.


Điều này đã giúp tôi rất nhiều. Cảm ơn!
Ed Manet

Câu hỏi không nói rõ ràng là bạn phải đưa ra câu trả lời đúng nhưng tôi nghĩ đó là ẩn ý trong câu hỏi ... Mảng không chứa giá trị "hai".
tetsujin

Ở trên sẽ báo cáo một trận đấu cho 'rd'.
Noel Yap

6

Nếu bạn muốn thực hiện một thử nghiệm nhanh và bẩn để xem liệu nó có đáng lặp lại trên toàn bộ mảng để có một kết quả khớp chính xác hay không, Bash có thể coi các mảng như vô hướng. Kiểm tra một trận đấu trong vô hướng, nếu không thì bỏ qua vòng lặp sẽ tiết kiệm thời gian. Rõ ràng bạn có thể nhận được dương tính giả.

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

Điều này sẽ xuất ra "Kiểm tra" và "Kết hợp". Với array=(word "two words" something)nó sẽ chỉ xuất "Kiểm tra". Với array=(word "two widgets" something)sẽ không có đầu ra.


Tại sao không chỉ thay thế wordsbằng một regex ^words$chỉ khớp với toàn bộ chuỗi, loại bỏ hoàn toàn nhu cầu kiểm tra từng mục riêng lẻ?
Dejay Clayton

@DejayClayton: Bởi vì pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]sẽ không bao giờ khớp vì nó kiểm tra toàn bộ mảng cùng một lúc như thể nó là vô hướng. Việc kiểm tra cá nhân trong câu trả lời của tôi chỉ được thực hiện nếu có lý do để tiến hành dựa trên trận đấu thô.
Tạm dừng cho đến khi có thông báo mới.

Ah, tôi thấy những gì bạn đang cố gắng làm. Tôi đã đề xuất một câu trả lời khác nhau mà hiệu quả và an toàn hơn.
Dejay Clayton

6

Điều này làm việc cho tôi:

# 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
}

Cuộc gọi ví dụ:

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

Nếu bạn thích, bạn có thể sử dụng các tùy chọn dài tương đương:

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

1
Điều này không hoạt động với BSD-grep trên Mac, vì không có - dữ liệu đầy đủ. :(
Sẽ

4

Mượn từ câu trả lời của Dennis Williamson , giải pháp sau đây kết hợp các mảng, trích dẫn an toàn vỏ và các biểu thức chính quy để tránh sự cần thiết: lặp qua các vòng lặp; sử dụng đường ống hoặc các quy trình phụ khác; hoặc sử dụng các tiện ích không 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

Đoạn mã trên hoạt động bằng cách sử dụng các biểu thức chính quy Bash để khớp với phiên bản được xâu chuỗi của nội dung mảng. Có sáu bước quan trọng để đảm bảo rằng kết hợp biểu thức chính quy không thể bị đánh lừa bởi các kết hợp giá trị thông minh trong mảng:

  1. Xây dựng chuỗi so sánh bằng cách sử dụng printftrích dẫn shell dựng sẵn của Bash , %q. Trích dẫn Shell sẽ đảm bảo rằng các ký tự đặc biệt trở nên "an toàn với vỏ" bằng cách thoát khỏi dấu gạch chéo ngược \.
  2. Chọn một ký tự đặc biệt để phục vụ như là một dấu phân cách giá trị. Dấu phân cách ĐÃ là một trong những ký tự đặc biệt sẽ được thoát khi sử dụng %q; đó là cách duy nhất để đảm bảo rằng các giá trị trong mảng không thể được xây dựng theo những cách thông minh để đánh lừa biểu thức khớp thông thường. Tôi chọn dấu phẩy ,vì ký tự đó là an toàn nhất khi bị đánh cắp hoặc sử dụng sai theo cách không mong muốn.
  3. Kết hợp tất cả các thành phần mảng thành một chuỗi, sử dụng hai thể hiện của ký tự đặc biệt để đóng vai trò là dấu phân cách. Sử dụng dấu phẩy làm ví dụ, tôi đã sử dụng ,,%qlàm đối số printf. Điều này rất quan trọng vì hai trường hợp của ký tự đặc biệt chỉ có thể xuất hiện cạnh nhau khi chúng xuất hiện dưới dạng dấu phân cách; tất cả các trường hợp khác của nhân vật đặc biệt sẽ được thoát.
  4. Nối hai trường hợp dấu của dấu phân cách vào chuỗi, để cho phép khớp với phần tử cuối cùng của mảng. Như vậy, thay vì so sánh với ${array_str}, so sánh với ${array_str},,.
  5. Nếu chuỗi mục tiêu bạn đang tìm kiếm được cung cấp bởi một biến người dùng, bạn phải thoát tất cả các trường hợp của ký tự đặc biệt bằng dấu gạch chéo ngược. Mặt khác, kết hợp biểu thức chính quy trở nên dễ bị lừa bởi các phần tử mảng được tạo khéo léo.
  6. Thực hiện một biểu thức chính quy Bash khớp với chuỗi.

Rất thông minh. Tôi có thể thấy rằng hầu hết các vấn đề tiềm ẩn đều được ngăn chặn, nhưng tôi muốn kiểm tra xem liệu có bất kỳ trường hợp góc nào không. Ngoài ra, tôi muốn xem một ví dụ về xử lý điểm 5. Một cái gì đó giống như printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]có lẽ.
Tạm dừng cho đến khi có thông báo mới.

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

3

Một bổ sung nhỏ cho câu trả lời của @ ghostdog74 về việc sử dụng caselogic để kiểm tra mảng đó có chứa giá trị cụ thể không:

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

Hoặc với extglobtùy chọn được bật, bạn có thể làm như thế này:

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

Ngoài ra chúng ta có thể làm điều đó với iftuyên bố:

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

2

được :

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

sau đó kiểm tra đơn giản về:

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

Ở đâu

c is element separator
p is regex pattern

(Lý do chỉ định p riêng, thay vì sử dụng biểu thức trực tiếp bên trong [[]] là để duy trì khả năng tương thích cho bash 4)


thích cách bạn sử dụng từ "đơn giản" ở đây ...
Christian

2

Kết hợp một vài trong số các ý tưởng được trình bày ở đây, bạn có thể tạo ra một sự thanh lịch nếu thống kê mà không có vòng lặp nào khớp từ chính xác .

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

Điều này sẽ không kích hoạt trên wordhoặc val, chỉ toàn bộ từ phù hợp. Nó sẽ phá vỡ nếu mỗi giá trị mảng chứa nhiều từ.


1

Tôi thường viết các loại tiện ích này để hoạt động theo tên của biến, thay vì giá trị biến, chủ yếu vì bash không thể chuyển các biến bằng tham chiếu.

Đây là phiên bản hoạt động với tên của mảng:

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
}

Với điều này, ví dụ câu hỏi trở thành:

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

Vân vân.


Ai đó có thể đăng một ví dụ về điều này được sử dụng trong một if, đặc biệt là cách bạn vượt qua trong mảng. Tôi đang cố kiểm tra xem liệu một đối số cho tập lệnh đã được thông qua hay chưa bằng cách coi các thông số là một mảng, nhưng nó không muốn hoạt động. params = ("$ @") check = mảng_contains $ {params} 'SKIPDIRCHECK' if [[$ {check} == 1]]; sau đó .... Nhưng khi chạy tập lệnh với 'asas' làm đối số, nó cứ nói asas: lệnh không tìm thấy. : /
Steve Childs

1

Sử dụng grepprintf

Định dạng từng thành viên mảng trên một dòng mới, sau đó greplà các dòng.

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
thí dụ:
$ array=("word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true

Lưu ý rằng điều này không có vấn đề với dấu phân cách và không gian.


1

Kiểm tra một dòng không có 'grep' và vòng lặp

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

Cách tiếp cận này không sử dụng các tiện ích bên ngoài như grepvòng lặp.

Điều gì xảy ra ở đây, là:

  • chúng tôi sử dụng một trình so khớp chuỗi ký tự đại diện để tìm mục của chúng tôi trong mảng được nối thành một chuỗi;
  • chúng tôi cắt bỏ các kết quả dương tính giả có thể bằng cách đặt mục tìm kiếm của chúng tôi giữa một cặp dấu phân cách;
  • chúng tôi sử dụng một ký tự không thể in là dấu phân cách, để ở bên an toàn;
  • chúng tôi cũng đạt được dấu phân cách của chúng tôi được sử dụng để nối mảng bằng cách thay thế tạm thời IFSgiá trị biến;
  • chúng tôi IFStạm thời thay thế giá trị này bằng cách đánh giá biểu thức điều kiện của chúng tôi trong một vỏ con (bên trong một cặp dấu ngoặc đơn)

Loại bỏ dlm. Sử dụng IFS trực tiếp.
Robin A. Meade

Đây là câu trả lời tốt nhất. Tôi thích nó rất nhiều, tôi đã viết một chức năng bằng cách sử dụng kỹ thuật này .
Robin A. Meade

1

Sử dụng mở rộng tham số:

$ {tham số: + word} Nếu tham số là null hoặc unset, không có gì được thay thế, nếu không thì việc mở rộng từ được thay thế.

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]:+_}hoạt động tuyệt vời cho các mảng kết hợp, nhưng không phải cho các mảng được lập chỉ mục thông thường. Câu hỏi là về việc tìm kiếm một giá trị trong một aray, không kiểm tra xem khóa của một mảng kết hợp có tồn tại hay không.
Eric

0

Sau khi trả lời, tôi đọc một câu trả lời khác mà tôi đặc biệt thích, nhưng nó thật thiếu sót và bị đánh giá thấp. Tôi đã lấy cảm hứng và đây là hai cách tiếp cận mới mà tôi thấy khả thi.

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

sử dụng grepprintf:

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

sử dụng for:

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

Để có kết quả không phải là thêm || <run_your_if_notfound_command_here>


0

Đây là của tôi về điều này.

Tôi thà không sử dụng bash cho vòng lặp nếu tôi có thể tránh nó, vì điều đó cần có thời gian để chạy. Nếu một cái gì đó phải lặp, hãy để nó là một cái gì đó được viết bằng ngôn ngữ cấp thấp hơn so với tập lệnh shell.

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

Điều này hoạt động bằng cách tạo ra một mảng kết hợp tạm thời _arr, có chỉ số được lấy từ các giá trị của mảng đầu vào. (Lưu ý rằng mảng kết hợp có sẵn trong bash 4 trở lên, vì vậy chức năng này sẽ không hoạt động trong các phiên bản trước của bash.) Chúng tôi đặt $IFSđể tránh chia tách từ trên khoảng trắng.

Hàm này không chứa các vòng lặp rõ ràng, mặc dù các bước bash bên trong thông qua mảng đầu vào để điền vào printf. Định dạng printf sử dụng %qđể đảm bảo rằng dữ liệu đầu vào được thoát để chúng có thể được sử dụng một cách an toàn làm khóa mảng.

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

Lưu ý rằng mọi thứ mà chức năng này sử dụng là tích hợp để bash, do đó không có đường ống bên ngoài nào kéo bạn xuống, ngay cả trong việc mở rộng lệnh.

Và nếu bạn không thích sử dụng eval... tốt, bạn có thể sử dụng một cách tiếp cận khác. :-)


Nếu mảng chứa dấu ngoặc vuông thì sao?
gniourf_gniourf

@gniourf_gniourf - có vẻ ổn nếu dấu ngoặc vuông được cân bằng, nhưng tôi có thể thấy đó là một vấn đề nếu mảng của bạn bao gồm các giá trị với dấu ngoặc vuông không cân bằng. Trong trường hợp đó, tôi sẽ gọi evalhướng dẫn ở cuối câu trả lời. :)
ghoti

Không phải là tôi không thích eval(tôi không có gì chống lại nó, không giống như hầu hết những người khóc evallà xấu xa, chủ yếu là không hiểu điều gì xấu về nó). Chỉ cần lệnh của bạn bị hỏng. Có lẽ %qthay vì %ssẽ tốt hơn.
gniourf_gniourf

1
@gniourf_gniourf: Tôi chỉ có nghĩa là bit "cách tiếp cận khác" (và evalrõ ràng tôi hoàn toàn với bạn ), nhưng bạn hoàn toàn đúng, %qdường như giúp đỡ, mà không phá vỡ bất cứ điều gì khác mà tôi có thể thấy. (Tôi không nhận ra rằng% q cũng sẽ thoát khỏi dấu ngoặc vuông.) Một vấn đề khác tôi thấy và đã sửa là về khoảng trắng. Với a=(one "two " three), tương tự như vấn đề của Keegan: không chỉ array_contains a "two "có kết quả âm tính giả mà còn array_contains a twocó kết quả dương tính giả. Đủ dễ dàng để sửa chữa bằng cách thiết lập IFS.
ghoti

Về khoảng trắng, không phải vì thiếu dấu ngoặc kép sao? nó cũng phá vỡ với các nhân vật toàn cầu. Tôi nghĩ rằng bạn muốn điều này thay vào đó: eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )và bạn có thể bỏ qua local IFS=. Vẫn còn một vấn đề với các trường trống trong mảng, vì Bash sẽ từ chối tạo một khóa trống trong một mảng kết hợp. Một cách nhanh chóng để khắc phục nó là chuẩn bị một nhân vật giả, nói x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf

-1

Phiên bản của tôi về kỹ thuật biểu thức chính quy đã được đề xuất:

values=(foo bar)
requestedValue=bar

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

Điều xảy ra ở đây là bạn đang mở rộng toàn bộ mảng các giá trị được hỗ trợ thành các từ và thêm vào một chuỗi cụ thể, "X-" trong trường hợp này, cho từng chuỗi và thực hiện tương tự với giá trị được yêu cầu. Nếu cái này thực sự được chứa trong mảng, thì chuỗi kết quả sẽ khớp với một trong số các mã thông báo kết quả, hoặc không có gì ngược lại. Trong trường hợp sau, | | toán tử kích hoạt và bạn biết bạn đang xử lý một giá trị không được hỗ trợ. Trước đó, tất cả các giá trị được yêu cầu sẽ bị tước bỏ tất cả các khoảng trắng hàng đầu và dấu kiểm thông qua thao tác chuỗi vỏ tiêu chuẩn.

Nó sạch sẽ và thanh lịch, tôi tin, mặc dù tôi không chắc chắn về hiệu suất của nó nếu mảng giá trị được hỗ trợ của bạn đặc biệt lớn.


-1

Đây là nhận của tôi về vấn đề này. Đây là phiên bản ngắn:

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

Và phiên bản dài, mà tôi nghĩ là dễ nhìn hơn nhiều.

# 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$"
}

Ví dụ:

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

Tôi đã không sử dụng bash trong một thời gian dài đến nỗi tôi có một thời gian khó hiểu câu trả lời, hoặc thậm chí những gì tôi đã tự viết :) Tôi không thể tin rằng câu hỏi này vẫn còn hoạt động sau tất cả thời gian này :)
Paolo Tedesco

Thế còn test_arr=("hello" "world" "two words")?
Qwerty

-1

Tôi đã có trường hợp tôi phải kiểm tra xem ID có được chứa trong danh sách ID được tạo bởi tập lệnh / lệnh khác không. Đối với tôi làm việc như sau:

# 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.

Bạn cũng có thể rút ngắn / nén nó như thế này:

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

Trong trường hợp của tôi, tôi đã chạy jq để lọc một số JSON cho danh sách ID và sau đó phải kiểm tra xem ID của tôi có trong danh sách này không và điều này hoạt động tốt nhất với tôi. Nó sẽ không hoạt động cho các mảng được tạo thủ công của loại LIST=("1" "2" "4")nhưng với đầu ra tập lệnh được phân tách dòng mới.


PS.: Không thể bình luận một câu trả lời vì tôi còn khá mới ...


-2

Đoạn mã sau kiểm tra xem một giá trị đã cho có trong mảng không và trả về giá trị bù dựa trên zero của nó:

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

Trận đấu được thực hiện trên các giá trị hoàn chỉnh, do đó, đặt VALUE = "ba" sẽ không khớp.


-2

Điều này có thể đáng để điều tra nếu bạn không muốn lặp lại:

#!/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

Đoạn trích được chuyển thể từ: http : //www.thegeek ware.com/2010/06/bash-array-tutorial/ Tôi nghĩ rằng nó khá thông minh.

EDIT: Bạn có thể chỉ cần làm:

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

Nhưng cái sau chỉ hoạt động nếu mảng chứa các giá trị duy nhất. Tìm kiếm 1 trong "143" sẽ cho kết quả sai, methinks.


-2

Hơi muộn, nhưng bạn có thể sử dụng điều này:

#!/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

Tôi đã đưa ra cái này, hóa ra chỉ hoạt động trong zsh, nhưng tôi nghĩ cách tiếp cận chung là tốt.

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

Bạn lấy ra mẫu của bạn từ mỗi phần tử chỉ khi nó bắt đầu ${arr[@]/#pattern/}hoặc kết thúc ${arr[@]/%pattern/}với nó. Hai sự thay thế này hoạt động trong bash, nhưng cả hai cùng một lúc${arr[@]/#%pattern/} chỉ hoạt động trong zsh.

Nếu mảng được sửa đổi bằng với bản gốc, thì nó không chứa phần tử.

Biên tập:

Cái này hoạt động trong bash:

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

Sau khi thay thế, nó so sánh độ dài của cả hai mảng. Obly nếu mảng chứa phần tử thay thế sẽ xóa hoàn toàn nó, và số lượng sẽ khác nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.