Làm cách nào để kiểm tra xem một biến có tồn tại trong danh sách trong BASH không


137

Tôi đang cố gắng viết một tập lệnh trong bash để kiểm tra tính hợp lệ của đầu vào của người dùng.
Tôi muốn khớp đầu vào (biến nói x) với danh sách các giá trị hợp lệ.

những gì tôi đã đưa ra tại thời điểm này là:

for item in $list
do
    if [ "$x" == "$item" ]; then
        echo "In the list"
        exit
    fi
done

Câu hỏi của tôi là nếu có một cách đơn giản hơn để làm điều này,
một cái gì đó giống như list.contains(x)đối với hầu hết các ngôn ngữ lập trình.

Ngoài ra:
Danh sách nói là:

list="11 22 33"

mã của tôi sẽ chỉ lặp lại thông báo cho các giá trị đó vì listđược coi là một mảng chứ không phải là một chuỗi, tất cả các thao tác chuỗi sẽ xác thực 1trong khi tôi muốn nó thất bại.

Câu trả lời:


142
[[ $list =~ (^|[[:space:]])$x($|[[:space:]]) ]] && echo 'yes' || echo 'no'

hoặc tạo một chức năng:

contains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && exit(0) || exit(1)
}

để dùng nó:

contains aList anItem
echo $? # 0: match, 1: failed

