シェルスクリプトの連想配列


回答:


20

Irfanの回答に追加するためにget()、マップの内容を反復する必要がないため、のより短いバージョンとより速いバージョンを次に示します。

get() {
    mapName=$1; key=$2

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}

16
サブシェルとsedをフォークすることはほとんど最適ではありません。Bash4はこれをネイティブにサポートし、bash3はより良い代替手段を持っています。
lhunath 2010

149

移植性が主な関心事ではない場合の別のオプションは、シェルに組み込まれている連想配列を使用することです。これはbash 4.0(ほとんどの主要なディストリビューションで現在利用可能ですが、自分でインストールしない限りOS Xでは利用できません)、ksh、およびzshで機能するはずです。

declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"

echo ${newmap[company]}
echo ${newmap[name]}

シェルによっては、のtypeset -A newmap代わりにを実行する必要がある場合declare -A newmapや、まったく必要ない場合もあります。


回答を投稿していただきありがとうございます。bash4.0以上を使用している人にとっては、これが最善の方法だと思います。
Irfan Zulfiqar

BASH_VERSIONが設定されていること、および> = 4であることを確認するために、少々余裕を追加します。そして、はい、BASH 4は本当に、本当にクールです!
Tim Post

私はこのようなものを使用しています。配列のインデックス/添え字が存在しないエラーを「キャッチ」する最良の方法は何ですか?たとえば、コマンドラインオプションとして下付き文字を使用していて、ユーザーがタイプミスして「designatio」と入力した場合はどうなりますか?「不正な配列添え字」エラーが発生しますが、可能な場合は、配列ルックアップ時に入力を検証する方法を教えてください。
Jer

3
@Jerこれはかなりあいまいですが、変数がシェルで設定されているかどうかを判別するために使用できますtest -z ${variable+x}x問題ではないので、任意の文字列にすることができます)。Bashの連想配列の場合も同様です。使用しますtest -z ${map[key]+x}
ブライアンキャンベル

95

別の非bash 4方法。

#!/bin/bash

# A pretend Python dictionary with bash 3 
ARRAY=( "cow:moo"
        "dinosaur:roar"
        "bird:chirp"
        "bash:rock" )

