我试图在bash中编写一个脚本,检查用户输入的有效性。 我想将输入(变量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,而我希望它失败。


当前回答

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

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

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

Please note the use of the separator (here: SPC) to correcyly delimit the pattern: At the beginning and end of " $haystack " and likewise in the test of " $needle ". This statement returns true ($?=0) in case $needle is in $haystack, false otherwise. Also you can test for more than one $needle very easily. When there are several similar cases like if (haystack.contains(needle1)) { run1() } elif (haystack.contains(needle2)) { run2() } else { run3() } you can wrap this into the case, too: case " $haystack " in (*" $needle1 "*) run1;; (*" $needle2 "*) run2;; (*) run3;; esac and so on

这也适用于所有值不包括分隔符本身的列表,比如逗号:

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

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

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

错误的部分可以去掉吗?

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

为什么:;;

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

在bash中,您还可以使用ksh/bashisms,如;& (fallthrough)或;;&(测试其他模式)来表示if (hastack .contains(needle1)) {run1();};If (hastack .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的清晰部分如果分隔符是空字符串,对吧?

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

其他回答

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
[[ $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

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

# 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

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

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

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

Please note the use of the separator (here: SPC) to correcyly delimit the pattern: At the beginning and end of " $haystack " and likewise in the test of " $needle ". This statement returns true ($?=0) in case $needle is in $haystack, false otherwise. Also you can test for more than one $needle very easily. When there are several similar cases like if (haystack.contains(needle1)) { run1() } elif (haystack.contains(needle2)) { run2() } else { run3() } you can wrap this into the case, too: case " $haystack " in (*" $needle1 "*) run1;; (*" $needle2 "*) run2;; (*) run3;; esac and so on

这也适用于所有值不包括分隔符本身的列表,比如逗号:

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

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

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

错误的部分可以去掉吗?

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

为什么:;;

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

在bash中,您还可以使用ksh/bashisms,如;& (fallthrough)或;;&(测试其他模式)来表示if (hastack .contains(needle1)) {run1();};If (hastack .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的清晰部分如果分隔符是空字符串,对吧?

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

这几乎是你最初的提议,但几乎是一行。不像其他有效答案那么复杂,也不那么依赖于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 ; }

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