在Bash中,最简单的测试数组是否包含某个值的方法是什么?
编辑 :在答案和评论的帮助下,经过一些测试,我想到了这个:
function contains() {
local n=$#
local value=${!n}
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}
A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
echo "contains three"
fi
我不确定这是否是最好的解决方案,但似乎可行。
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }
现在可以正确处理空数组。
另一个没有功能的班轮:
(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"
感谢@Qwerty有关空间的注意事项!
对应功能:
find_in_array() {
local word=$1
shift
for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
}
例:
some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"
以下代码检查给定值是否在数组中,并返回其从零开始的偏移量:
A=("one" "two" "three four")
VALUE="two"
if [[ "$(declare -p A)" =~ '['([0-9]+)']="'$VALUE'"' ]];then
echo "Found $VALUE at offset ${BASH_REMATCH[1]}"
else
echo "Couldn't find $VALUE"
fi
匹配是在完整值上完成的,因此设置VALUE =“ three”将不匹配。
如果您需要性能,则不想每次搜索时都遍历整个阵列。
在这种情况下,您可以创建一个表示该数组索引的关联数组(哈希表或字典)。 即它将每个数组元素映射到其在数组中的索引:
make_index () {
local index_name=$1
shift
local -a value_array=("$@")
local i
# -A means associative array, -g means create a global variable:
declare -g -A ${index_name}
for i in "${!value_array[@]}"; do
eval ${index_name}["${value_array[$i]}"]=$i
done
}
然后,您可以像这样使用它:
myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"
并像这样测试成员资格:
member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND
或者:
if [ "${myarray_index[$member]}" ]; then
echo FOUND
fi
请注意,即使测试值或数组值中有空格,此解决方案也可以正确处理。
另外,您还可以通过以下方式获取数组中值的索引:
echo "<< ${myarray_index[$member]} >> is the index of $member"
这种方法的优点是不需要遍历所有元素(至少不是显式地)。 但由于array_to_string_internal()
在array.c仍然会遍历数组元素和连接它们成一个字符串,它可能不超过所提出的解决方案循环更有效,但它更具有可读性。
if [[ " ${array[@]} " =~ " ${value} " ]]; then
# whatever you want to do when arr contains value
fi
if [[ ! " ${array[@]} " =~ " ${value} " ]]; then
# whatever you want to do when arr doesn't contain value
fi
请注意,如果要搜索的值是带有空格的数组元素中的单词之一,它将给出假肯定。 例如
array=("Jack Brown")
value="Jack"
regex会发现Jack
不在数组中,即使不是。 因此,如果您仍想使用此解决方案,则必须在正则表达式上更改IFS
和分隔符
IFS=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS
value="Jack Smith"
if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
echo "yep, it's there"
fi
如果您不想迭代,这可能值得调查:
#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
echo "Value was found"
fi
exit
摘录自: http : //www.thegeekstuff.com/2010/06/bash-array-tutorial/我认为这很聪明。
编辑:您可能可以做:
if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi
但是后者仅在数组包含唯一值时才有效。 在“ 143”中寻找1会产生假阳性的方法。
给出:
array=("something to search for" "a string" "test2000")
elem="a string"
然后简单检查一下:
if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[@]/#/${c}} ${c}" =~ $p ]]; then
echo "$elem exists in array"
fi
哪里
c is element separator
p is regex pattern
(之所以单独分配p而不是直接在[[]]内部使用表达式是为了保持对bash 4的兼容性)
我通常编写这类实用程序来对变量的名称进行操作,而不是对变量值进行操作,这主要是因为bash无法通过引用传递变量。
这是一个使用数组名称的版本:
function array_contains # array value
{
[[ -n "$1" && -n "$2" ]] || {
echo "usage: array_contains <array> <value>"
echo "Returns 0 if array contains value, 1 otherwise"
return 2
}
eval 'local values=("${'$1'[@]}")'
local element
for element in "${values[@]}"; do
[[ "$element" == "$2" ]] && return 0
done
return 1
}
这样,问题示例变为:
array_contains A "one" && echo "contains one"
等等
有点晚了,但是您可以使用:
#!/bin/bash
# isPicture.sh
FILE=$1
FNAME=$(basename "$FILE") # Filename, without directory
EXT="${FNAME##*.}" # Extension
FORMATS=(jpeg JPEG jpg JPG png PNG gif GIF svg SVG tiff TIFF)
NOEXT=( ${FORMATS[@]/$EXT} ) # Formats without the extension of the input file
# If it is a valid extension, then it should be removed from ${NOEXT},
#+making the lengths inequal.
if ! [ ${#NOEXT[@]} != ${#FORMATS[@]} ]; then
echo "The extension '"$EXT"' is not a valid image extension."
exit
fi
我通常只使用:
inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)
非零值表示找到匹配项。
我想出了这个方法,它只能在zsh中工作,但是我认为一般方法很好。
arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
echo "found!"
else
echo "not found!"
fi
仅当每个元素以${arr[@]/#pattern/}
开头或以${arr[@]/%pattern/}
结尾时,才从每个元素中取出模式。 这两个替换都可以在bash中使用,但同时${arr[@]/#%pattern/}
只能在zsh中使用。
如果修改后的数组等于原始数组,则它不包含元素。
编辑:
这在bash中有效:
function contains () {
local arr=(${@:2})
local el=$1
local marr=(${arr[@]/#$el/})
[[ "${#arr[@]}" != "${#marr[@]}" ]]
}
替换后,它将比较两个数组的长度。 如果数组包含元素,则替换将完全删除它,并且计数将不同。
a=(b c d)
if printf '%s\0' "${a[@]}" | grep -Fqxz c
then
echo 'array “a” contains value “c”'
fi
如果您愿意,可以使用等效的长选项:
--fixed-strings --quiet --line-regexp --null-data
尽管这里有几个很好的有用的答案,但我找不到一个似乎是性能,跨平台和健壮性的正确组合的答案。 所以我想分享我为代码编写的解决方案:
#!/bin/bash
# array_contains "$needle" "${haystack[@]}"
#
# Returns 0 if an item ($1) is contained in an array ($@).
#
# Developer note:
# The use of a delimiter here leaves something to be desired. The ideal
# method seems to be to use `grep` with --line-regexp and --null-data, but
# Mac/BSD grep doesn't support --line-regexp.
function array_contains()
{
# Extract and remove the needle from $@.
local needle="$1"
shift
# Separates strings in the array for matching. Must be extremely-unlikely
# to appear in the input array or the needle.
local delimiter='#!-\8/-!#'
# Create a string with containing every (delimited) element in the array,
# and search it for the needle with grep in fixed-string mode.
if printf "${delimiter}%s${delimiter}" "$@" | \
grep --fixed-strings --quiet "${delimiter}${needle}${delimiter}"; then
return 0
fi
return 1
}
我建议使用的我的正则表达式技术版本:
values=(foo bar)
requestedValue=bar
requestedValue=${requestedValue##[[:space:]]}
requestedValue=${requestedValue%%[[:space:]]}
[[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"
这里发生的事情是,您正在将整个支持的值数组扩展为单词,并在每个单词的前面添加一个特定的字符串“ X-”,并对请求的值执行相同的操作。 如果这个确实包含在数组中,那么结果字符串最多将与结果标记之一匹配,或者完全不匹配。 在后一种情况下|| 运算符触发,您知道您正在处理一个不受支持的值。 在此之前,所有请求的值都通过标准shell字符串操作从所有前导和尾随空格中去除。
我相信这是干净优雅的,尽管我不太确定如果支持的值数组特别大时性能会如何。
这是我对这个问题的看法。 这是简短的版本:
function arrayContains() {
local haystack=${!1}
local needle="$2"
printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}
而长版,我认为它在眼睛上要容易得多。
# With added utility function.
function arrayToLines() {
local array=${!1}
printf "%s\n" ${array[@]}
}
function arrayContains() {
local haystack=${!1}
local needle="$2"
arrayToLines haystack[@] | grep -q "^$needle$"
}
例子:
test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False
有示例代码显示了如何从数组中替换子字符串 。 您可以复制数组,然后尝试从副本中删除目标值。 如果副本和原始副本不同,则目标值存在于原始字符串中。
直接(但可能会更耗时)的解决方案是简单地遍历整个数组并分别检查每个项目。 我通常这样做是因为它易于实现,并且可以将其包装在函数中( 有关将数组传递给函数的信息,请参见此信息 )。
for i in "${array[@]}"
do
if [ "$i" -eq "$yourValue" ] ; then
echo "Found"
fi
done
对于字符串:
for i in "${array[@]}"
do
if [ "$i" == "$yourValue" ] ; then
echo "Found"
fi
done
如果您想进行快速而肮脏的测试以查看是否值得遍历整个数组以获得精确匹配,则Bash可以将数组视为标量。 测试标量中的匹配项,如果没有匹配项,则跳过循环可节省时间。 显然,您会得到误报。
array=(word "two words" words)
if [[ ${array[@]} =~ words ]]
then
echo "Checking"
for element in "${array[@]}"
do
if [[ $element == "words" ]]
then
echo "Match"
fi
done
fi
这将输出“检查中”和“匹配”。 使用array=(word "two words" something)
,它将仅输出“正在检查”。 使用array=(word "two widgets" something)
将没有输出。
$ myarray=(one two three)
$ case "${myarray[@]}" in *"two"*) echo "found" ;; esac
found
grep
和printf
在新行上格式化每个数组成员,然后grep
行。
if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
例:
$ array=("word", "two words") $ if printf '%s\\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi true
请注意,这对于分度符和空格没有问题。
回答完之后,我读了一个我特别喜欢的答案,但是它有缺陷并且被否决了。 我受到启发,这是我认为可行的两种新方法。
array=("word" "two words") # let's look for "two words"
grep
和printf
: (printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>
for
: (for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>
对于not_found结果,添加|| <run_your_if_notfound_command_here>
|| <run_your_if_notfound_command_here>
这为我工作:
# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
# odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
local list=$1[@]
local elem=$2
# echo "list" ${!list}
# echo "elem" $elem
for i in "${!list}"
do
# echo "Checking to see if" "$i" "is the same as" "${elem}"
if [ "$i" == "${elem}" ] ; then
# echo "$i" "was the same as" "${elem}"
return 0
fi
done
# echo "Could not find element"
return 1
}
示例调用:
arr=("abc" "xyz" "123")
if contains arr "abcx"; then
echo "Yes"
else
echo "No"
fi
这是我的看法。
如果可以避免,我宁愿不使用bash for循环,因为这会花费一些时间。 如果某些东西必须循环,那么就应该使用比Shell脚本低级的语言编写的东西。
function array_contains { # arrayname value
local -A _arr=()
local IFS=
eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
return $(( 1 - 0${_arr[$2]} ))
}
这是通过创建临时关联数组_arr
,该数组的索引是从输入数组的值派生的。 (请注意,关联数组在bash 4及更高版本中可用,因此该功能在bash的早期版本中不起作用。)我们设置$IFS
以避免在$IFS
进行单词拆分。
该函数不包含任何显式循环,尽管在内部bash会逐步遍历输入数组以填充printf
。 printf格式使用%q
来确保转义输入数据,以便可以安全地将它们用作数组键。
$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$
请注意,此函数使用的所有内容都是bash的内置功能,因此,即使在命令扩展中,也没有外部管道将您拖到下面。
而且,如果您不喜欢使用eval
...,那么您可以自由使用另一种方法。 :-)
结合此处介绍的一些思想,您可以使无言的陈述完全匹配单词 ,从而达到优雅的效果。
$find="myword"
$array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
echo "Array contains myword";
fi
这不会在word
或val
上触发,只有整个单词匹配。 如果每个数组值包含多个单词,它将中断。
借用Dennis Williamson的答案 ,以下解决方案结合了数组,shell安全引用和正则表达式,从而避免了以下需求:循环遍历; 使用管道或其他子流程; 或使用非bash实用程序。
declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[@]}"
if [[ "${array_str},," =~ ,,words,, ]]
then
echo 'Matches'
else
echo "Doesn't match"
fi
上面的代码通过使用Bash正则表达式与数组内容的字符串化版本匹配来工作。 有六个重要步骤可确保数组内值的巧妙组合不会误导正则表达式匹配:
printf
shell引用%q
构造比较字符串。 Shell引号将确保特殊字符通过使用反斜杠\\
进行转义而变得“ shell安全”。 %q
时将转义的特殊字符之一; 这是保证不能以巧妙的方式构造数组中的值来欺骗正则表达式匹配的唯一方法。 我选择逗号,
因为当以其他意外方式评估或滥用该字符时,
该字符是最安全的。 ,,%q
作为printf
的参数。 这很重要,因为两个特殊字符实例仅在作为分隔符出现时才可以相邻出现。 特殊字符的所有其他实例将被转义。 ${array_str}
比较,而是与${array_str},,
。 一线解决方案
printf '%s\n' ${myarray[@]} | grep -P '^mypattern$'
说明
printf
语句在单独的一行上打印数组的每个元素。
grep
语句使用特殊字符^
和$
查找一行,该行恰好包含作为mypattern
给出的模式(不多也不少)。
用法
将其放入if ... then
语句中:
if printf '%s\n' ${myarray[@]} | grep -q -P '^mypattern$'; then
# ...
fi
我在grep
表达式中添加了-q
标志,以便它不会显示匹配项; 它将匹配的存在视为“ true”。
我不得不检查一下另一个脚本/命令生成的ID列表中是否包含一个ID。 对我来说,以下工作:
# the ID I was looking for
ID=1
# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "
# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)
# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
echo "not found"
fi
# etc.
您还可以像这样缩短/压缩它:
if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
echo "not found"
fi
就我而言,我正在运行jq来为ID列表过滤一些JSON,后来不得不检查我的ID是否在此列表中,这对我来说是最好的。 它不适用于类型为LIST=("1" "2" "4")
手动创建的数组,但不适用于以换行符分隔的脚本输出。
PS .:无法评论答案,因为我比较新...
这是一个小贡献:
array=(word "two words" words)
search_string="two"
match=$(echo "${array[@]:0}" | grep -o $search_string)
[[ ! -z $match ]] && echo "found !"
注意:这种方式不能区分大小写“两个单词”,但这在问题中不是必需的。
@ ghostdog74的答案中有一小部分关于使用case
逻辑来检查数组是否包含特定值:
myarray=(one two three)
word=two
case "${myarray[@]}" in ("$word "*|*" $word "*|*" $word") echo "found" ;; esac
或者打开extglob
选项,您可以这样操作:
myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac
我们也可以使用if
语句来做到这一点:
myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi
if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
echo "array contains '$item'"
else
echo "array does not contain '$item'"
fi
这种方法既不使用grep
等外部实用程序,也不使用循环。
这里发生的是:
IFS
变量值,我们也实现了将定界符用于数组连接; IFS
值替换成为临时操作 以下是实现此目的的一个小功能。 搜索字符串是第一个参数,其余的是数组元素:
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
该功能的测试运行可能类似于:
$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1