1. 概要

この記事では、Linuxシェルでの配列/リストの実装とその癖について説明します。

2. バッシュ対。 POSIXシェルアレイ

基本的なPOSIX準拠のシェルと比較して、bashアレイははるかに強力で使いやすいです。 インデックス付き配列を作成し、その3番目のメンバーにアクセスして、これを説明しましょう。

bashでは、(…)構文で配列を宣言します。

$ array=(item1 item2 item3)
$ echo "${array[2]}"
item3

POSIXシェルでは、setを使用して配列を宣言します。

$ set -- item1 item2 item3
$ echo "$3" # Arrays indices start from 1
item3

bashの配列の構文がはるかにクリーンであり、より複雑な操作での使用が容易になっていることがわかります。

3. 配列の作成

POSIXシェルの配列に対するファーストクラスのサポートはありません。 ただし、、位置パラメータのリストを配列として使用できます。

位置パラメータは、シェルスクリプト/関数に渡されるすべてのパラメータです。 たとえば、 my_function 1 2 3 では、my_functionに続く数字が位置パラメータです。

組み込みのsetを使用して配列を変更し、すべての位置パラメーターを表す $ @ 変数を介して配列にアクセスできます。つまり、配列は次のとおりです。

$ set -- 1 2 3
$ echo [email protected]
1 2 3

オプションの終わりをダブルダッシュでマークします。

4. 基本的な配列操作

次に、実行できるさまざまな配列操作を見てみましょう。

4.1. アイテムの追加

アイテムを追加するには、元の配列を新しいアイテムとともにsetに渡すだけです。

$ set -- 1 2 3
$ echo "[email protected]" # Original array
1 2 3
$ set -- "[email protected]" 4
$ echo "[email protected]" # New array
1 2 3 4

ここで、“ $ @” は、元のすべての位置パラメーター、つまり配列の項目に展開されます。

4.2. アイテムの削除

配列の先頭から複数のアイテムを簡単に削除できますが、任意の削除には注意が必要です。

要素を削除するには、組み込みのshiftを使用して、削除するアイテムの数を渡します。

$ set -- 1 2 3
$ shift 2 # Remove first 2 items
$ echo "[email protected]"
3

特定のインデックスのアイテムを直接削除する方法はないので、そのための関数を書いてみましょう。

# Argument 1: The index to remove
# Argument 2: The array
# Usage: set -- "$(array_remove N "[email protected]")"
array_remove() {
    index="$
    shift # Remove the index from argument list

    counter=1 # Array indexing starts from 1

    # Print elements upto the index, "-lt" means less than.
    while [ "$counter" -lt "$index" ]; do
        : $((counter+=1)) # Increment counter
        echo "$1" # First item of current array
        shift # Move to the next item
    done

    # Skip the element at the removal index, we've printed everything before it.
    shift

    # Print the rest of the array.
    echo "[email protected]"
}

それをテストしてみましょう:

$ set -- 1 2 3 4 5
$ set -- "$(array_remove 4 "[email protected]")" # Remove at 4th index
$ echo "[email protected]"
1 2 3 5

配列全体を指定されたインデックスまでトラバースするため、この方法は非効率的であることに注意してください。

4.3. インデックス作成

変数${N} を使用して配列にインデックスを付けることができます。ここで、Nは必要なインデックスです。

$ set -- 3 2 1
$ echo "${3}"
1

1桁を超えるインデックスを許可するには、数値を中括弧で囲む必要があります。たとえば、シェルは「 $98」を文字列「8」と評価する場合があります。 「$9」変数の値に「」が追加されます。 「${99} 」は、この動作を防ぎます。

ただし、インデックスを環境変数に格納する場合は、evalを使用する必要があります。

$ set -- one two three
$ index=3
$ eval "echo \${${index}}"
three

eval を回避するために、配列を取り込んで N 要素をスキップし、最初の要素を出力する関数を作成できます。

