フォルダーを抽出し、ファイル名をキーと値に分割して連想配列に保存するにはどうすればよいですか?

フォルダーを抽出し、ファイル名をキーと値に分割して連想配列に保存するにはどうすればよいですか?

タイトルの問題を解決するはずの次のスクリプトがありますが、明らかにキーに値を割り当てることができません。原因は偶発的なエラーですか、それともスクリプトに重大な間違いがあるのでしょうか?

フォルダ名はgemファイルの名前です。gem-file-foo-1.2.3

この例では、キーはgem-file-foo、値はバージョン番号、1.2.3または同じ gem に複数のバージョンがある場合は複数のバージョン番号の文字列になります。

echo "${!my_gems[@]}"...のキーは出力されません。なぜだめですか?

#!/bin/bash

directory=$GEM_HOME/gems
declare -A my_gems

get_gemset_versions () {

last_key=""
values=""

FIND_RESULTS=$(find $directory -maxdepth 1 -type d -regextype posix-extended -regex "^${directory}\/[a-zA-Z0-9]+([-_]?[a-zA-Z0-9]+)*-[0-9]{1,3}(.[0-9]{1,3}){,3}\$")

 printf "%s\n" $FIND_RESULTS | sort | 
  while read -r line; do
    line=${line##*/}
            
    KEY="${line%-*}"
            
    VALUE="${line##*-}"
  
        if [[ $last_key -eq "" ]]; then
            last_key=$KEY
        fi
        
        if [[ $last_key -eq $KEY ]]; then
            values="$values ${VALUE}"
        else
            values="${VALUE}"
            last_key=$KEY
        fi
        
        my_gems[$KEY]=$values
    done
 
    echo "${!my_gems[@]}"

 }


get_gemset_versions

$last_keyまた、等しい gem パッケージをまとめるためのおよびのロジック$keyは間違っているようです。これは必ずしも質問の一部ではありませんが、ここで私が誤ったロジックを適用している場合は指摘していただけると幸いです。

ありがとう

答え1

あなたが持っている:

printf "%s\n" $FIND_RESULTS | sort | 
  while read -r line; do
    ...
    done
 
    echo "${!my_gems[@]}"

ここで、インデントに関係なく、はechoパイプラインの外側にあります。Bash はデフォルトでパイプラインのすべての部分をサブシェルで実行するので、whileループ内の割り当てはパイプラインの終了後には見えません。Shellcheck.net もこれについて警告しています。

Line 32:
        my_gems[$KEY]=$values
        ^-- SC2030: Modification of my_gems is local (to subshell caused by pipeline).

残念ながら回避策は示されていません。

Bash では、lastpipeオプションを有効にするか、パイプをプロセス置換に置き換えることができます。

shopt -s lastpipe
echo test | while read line; do
    out=$line
  done
echo "out=$out"

または

while read line; do
  out=$line
done < <(echo test)
echo "out=$out"

lastpipeジョブ制御に結びついているため、対話型シェルで試してもおそらく動作しないでしょう。ない有効になっています。

見る:変数が 1 つの 'while read' ループではローカルであるのに、他の類似したループではローカルではないのはなぜですか?


いずれにせよ、これは少し奇妙に思えます:

FIND_RESULTS=$(find ...)

 printf "%s\n" $FIND_RESULTS 

findファイル名は改行で区切られて出力されますが、ファイル名に改行が含まれていないことが分かっている限りは問題ありません。ただし、ここでは、変数を介したラウンドトリップと引用符なしの展開からの単語分割によって、スペースを含むファイル名も分割されます。

直接実行することもできますfind ... | while ...。またはwhile ...; done < <(find...)

while IFS= read -r line; doまた、先頭と末尾の空白文字が分割されないようにするには、ほとんどの場合、 , を使用する必要があることに注意してくださいread。ファイル名にも空白文字が含まれていないことを願いますが、いずれにしても。

今のところ、適切な参考質問が見つかりませんが、これはIFS空白文字が含まれている場合に特有のものです。他の先頭と末尾の区切り文字は、 を 1 つのフィールドだけに使用しても削除されませんread。たとえば、はリテラルのIFS=": " read -r foo <<< "::foobar "ままです。コロンは保持されますが、末尾のスペースは削除されます。foo::foobar

関連情報