Wie extrahiere ich Ordner, teile Dateinamen in Schlüssel und Werte auf und speichere sie in einem assoziativen Array?

Wie extrahiere ich Ordner, teile Dateinamen in Schlüssel und Werte auf und speichere sie in einem assoziativen Array?

Ich habe folgendes Skript, welches das Problem aus dem Titel lösen soll, aber offensichtlich funktioniert es nicht, die Werte den Schlüsseln zuzuweisen. Ist der Grund ein versehentlicher Fehler oder liegt ein wesentlicher Fehler im Skript vor?

Die Ordnernamen sind Namen von Gem-Dateien wiegem-file-foo-1.2.3

Der Schlüssel soll gem-file-fooin diesem Beispiel sein und der/die Wert(e) die Versionsnummer 1.2.3oder eine Zeichenfolge mit mehreren Versionsnummern, wenn mehrere Versionen desselben Gems vorhanden sind.

Es werden keine Schlüssel mit echo "${!my_gems[@]}"... ausgegeben.Warum nicht?

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

Außerdem scheint die Logik mit $last_keyund $keyzum Zusammenfassen gleicher Gem-Pakete fehlerhaft zu sein. Dies ist nicht unbedingt Teil der Frage, aber es wäre nett, wenn Sie mich darauf hinweisen würden, ob ich hier fehlerhafte Logiken anwende.

Danke

Antwort1

Du hast:

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

wobei sich das unabhängig von der Einrückung echoaußerhalb der Pipeline befindet. Bash führt standardmäßig alle Teile einer Pipeline in Subshells aus, sodass die Zuweisungen innerhalb der whileSchleife nach dem Ende der Pipeline nicht mehr sichtbar sind. Shellcheck.net warnt auch davor:

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

Leider gibt es keine Workarounds.

In Bash können Sie entweder die lastpipeOption aktivieren oder das Pipe-Zeichen durch eine Prozessersetzung ersetzen:

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

oder

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

( lastpipefunktioniert wahrscheinlich nicht, wenn Sie es in einer interaktiven Shell versuchen, da es an die Jobsteuerung gebunden istnichtaktiviert wird.)

Sehen:Warum ist meine Variable in einer „while read“-Schleife lokal, aber nicht in einer anderen, scheinbar ähnlichen Schleife?


Das hier erscheint mir jedenfalls etwas merkwürdig:

FIND_RESULTS=$(find ...)

 printf "%s\n" $FIND_RESULTS 

findgibt Dateinamen durch Zeilenumbrüche getrennt aus, was in Ordnung ist, solange Sie wissen, dass keine Dateinamen welche enthalten. Aber hier trennt der Roundtrip durch die Variable und die Worttrennung aus der nicht in Anführungszeichen gesetzten Erweiterung auch alle Dateinamen mit Leerzeichen.

Du könntest einfach find ... | while ...direkt losrennen. Oder while ...; done < <(find...)

Beachten Sie auch, dass Sie fast immer verwenden möchten while IFS= read -r line; do, um zu verhindern, readdass führende und abschließende Leerzeichen beschädigt werden. Nun, ich hoffe, Ihre Dateinamen enthalten diese auch nicht, aber auf jeden Fall.

Ich kann im Moment keine gute Referenzfrage finden, aber das bezieht sich speziell auf IFSenthaltene Leerzeichen. Andere führende und nachfolgende Trennzeichen werden bei einem readauf nur ein Feld nicht entfernt. Beispielsweise IFS=": " read -r foo <<< "::foobar "verlässt foomit dem Literal ::foobar. Die Doppelpunkte bleiben erhalten, aber die nachfolgenden Leerzeichen sind weg.

verwandte Informationen