# Argument 1: The index
# Argument 2: The array
array_index() {
    shift "$1" # Shift N number of elements, including the first argument

    # Return non-zero if index is out of bounds ($1 will be empty)
    echo "${1:?Index out of bounds}" # Print the first item after shifting
}

それを実行してみましょう:

$ set -- 0 1 2 3 4 5 6 7 8 9 10
$ array_index 12 "[email protected]"
/bin/sh: 1: Index out of bounds
$ array_index 11 "[email protected]"
10

4.4. 反復

for を使用して、配列を反復処理します。

$ set -- 1 2 3
$ for item in "[email protected]"; do echo "$item"; done
1
2
3

ここでは、「[email protected]はオプションです。 アイテムのを使用して、位置パラメータを反復処理できます。 行う …; 「[email protected]」にもなしでを実行しました。

4.5. コマンドからの配列の生成

set に引数としてサブシェルコマンドを渡して、配列を生成することもできます。 100個の整数の配列が必要だとします。

$ set -- $(seq 100)
$ echo "[email protected]"
1 2 3 4 5 6 7 8 9 10 11 12 ...

seq コマンドは、指定された範囲の数値を生成します。

5. 連想配列/ハッシュマップ

シェルでハッシュマップが必要になった場合は、Pythonなどのより強力な言語の使用を検討する必要があります。

その間 ファイルを使用してそれらを実装し、関数を介してそれらと対話することは依然として可能であり、ネストされたキーのようなより複雑な操作はクリーンに実装できません。

さらに、システムがデータを読み取るたびに新しいファイル記述子を作成する必要があるため、新しいキーをフェッチまたは作成すると、待ち時間が大幅に長くなります。

5.1. 実装

実装では、ファイル名をハッシュキーとして使用し、その内容を値として使用します。

また、ファイル名としてキー文字列を使用するだけでなく、キーのチェックサムも取得します。 これにより、ファイル名の長さ制限をバイパスできるだけでなく、名前に余分なスラッシュを含めることも回避できます。 たとえば、「 filewith / slash 」という名前のファイルを作成すると、スラッシュでディレクトリが区切られるため、無効になります。

ハッシュテーブルディレクトリ自体は、mktempで作成されます。

hm_create() {
    # Create a temporary directory and return it's name
    mktemp -d
}

# Lazy hash function that just generates a checksum.
# Feel free to replace this with a more secure checksum like sha256.
hm_hash() {
    echo "$1" | md5sum -
}

# Argument 1: Hash Table
# Argument 2: Key
# Argument 3: Value
hm_put() {
    echo "$3" > "$1/$(hm_hash "$2")"
}

# Argument 1: Hash Table
# Argument 2: Key
hm_delete() {
    rm -f "$1/$(hm_hash "$2")"
}

# Argument 1: Hash Table
# Argument 2: Key
hm_get() {
    cat "$1/$(hm_hash "$2")"
}

5.2. 使用法

いくつかのキーを使用してハッシュテーブルを作成し、それらを出力してみましょう。

$ hm="$(hm_create)"
$ echo "Created hashmap "$hm""
Created hashmap /tmp/tmp.K6Kuuv
$ hm_put "$hm" mykey myvalue
$ hm_put "$hm" hash table
$ hm_get "$hm" hash
table
$ hm_get "$hm" mykey
myvalue
$ hm_delete "$hm" hash
$ hm_get "$hm" hash # Deleted key "hash" doesn't exist, will raise an error.
cat: can't open '/tmp/tmp.K6Kuuv/4e76434eea3c9d9cf9cb10bbf3f4a74b  -': No such file or directory

次に、 $hmディレクトリにある単純なrm-rfを使用してハッシュテーブル全体を削除できます。

6. 結論

この記事では、bash配列とPOSIX配列の違いとその使用法について学びました。 また、実装から、ハッシュマップなどのより複雑なデータ構造の場合は、使いやすさと堅牢性のために、Pythonなどの強力で高レベルのスクリプト言語を使用する方がよいと結論付けることができます。