for animal in "${ARRAY[@]}" ; do
    KEY=${animal%%:*}
    VALUE=${animal#*:}
    printf "%s likes to %s.\n" "$KEY" "$VALUE"
done

echo -e "${ARRAY[1]%%:*} is an extinct animal which likes to ${ARRAY[1]#*:}\n"

そこを検索するためにifステートメントをスローすることもできます。if [[$ var =〜/ blah /]]。または何でも。


2
この方法は、Bash 4が実際にない場合に適しています。しかし、私はVALUEをフェッチする行はVALUE = $ {animal#*:}のように安全であると思います。#文字が1つだけの場合、マッチングは最初の「:」で停止します。これにより、値に ":"を含めることもできます。
Ced-le-pingouin 2012年

@ Ced-le-pingouin〜それは素晴らしいポイントです!聞き取れませんでした。提案された改善を反映するように投稿を編集しました。
Bubnoff

1
これは、BASHパラメータ置換を使用した連想配列のかなりハックなエミュレーションです。"key" param-sub はコロンののすべて置き換え、値のパターンはコロンののすべて置き換えます。正規表現のワイルドカード一致に似ています。したがって、真の連想配列ではありません。BASH 3以下でハッシュ/連想配列のような機能を実行する簡単な方法が必要でない限り、お勧めしません。それでも動作します!詳細:tldp.org/LDP/abs/html/parameter-substitution.html#PSOREX2
Bubnoff

1
これは、キーで項目を検索する方法を提供しないため、連想配列を実装していません。数値インデックスから各キー(および値)を見つける方法を提供するだけです。(キーを使用して配列を反復処理することで項目を見つけることができますが、これは連想配列では望ましくありません。)
Eric Postpischil

@EricPostpischil True。それは単なるハックです。それは人がセットアップでおなじみの構文を使用することを可能にしますが、あなたが言うようにまだ配列を反復することを必要とします。私は以前のコメントでそれを明らかに連想配列ではないことを明確にしようとしました、そしてあなたが代替案を持っているならそれを勧めさえしません。私の見解では、Pythonのような他の言語に精通している人にとっては、書きやすく、使いやすいという点が唯一の利点です。BASH 3で連想配列を実際に実装したい場合は、手順を少し遡る必要があるかもしれません。
バブノフ2018

34

地図、または連想配列が実際に何であるかについて考える必要があると思います。与えられたキーの値を格納し、その値を迅速かつ効率的に取得するための方法です。キーを反復処理してすべてのキーと値のペアを取得したり、キーとそれに関連付けられた値を削除したりすることもできます。

ここで、シェルスクリプトで常に使用するデータ構造について考えてみてください。スクリプトを記述せずにシェルで使用する場合でも、これらのプロパティがあります。困惑?それはファイルシステムです。

実際、シェルプログラミングで連想配列を使用するために必要なのは、一時ディレクトリだけです。mktemp -dあなたの連想配列コンストラクタです:

prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)

echoand を使用したくないcat場合は、いつでも小さなラッパーを作成できます。これらはIrfanからモデル化されていますが、次のような任意の変数を設定するのではなく、単に値を出力します$value

#!/bin/sh

prefix=$(basename -- "$0")
mapdir=$(mktemp -dt ${prefix})
trap 'rm -r ${mapdir}' EXIT

put() {
  [ "$#" != 3 ] && exit 1
  mapname=$1; key=$2; value=$3
  [ -d "${mapdir}/${mapname}" ] || mkdir "${mapdir}/${mapname}"
  echo $value >"${mapdir}/${mapname}/${key}"
}

get() {
  [ "$#" != 2 ] && exit 1
  mapname=$1; key=$2
  cat "${mapdir}/${mapname}/${key}"
}

put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"

value=$(get "newMap" "company")
echo $value

value=$(get "newMap" "name")
echo $value

編集:このアプローチは実際には、質問者が提案したsedを使用した線形検索よりもかなり高速で、より堅牢です(キーと値に-、=、スペース、qnd ":SP:"を含めることができます)。ファイルシステムを使用しているからといって遅くなることはありません。これらのファイルが実際に呼び出される場合を除いて、ディスクへの書き込みは保証されませんsync。寿命が短いこのような一時ファイルの場合、それらの多くがディスクに書き込まれないことはまずありません。

次のドライバープログラムを使用して、Irfanのコード、JerryによるIrfanのコードの変更、および私のコードのいくつかのベンチマークを行いました。

#!/bin/sh

mapimpl=$1
numkeys=$2
numvals=$3

. ./${mapimpl}.sh    #/ <- fix broken stack overflow syntax highlighting

for (( i = 0 ; $i < $numkeys ; i += 1 ))
do
    for (( j = 0 ; $j < $numvals ; j += 1 ))
    do
        put "newMap" "key$i" "value$j"
        get "newMap" "key$i"
    done
done

結果:

    $時間./driver.sh irfan 10 5

    実数0分0.975秒
    ユーザー0m0.280s
    sys 0m0.691s

    $時間./driver.sh brian 10 5

    実数0m0.226s
    ユーザー0m0.057s
    sys 0m0.123s

    $時間./driver.sh jerry 10 5

    実数0分0.706秒
    ユーザー0m0.228s
    sys 0m0.530s

    $時間./driver.sh irfan 100 5

    実数0分10.633秒
    ユーザー0m4.366s
    sys 0m7.127s

    $時間./driver.sh brian 100 5

    実数0分1.682秒
    ユーザー0m0.546s
    sys 0m1.082s

    $時間./driver.sh jerry 100 5

    実数0分9.315秒
    ユーザー0m4.565s
    sys 0m5.446s

    $時間./driver.sh irfan 10500

    実数1分46.197秒
    ユーザー0m44.869s
    sys 1m12.282s

    $時間./driver.sh brian 10500

    実数0分16.003秒
    ユーザー0m5.135s
    sys 0m10.396s

    $時間./driver.sh jerry 10500

    実数1分24.414秒
    ユーザー0m39.696s
    sys 0m54.834s

    $時間./driver.sh irfan 1000 5

    実数4分25.145秒
    ユーザー3m17.286s
    sys 1m21.490s

    $時間./driver.sh brian 1000 5

    実数0分19.442秒
    ユーザー0m5.287s
    sys 0m10.751s

    $時間./driver.sh jerry 1000 5

    実数5分29.136秒
    ユーザー4m48.926s
    sys 0m59.336s


1
私はマップにファイルシステムを使用するべきではないと思います。基本的に、メモリ内でかなり高速に実行できる処理にIOを使用します。
Irfan Zulfiqar

9
ファイルは必ずしもディスクに書き込まれるとは限りません。syncを呼び出さない限り、オペレーティングシステムはそれらをメモリに残しておくだけです。コードがsedを呼び出し、いくつかの線形検索を実行していますが、これらはすべて非常に低速です。私はいくつかの簡単なベンチマークを行い、私のバージョンは5-35倍高速です。
ブライアンキャンベル

一方、bash4のネイティブ配列の方がはるかに優れたアプローチであり、bash3では、宣言と間接指定を使用することで、フォークせずにすべてをディスクから切り離すことができます。
lhunath 2010

7
とにかく「高速」と「シェル」は実際には一緒に動作しません。「最小限のIOを回避する」レベルで話しているような速度の問題には当てはまりません。/ dev / shmを検索して使用し、IOがないことを保証できます。
jmtd 2011

2
この解決策は私を驚かせました、そしてただ素晴らしいです。それは2016年にも当てはまります。それは本当に受け入れられるべき答えです。
ゴードン


7
####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
    alias "${1}$2"="$3"
}

