Kiểm tra nếu phần tử nằm trong mảng trong bash


17

Có một cách hay để kiểm tra xem một mảng có một phần tử trong bash không (tốt hơn là lặp qua)?

Ngoài ra, có cách nào khác để kiểm tra xem một số hoặc chuỗi có bằng bất kỳ tập hợp các hằng số được xác định trước không?

Câu trả lời:


24

Trong Bash 4, bạn có thể sử dụng mảng kết hợp:

# 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

Để thiết lập mảng ban đầu, bạn cũng có thể thực hiện các bài tập trực tiếp:

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

hoặc theo cách này:

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

Trên thực tế, thử nghiệm [[]] không hoạt động trong trường hợp giá trị trống. Ví dụ: "mảng ['test'] = ''". Trong trường hợp này, khóa 'test' tồn tại và bạn có thể thấy nó được liệt kê với $ {! Mảng [@]}, nhưng "[[$ {mảng ['test']}]]; echo $?" vang 1, không phải 0.
haridsv

1
${array[$test1]}đơn giản nhưng có một vấn đề: nó sẽ không hoạt động nếu bạn sử dụng set -utrong các tập lệnh của mình (được khuyến nghị), vì bạn sẽ nhận được "biến không liên kết".
tokland

@tokland: Ai giới thiệu nó? Tôi chắc chắn không.
Tạm dừng cho đến khi có thông báo mới.

@DennisWilliamson: Ok, một số người khuyên dùng nó, nhưng tôi nghĩ sẽ rất tốt nếu có một giải pháp hoạt động bất kể giá trị của những lá cờ này.
tokland


10

Đó là một câu hỏi cũ, nhưng tôi nghĩ giải pháp đơn giản nhất chưa xuất hiện là gì : test ${array[key]+_}. Thí dụ:

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"

Đầu ra:

a is set
b is set

Để xem làm thế nào công việc này kiểm tra này .


2
Hướng dẫn thông tin khuyến nghị bạn nên sử dụng envđể tránh sự mơ hồ trong bí danh, pross và các chức năng khác có thể đã áp dụng tên "thử nghiệm". Như trên env test ${xs[a]+_} && echo "a is set". Bạn cũng có thể có được chức năng này bằng cách sử dụng dấu ngoặc kép, cùng một mẹo sau đó kiểm tra null:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski

Ngoài ra, bạn có thể sử dụng đơn giản hơn nữa[[ ${xs[b]+set} ]]
Arne L.

5

Có một cách để kiểm tra nếu một phần tử của mảng kết hợp tồn tại (không được đặt), điều này khác với trống:

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

Sau đó sử dụng nó:

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

chỉ là một lưu ý: khai báo -A không hoạt động trên bash 3.2,39 (debian lenny), nhưng nó hoạt động trên bash 4.1.5 (bóp debian)
Paweł Polewicz

Mảng kết hợp đã được giới thiệu trong Bash 4.
Diego F. Durán

1
lưu ý rằng if ! some_check then return 1= some_check. Vì vậy : isNotSet() { [[ ... ]] }. Kiểm tra giải pháp của tôi dưới đây, bạn có thể làm điều đó trong một kiểm tra đơn giản.
tokland

3

Bạn có thể xem nếu một mục có mặt bằng cách chuyển nội dung của mảng sang grep.

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

Bạn cũng có thể lấy chỉ mục của mục nhập bằng grep -n, trả về số dòng của một trận đấu (nhớ trừ 1 để lấy chỉ số dựa trên số 0) Điều này sẽ nhanh chóng hợp lý trừ các mảng rất lớn.

# 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

giải trình:

  • $( ... ) cũng giống như sử dụng backticks để bắt đầu ra của lệnh thành một biến
  • printf xuất ra mydata một phần tử trên mỗi dòng
  • (tất cả các trích dẫn cần thiết, cùng với @thay vì *. điều này tránh việc chia "hello world" thành 2 dòng)
  • greptìm kiếm chuỗi chính xác: ^$khớp đầu và cuối dòng
  • grep -n trả về dòng #, dưới dạng 4: xin chào thế giới
  • grep -m 1 chỉ tìm thấy trận đấu đầu tiên
  • cut chỉ trích xuất số dòng
  • trừ 1 từ số dòng trả về.

Tất nhiên bạn có thể gấp phép trừ vào lệnh. Nhưng sau đó kiểm tra -1 cho thiếu:

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

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) không số học số nguyên

1

Tôi không nghĩ rằng bạn có thể làm điều đó đúng mà không cần lặp trừ khi bạn có dữ liệu rất hạn chế trong mảng.

Đây là một biến thể đơn giản, điều này sẽ nói chính xác rằng "Super User"tồn tại trong mảng. Nhưng nó cũng sẽ nói rằng đó "uper Use"là trong mảng.

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

Vấn đề là không có cách dễ dàng để thêm các neo (mà tôi có thể nghĩ ra) ngoài việc lặp qua mảng. Trừ khi bạn có thể thêm chúng trước khi bạn đặt chúng vào mảng ...


Đó là một giải pháp tốt đẹp khi các hằng số là chữ và số, mặc dù (với grep "\b$FINDME\b"). Có lẽ có thể làm việc với các hằng số không phải là chữ và số không có khoảng trắng, với "(^| )$FINDME(\$| )"(hoặc một cái gì đó tương tự ... Tôi chưa bao giờ có thể tìm hiểu hương vị của regrec grep sử dụng.)
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
Bạn có thể giải thích lý do tại sao bạn đề xuất phương pháp này? OP hỏi nếu có một cách để làm điều đó mà không lặp qua mảng , đó là những gì bạn đang làm in_array. Chúc mừng
bertieb

Chà, ít nhất vòng lặp đó được đặt trong một hàm, có thể đủ tốt cho nhiều trường hợp (với các tập dữ liệu nhỏ) và không yêu cầu bash 4+. Có lẽ ${ARRAY[@]}nên được sử dụng.
Tobias
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.