在Bash中检查变量是否存在于列表中

我试图在bash中写一个脚本,检查用户输入的有效性。< br / > 我想将输入(比如变量x)匹配到一个有效值列表

我现在想到的是:

for item in $list
do
if [ "$x" == "$item" ]; then
echo "In the list"
exit
fi
done
我的问题是是否有更简单的方法来做到这一点,
对于大多数编程语言,类似list.contains(x)

列表是:

list="11 22 33"

我的代码将只对这些值回显消息,因为list被视为数组而不是字符串, 所有的字符串操作都将验证1,而我希望它失败

334167 次浏览

如何

echo $list | grep -w -q $x

你可以检查输出或上面一行的$?来做出决定。

grep -w检查整个单词模式。添加-q可以防止回显列表。

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

或者创建一个函数:

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

使用它:

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

如果你使用双括号,你也可以在case语句外使用(*通配符):

string='My string';


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

Matvey是对的,但你应该引用$x,并考虑任何类型的“空格”(例如新行)

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

所以,即。

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

然后结束

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

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

注意,对于变量,必须使用"":

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

考虑利用关联数组的键。我认为这优于正则表达式/模式匹配和循环,尽管我还没有对其进行分析。

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

输出:

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 )

如果将列表固定在脚本中,我最喜欢以下几个:

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

然后使用validate "$x"来测试是否允许$x

如果你想要一行语句,并且不关心项目名称中的空格,你可以使用这个(注意-w而不是-x):

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

注:

  • 这是POSIX sh兼容的。
  • validate接受子字符串(如果你想要,可以将-x选项删除到grep)。
  • validate将其参数解释为固定字符串,而不是常规字符串
  • . expression(如果你想去掉grep的-F选项)

演示函数的示例代码:

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

例子

$ 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

in_list

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"

如果要在脚本中硬编码值列表,则使用case进行测试相当简单。下面是一个简短的例子,你可以根据自己的需求进行调整:

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

如果列表在运行时是一个数组变量,那么其他答案中的一个可能更适合。

我发现使用下面所示的echo $LIST | xargs -n1 echo | grep $VALUE形式更容易:

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

这适用于空格分隔的列表,但您可以通过执行以下操作将其改编为任何其他分隔符(如:):

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

注意,"是测试工作所必需的。

在我看来,最简单的解决方案是在原始字符串前加上一个空格,并用[[ ]]检查正则表达式

haystack='foo bar'
needle='bar'


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

对于包含needle作为子字符串的值,这将不会是假阳性,例如with a haystack foo barbaz

(这个概念是从JQuery的hasClass()-Method中偷来的)

假设TARGET变量只能是“二项式”或“回归”,那么如下所示:

# 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

您可以通过使用|(管道)字符分隔更多字符串到列表中。

使用egrep的优点是可以轻松地添加大小写不敏感(-i),或者使用正则表达式检查更复杂的场景。

我想把我的解决方案也加进去。

# 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

这几乎是你最初的提议,但几乎是一行。不像其他有效答案那么复杂,也不那么依赖于bash版本(可以使用旧的bash)。

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

我想我的提案在篇幅和风格上都还可以改进。

如果不是太长;你可以像这样用逻辑或比较把它们串在相等之间。

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

我遇到过这个问题,虽然上面的问题很难看,但它比其他广义解决方案更明显。

shell内置的compgen可以在这里提供帮助。它可以接受带有-W标志的列表,并返回它找到的任何潜在匹配项。

# My list can contain spaces so I want to set the internal
# file separator to newline to preserve the original strings.
IFS=$'\n'


# Create a list of acceptable strings.
accept=( 'foo' 'bar' 'foo bar' )


# The string we will check
word='foo'


# compgen will return a list of possible matches of the
# variable 'word' with the best match being first.
compgen -W "${accept[*]}" "$word"


# Returns:
# foo
# foo bar

我们可以编写一个函数来测试字符串是否等于可接受字符串的最佳匹配。这允许您分别为通过或失败返回0或1。

function validate {
local IFS=$'\n'
local accept=( 'foo' 'bar' 'foo bar' )
if [ "$1" == "$(compgen -W "${accept[*]}" "$1" | head -1)" ] ; then
return 0
else
return 1
fi
}

现在您可以编写非常清晰的测试来验证字符串是否可接受。

validate "blah" || echo unacceptable


if validate "foo" ; then
echo acceptable
else
echo unacceptable
fi

另一种受公认回答启发的解决方案,但使用了相反的逻辑:

MODE="${1}"


echo "<${MODE}>"
[[ "${MODE}" =~ ^(preview|live|both)$ ]] && echo "OK" || echo "Uh?"

这里,输入($MODE)必须是正则表达式中的一个选项('preview', 'live',或'both'),而不是将整个选项列表匹配到用户输入。当然,您不会期望正则表达式发生变化。

先前的答案不使用< >强tr < / >强,我发现grep是有用的。假设列表中的项以空格分隔,检查精确匹配:

echo $mylist | tr ' ' '\n' | grep -F -x -q "$myitem"