# map_get map_name key
# @return value
#
function map_get
{
    alias "${1}$2" | awk -F"'" '{ print $2; }'
}

# map_keys map_name 
# @return map keys
#
function map_keys
{
    alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}

例:

mapName=$(basename $0)_map_
map_put $mapName "name" "Irfan Zulfiqar"
map_put $mapName "designation" "SSE"

for key in $(map_keys $mapName)
do
    echo "$key = $(map_get $mapName $key)
done

4

今この質問に答えます。

次のスクリプトは、シェルスクリプトで連想配列をシミュレートします。そのシンプルで非常に理解しやすい。

マップは、keynamePairが--name = Irfan --designation = SSE --company = My:SP:Own:SP:Companyとして保存された、決して終了しない文字列です。

値の場合、スペースは「:SP:」に置き換えられます

put() {
    if [ "$#" != 3 ]; then exit 1; fi
    mapName=$1; key=$2; value=`echo $3 | sed -e "s/ /:SP:/g"`
    eval map="\"\$$mapName\""
    map="`echo "$map" | sed -e "s/--$key=[^ ]*//g"` --$key=$value"
    eval $mapName="\"$map\""
}

get() {
    mapName=$1; key=$2; valueFound="false"

    eval map=\$$mapName

    for keyValuePair in ${map};
    do
        case "$keyValuePair" in
            --$key=*) value=`echo "$keyValuePair" | sed -e 's/^[^=]*=//'`
                      valueFound="true"
        esac
        if [ "$valueFound" == "true" ]; then break; fi
    done
    value=`echo $value | sed -e "s/:SP:/ /g"`
}

put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"

get "newMap" "company"
echo $value

get "newMap" "name"
echo $value

編集:すべてのキーを取得する別のメソッドを追加しました。

getKeySet() {
    if [ "$#" != 1 ]; 
    then 
        exit 1; 
    fi

    mapName=$1; 

    eval map="\"\$$mapName\""

    keySet=`
           echo $map | 
           sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
          `
}