37
nên là[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Matvey Aksenov

12
Có thể cho kết quả dương tính giả nếu đầu vào của người dùng chứa các ký tự đặc biệt biểu thức chính quy, ví dụx=.
glenn jackman

4
Điều này sẽ không có tích cực / tiêu cực sai : contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin

6
một giải pháp ngắn gọn hơn : [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. đúng là giả sử không gian là dải phân cách và $xkhông chứa khoảng
trắng

2
Tôi nghĩ tốt hơn là sử dụng hàm "is in" isIn()để bạn có thể viết mục đầu tiên trong các tham số. Ngoài ra, bạn có thể echosử dụng một cái gì đó thay vì sử dụng exitnhư thế này: [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0Vì vậy, bạn có thể sử dụng chức năng theo cách này:result=$(isIn "-t" "-o -t 45") && echo $result
hayj

34

Matvey đúng, nhưng bạn nên trích dẫn $ x và xem xét bất kỳ loại "khoảng trắng" nào (ví dụ: dòng mới) với

[[ $list =~ (^|[[:space:]])"$x"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 

vì vậy, tức là

# list_include_item "10 11 12" "2"
function list_include_item {
  local list="$1"
  local item="$2"
  if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then
    # yes, list include item
    result=0
  else
    result=1
  fi
  return $result
}

kết thúc sau đó

`list_include_item "10 11 12" "12"`  && echo "yes" || echo "no"

hoặc là

if `list_include_item "10 11 12" "1"` ; then
  echo "yes"
else 
  echo "no"
fi

Lưu ý rằng bạn phải sử dụng ""trong trường hợp biến:

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"

3
Giải pháp này hoạt động ngay cả khi $itemcó chứa các ký tự đặc biệt, như .(nhưng $listbiến có lẽ cần được trích dẫn bên trong thử nghiệm). Và chức năng có thể được định nghĩa thậm chí đơn giản hơn : contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin

không hoạt động trong các trường hợp như: list="aa bb xx cc ff"x="aa bb"
creativeChips

xin chào. Tôi đang cố gắng học các nguyên tắc cơ bản của bashscript và tôi muốn biết làm thế nào bạn có thể phân tích chuỗi danh sách, vào danh sách và bạn thực hiện kiểm tra tồn tại trong danh sách đó. Tôi có thể hiểu bạn phân chia bằng cách sử dụng không gian nhưng tôi không thể hiểu đầy đủ biểu thức. Bạn có thể cung cấp cho tôi các từ khóa để Google các nguyên tắc cơ bản của biểu thức này : $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Hoặc, nếu bạn có thời gian, tôi sẽ rất vui khi nghe lời giải thích của bạn cho biểu hiện này. Lưu ý: Tôi đoán đó là biểu thức regex (bắt đầu bằng ^ vv) nhưng tôi không biết = ~ nghĩa là gì. Vì vậy, tôi thích một lời giải thích tổng thể: P
Ali Yılmaz

28

Giải pháp đơn giản nhất của IMHO là thêm vào và nối thêm chuỗi gốc với khoảng trắng và kiểm tra lại biểu thức chính quy với [[ ]]

haystack='foo bar'
needle='bar'

if [[ " $haystack " =~ .*\ $needle\ .* ]]; then
    ...
fi

điều này sẽ không dương tính giả trên các giá trị với các giá trị chứa kim dưới dạng chuỗi con, ví dụ như với một đống cỏ khô foo barbaz.

(Khái niệm này bị đánh cắp một cách đáng xấu hổ dưới dạng hasClass()-Method của JQuery )


4
nếu dấu phân cách không phải là không gian, thì giải pháp rõ ràng hơn. nó cũng có thể được thực hiện mà không cần regex: haystack="foo:bar"[[ ":$haystack:" = *:$needle:* ]]
phiphi

25

làm thế nào về

echo $list | grep -w $x

bạn có thể kiểm tra đầu ra hoặc $?của dòng trên để đưa ra quyết định.

grep -w kiểm tra trên toàn bộ mẫu từ.


1
điều này sẽ không xác thực "val" nếu "giá trị" là hợp lệ (tức là trong danh sách) vì nó là chuỗi con của nó
Ofir Farchy

Vâng, nó sẽ, như là câu trả lời được chấp nhận. @glennjackman đưa ra giải pháp làm việc.
f.ardelian

3
bạn có thể sử dụng grep -q để khiến grep im lặng
Amir

điều này cũng sẽ chấp nhận kết quả khớp một phần, có thể không phải là điều người dùng muốn
carnicer

3
@carnicer sau đó chỉ cần sử dụng grep -w $ x để có kết quả khớp chính xác
user1297406

18

Bạn cũng có thể sử dụng (* ký tự đại diện) bên ngoài câu lệnh tình huống, nếu bạn sử dụng dấu ngoặc kép:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi

3
Đơn giản và thanh lịch, +1
João Pereira

14
Điều này sẽ cho kết quả dương tính giả nếu "My" là một chuỗi con của một chuỗi khác, ví dụ: chuỗi = 'Chuỗi MyCommand'.
Luke Lee

Điều đáng chú ý là các ký tự đại diện ( *My*) phải ở bên phải của bài kiểm tra.
Jacob Vanus

8

Nếu danh sách các giá trị của bạn được mã hóa cứng trong tập lệnh, việc kiểm tra bằng cách sử dụng khá đơn giản case. Đây là một ví dụ ngắn, mà bạn có thể thích ứng với yêu cầu của mình:

for item in $list
do
    case "$x" in
      item1|item2)
        echo "In the list"
        ;;
      not_an_item)
        echo "Error" >&2
        exit 1
        ;;
    esac
done

Nếu danh sách là một biến mảng trong thời gian chạy, một trong những câu trả lời khác có lẽ phù hợp hơn.


7

Nếu danh sách được cố định trong tập lệnh, tôi thích nhất sau đây:

validate() {
    grep -F -q -x "$1" <<EOF
item 1
item 2
item 3
EOF
}

Sau đó sử dụng validate "$x"để kiểm tra nếu $xđược phép.

Nếu bạn muốn có một lớp lót và không quan tâm đến khoảng trắng trong tên vật phẩm, bạn có thể sử dụng cái này (chú ý -wthay vì -x):

validate() { echo "11 22 33" | grep -F -q -w "$1"; }

Ghi chú:

  • Đây là shtuân thủ POSIX .
  • validatekhông không chấp nhận chuỗi con (loại bỏ các -xtùy chọn để grep nếu bạn muốn điều đó).
  • validatediễn giải đối số của nó dưới dạng một chuỗi cố định, không phải là biểu thức chính quy (loại bỏ -Ftùy chọn thành grep nếu bạn muốn điều đó).

Mã mẫu để thực hiện chức năng:

for x in "item 1" "item2" "item 3" "3" "*"; do
    echo -n "'$x' is "
    validate "$x" && echo "valid" || echo "invalid"
done

6

Xem xét khai thác các khóa của mảng kết hợp . Tôi sẽ cho rằng điều này vượt trội hơn cả so khớp regex / mẫu và lặp, mặc dù tôi chưa mô tả nó.

declare -A list=( [one]=1 [two]=two [three]='any non-empty value' )
for value in one two three four
do
    echo -n "$value is "
    # a missing key expands to the null string, 
    # and we've set each interesting key to a non-empty value
    [[ -z "${list[$value]}" ]] && echo -n '*not* '
    echo "a member of ( ${!list[*]} )"
done

Đầu ra:

one is a member of ( one two three )
two is a member of ( one two three )
three is a member of ( one two three )
four is *not* a member of ( one two three )

2
... Và bạn có thể đơn giản hóa việc thay thế đó (và không phụ thuộc vào echo -n) với việc sử dụng các tham số sáng tạo : do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight

Có cách nào dễ dàng để tạo một mảng như vậy không nếu tôi chỉ có một danh sách (dài) như `list =" one hai ba xyz ... "?
Ott Toomet

5

Tôi thấy việc sử dụng biểu mẫu echo $LIST | xargs -n1 echo | grep $VALUEnhư minh họa dưới đây dễ dàng hơn :

LIST="ITEM1 ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE`$\" ]; then
    ...
fi

Điều này hoạt động cho một danh sách được phân tách bằng dấu cách, nhưng bạn có thể điều chỉnh nó với bất kỳ dấu phân cách nào khác (như :) bằng cách thực hiện như sau:

LIST="ITEM1:ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | sed 's|:|\\n|g' | grep -e \"^$VALUE`$\"`" ]; then
   ...
fi

Lưu ý rằng các "yêu cầu bắt buộc để thử nghiệm hoạt động.


Điều này sẽ khiến LIST="SOMEITEM1 ITEM2"trả về đúng sự thật mặc dù ITEM1không có trong đó
Ofir Farchy

Bắt tốt, tôi đã cập nhật ví dụ với grep -e để loại trừ khớp một phần.
Sébastien Pierre

Tôi tin rằng có thêm một `ở cuối câu lệnh" nếu ". Hình thức đúng là: [-n "` echo $ LIST | xargs -n1 echo | grep -e \ "^ $ VALUE $ \"]
Elad Tabak

3

Tôi nghĩ tôi sẽ thêm giải pháp của mình vào danh sách.

# Checks if element "$1" is in array "$2"
# @NOTE:
#   Be sure that array is passed in the form:
#       "${ARR[@]}"
elementIn () {
    # shopt -s nocasematch # Can be useful to disable case-matching
    local e
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
    return 1
}

# Usage:
list=(11 22 33)
item=22

if elementIn "$item" "${list[@]}"; then
    echo TRUE;
else
    echo FALSE
fi
# TRUE

item=44
elementIn $item "${list[@]}" && echo TRUE || echo FALSE
# FALSE

1

Ví dụ

$ in_list super test me out
NO

$ in_list "super dude" test me out
NO

$ in_list "super dude" test me "super dude"
YES

# How to use in another script
if [ $(in_list $1 OPTION1 OPTION2) == "NO" ]
then
  echo "UNKNOWN type for param 1: Should be OPTION1 or OPTION2"
  exit;
fi

trong danh sách

function show_help()
{
  IT=$(CAT <<EOF

  usage: SEARCH_FOR {ITEM1} {ITEM2} {ITEM3} ...

  e.g. 

  a b c d                    -> NO
  a b a d                    -> YES
  "test me" how "test me"    -> YES

  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

if [ "$#" -eq 0 ]; then
  show_help
fi

SEARCH_FOR=$1
shift;

for ITEM in "$@"
do
  if [ "$SEARCH_FOR" == "$ITEM" ]
  then
    echo "YES"
    exit;
  fi
done

echo "NO"

1

Giả sử biến TARGET chỉ có thể là 'nhị thức' hoặc 'hồi quy', thì sau đây sẽ làm:

# Check for modeling types known to this script
if [ $( echo "${TARGET}" | egrep -c "^(binomial|regression)$" ) -eq 0 ]; then
    echo "This scoring program can only handle 'binomial' and 'regression' methods now." >&2
    usage
fi

Bạn có thể thêm nhiều chuỗi vào danh sách bằng cách tách chúng bằng một | (ống) ký tự.

Ưu điểm của việc sử dụng egrep, là bạn có thể dễ dàng thêm độ nhạy cảm trường hợp (-i) hoặc kiểm tra các tình huống phức tạp hơn bằng biểu thức chính quy.


1

Đây gần như là đề xuất ban đầu của bạn nhưng gần như là một lớp lót. Không phức tạp như các câu trả lời hợp lệ khác, và không phụ thuộc vào các phiên bản bash (có thể hoạt động với các bash cũ).

OK=0 ; MP_FLAVOURS="vanilla lemon hazelnut straciatella"
for FLAV in $MP_FLAVOURS ; do [ $FLAV == $FLAVOR ] && { OK=1 ; break; } ; done
[ $OK -eq 0 ] && { echo "$FLAVOR not a valid value ($MP_FLAVOURS)" ; exit 1 ; }

Tôi đoán đề xuất của tôi vẫn có thể được cải thiện, cả về chiều dài và phong cách.


0

Nếu nó không quá dài; bạn chỉ có thể xâu chuỗi chúng giữa các đẳng thức dọc theo một so sánh logic HOẶC như vậy.

if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
    echo In the list
fi 

Tôi đã có vấn đề chính xác này và trong khi ở trên là xấu, rõ ràng những gì đang xảy ra hơn so với các giải pháp tổng quát khác.

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.