如何检查BASH列表中是否存在变量

时间:2011-11-09 09:57:19

标签: linux bash

我正在尝试用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,而我希望它失败。

17 个答案:

答案 0 :(得分:133)

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

答案 1 :(得分:28)

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"

答案 2 :(得分:14)

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

string='My string';

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

答案 3 :(得分:11)

恕我直言最简单的解决方案是在前面添加原始字符串并附加空格并使用[[ ]]检查正则表达式

haystack='foo bar'
needle='bar'

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

对于包含针作为子字符串的值的值,这不会是误报,例如与大海捞foo barbaz

(这个概念是从JQuery的hasClass() - 方法中无耻地偷走的

答案 4 :(得分:10)

怎么样

echo $list|grep $x

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

答案 5 :(得分:6)

如果列表已在脚本中修复,我最喜欢以下内容:

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 接受子字符串(如果需要,请删除grep的-x选项。)
  • validate将其参数解释为固定字符串,而不是常规字符串 表达式(如果需要,请删除grep的-F选项。)

练习该功能的示例代码:

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

答案 6 :(得分:5)

如果要在脚本中对您的值列表进行硬编码,则使用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

如果列表是运行时的数组变量,则其他答案之一可能更适合。

答案 7 :(得分:5)

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

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

答案 8 :(得分:4)

考虑利用associative arrays的密钥。我认为这优于正则表达式/模式匹配和循环,虽然我没有对它进行分析。

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 )

答案 9 :(得分:3)

如果时间不长;您可以像这样通过逻辑或比较将它们放在相等之间。

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

我遇到了这个确切的问题,尽管上述情况很难看,但比起其他广义解决方案,发生了什么事更明显。

答案 10 :(得分:1)

假设TARGET变量只能是&#39;二项式&#39;或者&#39;回归&#39;然后关注:

# 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),或使用正则表达式检查更复杂的场景。

答案 11 :(得分:1)

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

答案 12 :(得分:0)

应该将解决方案添加到列表中。

# 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

答案 13 :(得分:0)

这几乎是您最初的建议,但几乎是1排。并没有其他有效答案那么复杂,而且也不取决于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 ; }

我想我的建议在长度和样式上都可以改进。

答案 14 :(得分:0)

受接受的响应启发的替代解决方案,但是使用了倒置逻辑:

MODE="${1}"

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

在此,输入($ MODE)必须是正则表达式(“ preview”,“ live”或“ both”)中的选项之一,这与将整个选项列表与用户输入进行匹配相反。当然,您不希望正则表达式会发生变化。

答案 15 :(得分:0)

先前的答案不使用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

答案 16 :(得分:0)

演出迟到了?尚未明确提及以下非常简单的变体。我使用 case 来检查简单列表,这是一个通用的 Bourne Shell 习语,不依赖任何外部或扩展:

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

case " $haystack " in (*" $needle "*) :;; (*) false;; esac
  • 请注意使用分隔符(此处:SPC)来正确地分隔模式:在 " $haystack " 的开头和结尾以及 " $needle " 的测试中也是如此。
  • 如果 true$?=0 中,此语句返回 $needle ($haystack),否则返回 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,不受“普通”POSIX/Bourne shell 支持。 (您可以在 POSIX-Shells 中使用 $@ 解决此限制,但这与此处提出的完全不同。)

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

  • 否,因为这是关键的返回值。默认情况下,case 返回 true
  • 是的,如果您不需要返回值并将处理放在 : 的位置

为什么:;;

  • 我们也可以写 true;;,但我习惯使用 : 而不是 true,因为它更短而且输入速度更快
  • 此外,我认为不要写任何不好的做法,因为 case 的默认返回值是否为真对每个人来说并不明显。
  • 还有“遗漏”该命令通常表示“这里忘记了某事”。因此,在此处放置一个多余的“:”清楚地表明“除了在此处返回 true 之外,它什么都不做”。

bash 中,您还可以使用 ;&(fallthroug)或 ;;&(测试其他模式)等 ksh/bashisms 来表达 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 的明确部分,以防您的分隔符是空字符串,对吗?

<块引用>

请注意,此功能属于公共领域,因为它绝对没有真正受版权保护的内容。