1
あなたはしているeval「ことのbashのコードかのようにデータをINGの、そしてより多くの何:あなたはそれを正しく引用することはできません。どちらも、大量のバグと任意のコードインジェクションを引き起こします。
lhunath

3

Bash 3には、素晴らしくシンプルな解決策がある特別なケースがあります。

多くの変数を処理したくない場合、またはキーが単に無効な変数識別子であり、配列のアイテム数が256未満であることが保証されている場合は、関数の戻り値を悪用できます。このソリューションでは、値を変数としてすぐに利用できるため、サブシェルは必要ありません。また、パフォーマンスを向上させるための反復も必要ありません。また、Bash 4バージョンとほぼ同じように、非常に読みやすくなっています。

最も基本的なバージョンは次のとおりです。

hash_index() {
    case $1 in
        'foo') return 0;;
        'bar') return 1;;
        'baz') return 2;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo"
echo ${hash_vals[$?]}

で、単一引用符を使用することを忘れないでくださいcase。そうしないと、グロビングの影響を受けやすくなります。最初から静的/凍結されたハッシュに本当に便利ですが、hash_keys=()配列からインデックスジェネレータを作成することもできます。

注意してください、それはデフォルトで最初のものになっているので、あなたはゼロ番目の要素を取っておきたいかもしれません:

hash_index() {
    case $1 in
        'foo') return 1;;
        'bar') return 2;;
        'baz') return 3;;
    esac
}

hash_vals=("",           # sort of like returning null/nil for a non existent key
           "foo_val"
           "bar_val"
           "baz_val");

hash_index "foo" || echo ${hash_vals[$?]}  # It can't get more readable than this

警告:長さが正しくありません。

または、ゼロから始まるインデックスを保持したい場合は、別のインデックス値を予約して、存在しないキーから保護できますが、読みにくくなります。

hash_index() {
    case $1 in
        'foo') return 0;;
        'bar') return 1;;
        'baz') return 2;;
        *)   return 255;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo"
[[ $? -ne 255 ]] && echo ${hash_vals[$?]}

または、長さを正しく保つために、インデックスを1つオフセットします。

hash_index() {
    case $1 in
        'foo') return 1;;
        'bar') return 2;;
        'baz') return 3;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo" || echo ${hash_vals[$(($? - 1))]}

2

動的変数名を使用して、変数名をハッシュマップのキーのように機能させることができます。

たとえば、次の例のように、名前、クレジットの2つの列を持つ入力ファイルがあり、各ユーザーの収入を合計したい場合は、次のようにします。

Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100

以下のコマンドは、動的変数をキーとして使用して、map _ $ {person}の形式ですべてを合計します。

while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)

結果を読むには:

set | grep map

出力は次のようになります。

map_David=100
map_John=500
map_Mary=150
map_Paul=500

これらのテクニックについて詳しく説明し、GitHubで、 について詳しく説明 HashMapオブジェクトshell_mapと

HashMapインスタンスを作成するには」、shell_map関数は異なる名前で自身のコピーを作成できます。新しい関数のコピーごとに、$ FUNCNAME変数が異なります。次に$ FUNCNAMEを使用して、各Mapインスタンスの名前空間を作成します。

マップキーは$ FUNCNAME_DATA_ $ KEYの形式のグローバル変数です。$ KEYはマップに追加されたキーです。これらの変数は動的変数です

例として使用できるように、簡略化したバージョンを配置します。

#!/bin/bash

