我试图在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

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

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

输出:

1是(1,2,3)的成员 2是(1,2,3)的一个元素 3是(1,2,3)的一个元素 4不是(1,2,3)的成员

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

    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

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

haystack='foo bar'
needle='bar'

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

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

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

我发现使用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

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