Como extrair pastas, dividir nomes de arquivos em chave e valor(es) e salvá-los em uma matriz associativa?

Como extrair pastas, dividir nomes de arquivos em chave e valor(es) e salvá-los em uma matriz associativa?

Eu tenho o seguinte script que deveria resolver o problema do título, mas obviamente não está funcionando para atribuir os valores às chaves. O motivo é um erro acidental ou há um erro substancial no script?

Os nomes das pastas são nomes de arquivos gem comogem-file-foo-1.2.3

A chave deve estar gem-file-fooneste exemplo e o(s) valor(es) é o número da versão 1.2.3ou uma sequência de vários números de versão se houver várias versões da mesma gema.

Ele não gera nenhuma chave com echo "${!my_gems[@]}"...por que não?

#!/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

Além disso, a lógica com $last_keye $keypara verão pacotes de gemas iguais parece estar com defeito. Isso não faz necessariamente parte da questão, mas seria bom se você apontasse se estou aplicando alguma lógica errada aqui.

Obrigado

Responder1

Você tem:

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

onde, independentemente do recuo, o echoestá fora do pipeline. O Bash executa todas as partes de um pipeline em subshells por padrão, portanto, as atribuições dentro do whileloop não ficam visíveis após o término do pipeline. Shellcheck.net também alerta sobre isso:

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

Infelizmente, isso não oferece soluções alternativas.

No Bash, você pode ativar a lastpipeopção ou substituir o pipe pela substituição do processo:

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

ou

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

( lastpipeprovavelmente não funcionará se você tentar em um shell interativo, pois está vinculado ao controle de tarefasnãosendo habilitado.)

Ver:Por que minha variável é local em um loop 'while read', mas não em outro loop aparentemente semelhante?


De qualquer forma, isso parece um pouco estranho:

FIND_RESULTS=$(find ...)

 printf "%s\n" $FIND_RESULTS 

findgera nomes de arquivos separados por novas linhas, o que é bom, desde que você saiba que nenhum nome de arquivo contém nenhum. Mas aqui, o percurso de ida e volta pela variável e a divisão de palavras da expansão sem aspas também divide quaisquer nomes de arquivos com espaços.

Você poderia simplesmente executar find ... | while ...diretamente. Ou while ...; done < <(find...).

Além disso, observe que você quase sempre deseja usar while IFS= read -r line; do, para evitar reada quebra de espaços em branco iniciais e finais. Bem, espero que seus nomes de arquivos também não os contenham, mas de qualquer forma.

Não consigo encontrar uma boa pergunta de referência no momento, mas isso é específico para IFSconter espaços em branco. Outros separadores iniciais e finais não são removidos com readapenas um campo. Por exemplo, IFS=": " read -r foo <<< "::foobar "sai foocom o literal ::foobar. Os dois pontos são mantidos, mas os espaços finais desaparecem.

informação relacionada