shell_map () {
    local METHOD="$1"

    case $METHOD in
    new)
        local NEW_MAP="$2"

        # loads shell_map function declaration
        test -n "$(declare -f shell_map)" || return

        # declares in the Global Scope a copy of shell_map, under a new name.
        eval "${_/shell_map/$2}"
    ;;
    put)
        local KEY="$2"  
        local VALUE="$3"

        # declares a variable in the global scope
        eval ${FUNCNAME}_DATA_${KEY}='$VALUE'
    ;;
    get)
        local KEY="$2"
        local VALUE="${FUNCNAME}_DATA_${KEY}"
        echo "${!VALUE}"
    ;;
    keys)
        declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))"
    ;;
    name)
        echo $FUNCNAME
    ;;
    contains_key)
        local KEY="$2"
        compgen -v ${FUNCNAME}_DATA_${KEY} > /dev/null && return 0 || return 1
    ;;
    clear_all)
        while read var; do  
            unset $var
        done < <(compgen -v ${FUNCNAME}_DATA_)
    ;;
    remove)
        local KEY="$2"
        unset ${FUNCNAME}_DATA_${KEY}
    ;;
    size)
        compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l
    ;;
    *)
        echo "unsupported operation '$1'."
        return 1
    ;;
    esac
}

使用法:

shell_map new credit
credit put Mary 100
credit put John 200
for customer in `credit keys`; do 
    value=`credit get $customer`       
    echo "customer $customer has $value"
done
credit contains_key "Mary" && echo "Mary has credit!"

2

さらに別の非bash-4(つまり、bash 3、Mac互換)の方法:

val_of_key() {
    case $1 in
        'A1') echo 'aaa';;
        'B2') echo 'bbb';;
        'C3') echo 'ccc';;
        *) echo 'zzz';;
    esac
}

for x in 'A1' 'B2' 'C3' 'D4'; do
    y=$(val_of_key "$x")
    echo "$x => $y"
done

プリント:

A1 => aaa
B2 => bbb
C3 => ccc
D4 => zzz

関数caseは連想配列のように機能します。残念ながら、それを使用することはできないためreturnechoその出力を使用する必要がありますが、サブシェルの分岐を回避する純粋主義者でない限り、これは問題にはなりません。


1

私が以前に質問を見なかったなんて残念です- 特にマップ(連想配列)を含むライブラリシェルフレームワークを書きました。それの最後のバージョンはここにあります

例:

#!/bin/bash 
#include map library
shF_PATH_TO_LIB="/usr/lib/shell-framework"
source "${shF_PATH_TO_LIB}/map"

#simple example get/put
putMapValue "mapName" "mapKey1" "map Value 2"
echo "mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"

#redefine old value to new
putMapValue "mapName" "mapKey1" "map Value 1"
echo "after change mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"

#add two new pairs key/values and print all keys
putMapValue "mapName" "mapKey2" "map Value 2"
putMapValue "mapName" "mapKey3" "map Value 3"
echo -e "mapName keys are \n$(getMapKeys "mapName")"

#create new map
putMapValue "subMapName" "subMapKey1" "sub map Value 1"
putMapValue "subMapName" "subMapKey2" "sub map Value 2"

#and put it in mapName under key "mapKey4"
putMapValue "mapName" "mapKey4" "subMapName"

#check if under two key were placed maps
echo "is map mapName[mapKey3]? - $(if isMap "$(getMapValue "mapName" "mapKey3")" ; then echo Yes; else echo No; fi)"
echo "is map mapName[mapKey4]? - $(if isMap "$(getMapValue "mapName" "mapKey4")" ; then echo Yes; else echo No; fi)"

#print map with sub maps
printf "%s\n" "$(mapToString "mapName")"

1

jqが利用可能な場合は、別のオプションを追加します。

export NAMES="{
  \"Mary\":\"100\",
  \"John\":\"200\",
  \"Mary\":\"50\",
  \"John\":\"300\",
  \"Paul\":\"100\",
  \"Paul\":\"400\",
  \"David\":\"100\"
}"
export NAME=David
echo $NAMES | jq --arg v "$NAME" '.[$v]' | tr -d '"' 

0

