リストの項目が期待どおりに機能しないことを確認する Bash

リストの項目が期待どおりに機能しないことを確認する Bash

以下に基づく条件を持つスクリプトを作成しようとしています。変数がリストに表示されます:

#!/bin/bash

LIST=`ls`

function listcontains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && return 0 || return 1
}

if [ $(listcontains "${LIST}" "multi.sh") ] ; then
     echo "Found!"
else
    echo "Failed :("
fi

$(listcontains "${LIST}" "multi.sh")
echo returned $?

リストには「multi.sh」というファイルがあるため、「Found!」が予想されますが、上記のスクリプトでは「Failed:(」を報告します。後続の呼び出しは0を返します。

頑張った

if [ 0 -eq $(listcontains "${LIST}" "multi.sh") ] ; then

ところで、エラーが発生します。

./script.sh: 行9: [: 0: 単項演算子が必要

失敗:(

私がここで何を見逃しているのでしょうか?

答え1

if [ $(listcontains "${LIST}" "multi.sh") ] ; then

関数は何も印刷しないので、コマンド置換はトークン化後にフィールドを生成しません。これは実行と同じで、if [ ]; then ...間にパラメータがない場合はfalse状態を返します。[][

引用符を使用してこれを行うと、空の文字列をlikeif [ "$(listcontains...)" ]; thenに渡し、false状態も返すので役に立ちません。 (括弧の間にパラメータがあり、そのパラメータがnullでないことを確認してください。)[[ "" ]

コマンド置換自体の終了ステータスは、実行する他のコマンドがない場合にのみ表示されます。$(listcontains ...)これは、後で1行でコマンドを単独で実行する場合です。

コマンドオーバーライドでテストするには、何かを印刷する必要があります。例えば

listcontains() {
    if ...; then
        echo yes
    fi # else print nothing
}
if [ "$(listcontains ...)" ]; then
    echo ok
fi

(またはと共にif [ "$(listcontains ...)" = yes ]; then ...

ただし、終了ステータスを直接表示できるため、これは必要ありません。

listcontains() {
    if [[ ... ]]; then
        return 0
    fi
    return 1
}
if listcontains ...; then
    echo ok
fi

関数の終了状態は最後のコマンドの終了状態なので、関数を次のように単純化できます。

listcontains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]]
}

$2ただし、内容に正規表現の特殊文字が含まれていても、内容が文字通り処理されるように引用したいと思います。

たとえば、上記の場合は単一の文字と一致するため、listcontains 'foo matchxsh bar' match.sh一致が検索されます。.一致しない括弧などの問題が発生する可能性があります。

一致する前に文字列の先頭と末尾にスペースを入れることで長さを短くすることもできます。

listcontains() {
    [[ " $1 " =~ [[:space:]]"$2"[[:space:]] ]]
}

または、より多くのPOSIXlyを使用してDashでも使用できます。

listcontains() {
    case " $1 " in
        *[[:space:]]"$2"[[:space:]]*) return 0;;
        *) return 1;;
    esac
}

答え2

アイテムがリストにあるかどうかを確認する最も簡単な方法の1つは、リストを関連配列、つまり「ハッシュ」(アイテムはキーと任意の値です)に変換し、必要なアイテムがあるかどうかをテストすることです。配列のインデックスです。

私は通常、各キーの値として「0」または「1」を使用します。時には空の文字列と空でない文字列をテストすることもあります。それは主に私が使用している言語とそれが正しいことを確認することに依存します。

効果的には、連想配列を単純な方法で使用することによって達成される。置く集合メンバーシップをテストします(キーに値がある場合はメンバー、それ以外の場合はメンバーではありません)。したがって、何をテストし、どのようにテストするかを知る限り、値は重要ではありません。

正規表現のマッチングは不要で、簡単なテストを行うだけです。私が探しているものは連想配列のキーですか?

コレクションメンバーシップテストも高速です。ワンタイムテストではパフォーマンスは重要ではありませんが、多数の潜在的なコレクションメンバーをテストする場合はパフォーマンスが重要です。これは、シェルなどの非常に遅い言語の場合に特に当てはまります。

以下は、インデックス配列に含まれるリストを使用する例です。

$ items=(item1 item2 item3 item4)

$ declare -A itemhash
$ for i in "${items[@]}" ; do itemhash[$i]=1 ; done

現在のインデックス配列と関連配列に含まれる内容は次のとおりです。

$ declare -p items itemhash
declare -a items=([0]="item1" [1]="item2" [2]="item3" [3]="item4")
declare -A itemhash=([item1]="1" [item2]="1" [item3]="1" [item4]="1" )

いいですね。ハッシュ(関連配列)が埋められました。ここで特定の項目が含まれているかどうかをテストできます。

$ if [ "${itemhash[item1]}" == 1 ] ; then echo in array ; else echo not in array ; fi
in array

$ if [ "${itemhash[item5]}" == 1 ] ; then echo in array ; else echo not in array ; fi
not in array

この方法は、エントリのソース(インデックス配列、ファイル名リスト、データベースクエリ出力など)に関係なく、ほとんどすべてのリストで機能し、ハッシュがいっぱいになる方法は重要ではありません。ハッシュのキーは項目の名前でなければならず、各キーの値は簡単にテストできるものでなければなりません。


大きなノイズが発生する可能性があるため、ファイル名に関連する例を使用しないでくださいls。ただし、ここでテストされている現在のディレクトリのファイル名のリストに "multi.sh"ファイル名を使用する別の例があります。

まず、multi.sh現在のディレクトリが存在しない場合:

$ declare -A foo
$ while read -d '' -r f; do foo[$f]=1 ; done < <(printf '%s\0' *)
$ if [ "${foo[multi.sh]}" == 1 ] ; then echo in array ; else echo not in array ; fi
not in array

その後、作成してmulti.shもう一度やり直してください。

$ unset foo ; declare -A foo
$ touch multi.sh
$ while read -d '' -r f; do foo[$f]=1 ; done < <(printf '%s\0' *)
$ if [ "${foo[multi.sh]}" == 1 ] ; then echo in array ; else echo not in array ; fi
in array

printf '%s\0' *注:代わりに使用しましたls解析された出力lsは悪い考えです。。 NULで区切られたファイル名のリストを読むことは、有効なファイル名、改行文字などの迷惑な文字を含むファイル名でも機能します。

ただし、最新バージョンのGNUにはNULで区切られた出力オプションがlsあります--zero。私はまだそれを使用する意図はありません。むしろ上記printf ... *またはfind

関連情報