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 () {
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
この方法には、(少なくとも明示的にではなく)すべての要素をループ処理する必要がないという利点があります。しかし、 array.c のarray_to_string_internal()
は依然として配列要素をループしてそれらを文字列に連結するので、おそらく提案されているループ解決策より効率的ではありませんが、より読みやすくなります。
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
検索している値がスペースを含む配列要素内の単語の1つである場合は、誤検出が発生することに注意してください。例えば
array=("Jack Brown")
value="Jack"
正規表現では、そうでなくても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
$ myarray=(one two three)
$ case "${myarray[@]}" in *"two"*) echo "found" ;; esac
found
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
パフォーマンスが必要な場合は、検索するたびに配列全体をループする必要はありません。
この場合、その配列のインデックスを表す連想配列(ハッシュテーブル、または辞書)を作成できます。すなわち各配列要素を配列内のそのインデックスにマッピングします。
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"
私は通常単に使用します:
inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)
ゼロ以外の値は、一致が見つかったことを示します。
ワンラインソリューション
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
フラグを追加しました。それは単に一致の存在を「真」として扱うでしょう。
機能のないもう一つのライナー:
(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo found || 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"
これはちょっとした貢献です。
array=(Word "two words" words)
search_string="two"
match=$(echo "${array[@]:0}" | grep -o $search_string)
[[ ! -z $match ]] && echo "found !"
注:この方法では「2語」というケースは区別されませんが、これは問題では必要ありません。
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }
今 空の配列を正しく処理します。
正確な一致を得るために配列全体を反復処理する価値があるかどうかを確認するために、迅速で汚いテストを行いたい場合、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
これにより、 "Checking"と "Match"が出力されます。 array=(Word "two words" something)
では、 "Checking"のみを出力します。 array=(Word "two widgets" something)
の場合は何も出力されません。
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
これは私のために働いています:
# 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
与えられた:
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との互換性を維持するためです)
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
区切り文字とスペースに問題がないことに注意してください。
Dennis Williamson の answer から借りて、以下の解決法は、配列、シェルセーフな引用、および正規表現を組み合わせて、以下の必要性を回避します。パイプまたは他のサブプロセスを使用する。または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正規表現を使用することによって機能します。正規表現の一致が配列内の値の巧妙な組み合わせによってだまされないようにするための6つの重要なステップがあります。
printf
シェルクォート%q
を使用して比較文字列を作成します。シェルクォーテーションは、バックスラッシュ\
でエスケープされることによって、特殊文字が「シェルセーフ」になることを保証します。%q
を使用すると、区切り文字はエスケープされる特殊文字の1つになります。これが、配列内の値が正規表現の一致を欺くための賢い方法で構築できないことを保証する唯一の方法です。私はカンマ,
を選びました。なぜなら、その文字は他の方法で予想外の方法で評価または誤用されたときに最も安全だからです。printf
への引数として,,%q
を使用しました。特殊文字の2つのインスタンスは、区切り文字として表示されている場合にのみ隣接して表示されるため、これは重要です。特殊文字の他のすべてのインスタンスはエスケープされます。${array_str}
と比較する代わりに、${array_str},,
と比較します。私は一般的にこれらの種類のユーティリティは変数の値ではなく変数の名前で動作するように書く。これは主に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"
等.
答えた後、私は私が特に好きだったもう一つの答えを読みました、しかしそれは欠陥がありそして軽蔑されました。私はインスピレーションを得ました、そしてここに私が実行可能に見える2つの新しいアプローチがあります。
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>
を追加
これが私の考えです。
回避することができれば、実行に時間がかかるため、bash for loopを使用しないでください。何かループする必要がある場合は、シェルスクリプトよりも低レベルの言語で書かれたものにします。
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の以前のバージョンでは動作しません。)空白でのWord分割を避けるために$IFS
を設定します。
この関数には明示的なループは含まれていませんが、printf
を生成するために内部的に入力配列をステップスルーします。 printfフォーマットは%q
を使用して、入力データが配列キーとして安全に使用できるようにエスケープされるようにします。
$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$
この関数が使うことはすべてbashに組み込まれていることに注意してください。そのため、コマンド展開でも、外部のパイプがあなたをドラッグダウンすることはありません。
そしてeval
を使うのが好きでなければ…他のアプローチを自由に使うことができます。 :-)
ここに提示されたアイデアのいくつかを組み合わせると、ループなしの文が 完全に一致するWordが に一致する場合、エレガントにすることができます。
$find="myword"
$array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
echo "Array contains myword";
fi
これはWord
やval
では起動せず、Word全体が一致するだけです。各配列値に複数の単語が含まれていると壊れます。
配列に特定の値が含まれていることを確認するためにcase
ロジックを使用することに関する@ ghostdog74の回答に少し追加しました。
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
私は、ある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
私の場合は、IDのリスト用にいくつかのJSONをフィルタリングするためにjqを実行していて、後で自分のIDがこのリストに含まれているかどうかを確認する必要がありました。手動で作成されたLIST=("1" "2" "4")
型の配列に対しては機能しませんが、改行で区切られたスクリプト出力に対しては機能します。
シモンズ:私は比較的新しいので答えをコメントできませんでした….
これが私のこの問題に対する考え方です。これがショートバージョンです。
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