すでに述べたように、キー/値をファイルに書き込み、grep / awkを使用してそれらを取得するのが最善の方法であることが本当であることがわかりました。あらゆる種類の不要なIOのように聞こえますが、ディスクキャッシュが作動して非常に効率的になります-ベンチマークが示すように、上記の方法のいずれかを使用してメモリに格納するよりもはるかに高速です。

ここに私が好きな迅速でクリーンな方法があります:

hinit() {
    rm -f /tmp/hashmap.$1
}

hput() {
    echo "$2 $3" >> /tmp/hashmap.$1
}

hget() {
    grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };'
}

hinit capitols
hput capitols France Paris
hput capitols Netherlands Amsterdam
hput capitols Spain Madrid

echo `hget capitols France` and `hget capitols Netherlands` and `hget capitols Spain`

キーごとに単一の値を適用したい場合は、hput()で小さなgrep / sedアクションを実行することもできます。


0

数年前、他の機能(ロギング、構成ファイル、コマンドライン引数の拡張サポート、ヘルプの生成、単体テストなど)の中で連想配列をサポートするbashのスクリプトライブラリを書きました。ライブラリには連想配列のラッパーが含まれており、適切なモデルに自動的に切り替わります(bash4の内部および以前のバージョンのエミュレート)。これはシェルフレームワークと呼ばれ、origo.ethz.chでホストされていましたが、現在、リソースはクローズされています。それでも誰かがそれを必要とするなら、私はあなたとそれを共有することができます。


githubにそれを貼り付ける価値があるかもしれません
Mark K Cowan

0

シェルにはデータ構造のような組み込みのマップはありません。そのようなアイテムを説明するために生の文字列を使用します。

ARRAY=(
    "item_A|attr1|attr2|attr3"
    "item_B|attr1|attr2|attr3"
    "..."
)

アイテムとその属性を抽出する場合:

for item in "${ARRAY[@]}"
do
    item_name=$(echo "${item}"|awk -F "|" '{print $1}')
    item_attr1=$(echo "${item}"|awk -F "|" '{print $2}')
    item_attr2=$(echo "${item}"|awk -F "|" '{print $3}')

    echo "${item_name}"
    echo "${item_attr1}"
    echo "${item_attr2}"
done

これは他の人の答えほど賢くはないように見えますが、新しい人が砲撃するために理解しやすいです。


-1

Vadimのソリューションを次のように変更しました。

####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
    alias "${1}$2"="$3"
}

# map_get map_name key
# @return value
#
function map_get {
    if type -p "${1}$2"
        then
            alias "${1}$2" | awk -F "'" '{ print $2; }';
    fi
}

# map_keys map_name 
# @return map keys
#
function map_keys
{
    alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}

存在しないキーをリクエストした場合にエラーが返されないようにmap_getを変更しますが、副作用として、欠落しているマップも暗黙的に無視しますが、これは、ループ内のアイテムをスキップするためにキーをチェックしたかったのです。


-1

遅い返信ですが、この方法で問題に対処することを検討してください。次のufwファイアウォールスクリプトのコードスニペット内に示されているbash組み込み読み取りを使用してください。このアプローチには、2つだけでなく、区切られたフィールドセットを必要なだけ使用できるという利点があります。|を使用しました ポート範囲指定子がコロンを必要とする場合があるため、区切り文字、つまり6001:6010

#!/usr/bin/env bash

readonly connections=(       
                            '192.168.1.4/24|tcp|22'
                            '192.168.1.4/24|tcp|53'
                            '192.168.1.4/24|tcp|80'
                            '192.168.1.4/24|tcp|139'
                            '192.168.1.4/24|tcp|443'
                            '192.168.1.4/24|tcp|445'
                            '192.168.1.4/24|tcp|631'
                            '192.168.1.4/24|tcp|5901'
                            '192.168.1.4/24|tcp|6566'
)

function set_connections(){
    local range proto port
    for fields in ${connections[@]}
    do
            IFS=$'|' read -r range proto port <<< "$fields"
            ufw allow from "$range" proto "$proto" to any port "$port"
    done
}

set_connections
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.