如果条目在列表中,这将返回退出代码0,如果不在列表中,则返回退出代码1。

最好将它作为函数使用:

_contains () {  # Check if space-separated list $1 contains line $2
echo "$1" | tr ' ' '\n' | grep -F -x -q "$2"
}


mylist="aa bb cc"


# Positive check
if _contains "${mylist}" "${myitem}"; then
echo "in list"
fi


# Negative check
if ! _contains "${mylist}" "${myitem}"; then
echo "not in list"
fi

演出迟到了?下面的非常简单的变体还没有明确提到。我使用case来检查简单的列表,这是一个一般的Bourne Shell成语,不依赖于任何外部或扩展:

haystack='a b c'
needle='b'


case " $haystack " in (*" $needle "*) :;; (*) false;; esac
  • 请注意使用分隔符(这里:SPC)来正确地分隔模式:在" $haystack "的开头和结尾,在" $needle "的测试中也是如此。
  • 如果$needle$haystack中,此语句返回true ($?=0),否则返回false。
  • 也可以很容易地测试多个$needle。当有几个类似的情况,如
    if (haystack.contains(needle1)) { run1() } elif (haystack.contains(needle2)) { run2() } else { run3() }
    你也可以把它包装到case中:
    case " $haystack " in (*" $needle1 "*) run1;; (*" $needle2 "*) run2;; (*) run3;; esac
    依此类推

这也是适用于值不包括分隔符本身的所有列表,像逗号一样:

haystack=' a , b , c '
needle=' b '


case ",$haystack," in (*",$needle,"*) :;; (*) false;; esac

注意,如果值可以包含包括分隔符序列的任何东西(除了NUL,因为shell不支持变量中的NUL,因为你不能将包含NUL的参数传递给命令),那么你需要使用数组。数组是ksh/bashisms,不支持"ordinary"POSIX / Bourne shell。(你可以在posix - shell中使用$@来解决这个限制,但这与这里所讲述的完全不同。)

(*) false部分可以省略吗?

  • 不,因为这是临界返回值。默认情况下,case返回true
  • 是的,如果你不需要返回值,并把你的处理放在:的位置

为什么是:;;

  • 我们也可以写true;;,但我习惯使用:而不是true,因为它更短,打字更快
  • 另外,我认为不写任何不好的做法,因为对于每个人来说,case的默认返回值是否为真并不明显。
  • 还有“leaving out”;这个命令通常表示“这里遗忘了一些东西”。所以用了一个多余的“;:"清楚地表明“它的目的只是在这里返回true”。

bash中,你也可以使用ksh/bashisms,如;& (fallthrough)或;;& (test other patterns)来表示if (haystack.contains(needle1)) { run1(); }; if (haystack.contains(needle2)) { run2(); }

因此,通常case比其他正则表达式结构更易于维护。此外,它不使用正则表达式,它只使用shell模式,这甚至可能更快。


可重用的功能:

: Needle "list" Seperator_opt
NeedleListSep()
{
if [ 3 -gt $# ];
then NeedleListSep "$1" "$2" " ";
else case "$3$2$3" in (*"$3$1$3"*) return 0;; esac; return 1;
fi;
}

bash中,你可以将其简化为

: Needle "list" Seperator_opt
NeedleListSep()
{
local s="${3-" "}";
case "$s$2$s" in (*"$s$1$s"*) return 0;; esac; return 1;
}

像这样使用

Test() {
NeedleListSep "$1" "a b c"           && echo found $1 || echo no $1;
NeedleListSep "$1" "a,b,c"     ','   && echo found $1 || echo no $1;
NeedleListSep "$1" "a # b # c" ' # ' && echo found $1 || echo no $1;
NeedleListSep "$1" "abc"       ''    && echo found $1 || echo no $1;
}
Test a
Test z

如上所示,这也适用于分隔符为空字符串的退化情况(因此列表中的每个字符都是针)。例子:

Test

返回

no
no
no
found

因为空字符串是abc的明确部分,如果你的分隔符是空字符串,对吧?

请注意,这个函数是公共领域的,因为它绝对没有任何东西可以真正获得版权。

有一种更简单的方法来检查string是否在列表中:

if [[ $my_str = @(str1|str2|str3) ]]; then
echo "string found"
fi

下面的脚本实现了一个列表的包含函数。

    function contains {
local target=$1
shift
    

printf '%s\n' "$@" | grep -x -q "$target"
out=$?
(( out = 1 - out ))
return $out
}

如果将一个基于空白的字符串转换为一个列表并使用它,它似乎可以按以下方式解决。


list="11 22 33"
IFS=" " read -ra parsed_list <<< "$list"
    

# parsed_list would be ("11" "22" "33")
    

contains "11" "${parsed_list[@]}"
echo $?  # 1
    

contains "22" "${parsed_list[@]}"
echo $?  # 1
    

contains "1" "${parsed_list[@]}"
echo $? # 0
    

contains "11 22" "${parsed_list[@]}"
echo $? # 0