检查 Bash 中的列表中是否存在变量
- 2024-11-05 08:37:00
- admin 原创
- 40
问题描述:
我正在尝试用 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
而我希望它失败。
解决方案 1:
[[ $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
解决方案 2:
怎么样
echo $list | grep -w -q $x
您可以检查输出或 $?
上面的行来做出决定。
grep -w
检查整个单词模式。添加-q
可防止回显列表。
解决方案 3:
我认为最简单的解决方案是在原始字符串前面和后面添加一个空格,然后使用正则表达式进行检查[[ ]]
haystack='foo bar'
needle='bar'
if [[ " $haystack " =~ .* $needle .* ]]; then
...
fi
对于包含针作为子字符串的值(例如 haystack ),这不会产生假阳性foo barbaz
。
(这个概念是从 JQuery 的hasClass()
-Method 中偷来的)
解决方案 4:
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"
解决方案 5:
有一种更简洁的方法来检查字符串是否在列表中:
if [[ $my_str = @(str1|str2|str3) ]]; then
echo "string found"
fi
解决方案 6:
如果不是太长;您可以将它们沿着逻辑或比较串联在相等位置,就像这样。
if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
echo In the list
fi
我遇到了同样的问题,虽然上面的内容很难看,但与其他通用解决方案相比,它更明显地表明了发生了什么。
解决方案 7:
如果使用双括号,那么也可以在 case 语句外使用 (* 通配符):
string='My string';
if [[ $string == *My* ]]
then
echo "It's there!";
fi
解决方案 8:
如果您的值列表要在脚本中硬编码,则使用 进行测试相当简单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
如果列表在运行时是一个数组变量,那么其他答案之一可能更适合。
解决方案 9:
考虑利用关联数组的键。我认为这比正则表达式/模式匹配和循环都要好,尽管我还没有对它进行分析。
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 )
解决方案 10:
如果脚本中的列表是固定的,我最喜欢下面的内容:
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"; }
笔记:
这是
sh
符合 POSIX 标准的。validate
不接受子字符串(如果需要,请删除 grep 选项)。-x
validate
将其参数解释为固定字符串,而不是正则表达式(-F
如果需要,请删除 grep 选项)。
执行该功能的示例代码:
for x in "item 1" "item2" "item 3" "3" "*"; do
echo -n "'$x' is "
validate "$x" && echo "valid" || echo "invalid"
done
解决方案 11:
我发现使用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
请注意,"
测试必须能够进行。
解决方案 12:
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=$'
'
# 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=$'
'
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
解决方案 13:
我想将我的解决方案添加到列表中。
# 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
解决方案 14:
先前的答案不使用tr ,我发现它对grep很有用。假设列表中的项目以空格分隔,则检查是否完全匹配:
echo $mylist | tr ' ' '
' | grep -F -x -q "$myitem"
如果该项目在列表中,则返回退出代码 0,如果不在列表中,则返回退出代码 1。
最好将其用作函数:
_contains () { # Check if space-separated list $1 contains line $2
echo "$1" | tr ' ' '
' | 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
解决方案 15:
迟到了?以下非常简单的变体尚未明确提及。我用它case
来检查简单列表,这是 Bourne Shell 的通用习惯用法,不依赖于任何外部或扩展:
haystack='a b c'
needle='b'
case " $haystack " in (*" $needle "*) :;; (*) false;; esac
请注意使用分隔符(此处
SPC
:)来正确界定模式:在的开始和结束处" $haystack "
,以及在的测试中同样如此" $needle "
。如果处于 中,该语句返回
true
( ) ,否则返回 false。$?=0
`$needle`$haystack
您还可以非常轻松地测试多个
$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
也可以使用 ksh/bashisms 如;&
(fallthroug) 或;;&
(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
是分隔符的一部分,对吗?
请注意,此功能属于公共领域,因为其中绝对没有任何内容可以真正受到版权保护。
解决方案 16:
简单的一行...
if [[ " 11 22 33 " == *" ${x} "* ]]; then echo "${x} is in the list"; fi;
在 fi 之前添加:else echo "${x} is NOT in the list";
解决方案 17:
受已接受的响应启发的替代解决方案,但使用了反向逻辑:
MODE="${1}"
echo "<${MODE}>"
[[ "${MODE}" =~ ^(preview|live|both)$ ]] && echo "OK" || echo "Uh?"
这里,输入 ($MODE) 必须是正则表达式中的选项之一(“preview”、“live”或“both”),而不是将整个选项列表与用户输入匹配。当然,您不希望正则表达式发生变化。
解决方案 18:
示例
$ 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
列表
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"
解决方案 19:
假设目标变量只能是“二项式”或“回归”,则以下内容可行:
# 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),或使用正则表达式检查更复杂的场景。
解决方案 20:
这几乎就是您的原始建议,但几乎是一行代码。不像其他有效答案那样复杂,也不那么依赖于 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 ; }
我想我的提案还有待改进,无论是长度还是风格。
解决方案 21:
肯特答案的更短版本,没有echo
:
grep -wq term <<< "item1 item2 item3"
解决方案 22:
下面的脚本实现了包含列表的功能。
function contains {
local target=$1
shift
printf '%s
' "$@" | 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
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件