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-foo
in diesem Beispiel sein und der/die Wert(e) die Versionsnummer 1.2.3
oder 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_key
und $key
zum 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 echo
außerhalb der Pipeline befindet. Bash führt standardmäßig alle Teile einer Pipeline in Subshells aus, sodass die Zuweisungen innerhalb der while
Schleife 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 lastpipe
Option 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"
( lastpipe
funktioniert wahrscheinlich nicht, wenn Sie es in einer interaktiven Shell versuchen, da es an die Jobsteuerung gebunden istnichtaktiviert wird.)
Das hier erscheint mir jedenfalls etwas merkwürdig:
FIND_RESULTS=$(find ...)
printf "%s\n" $FIND_RESULTS
find
gibt 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, read
dass 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 IFS
enthaltene Leerzeichen. Andere führende und nachfolgende Trennzeichen werden bei einem read
auf nur ein Feld nicht entfernt. Beispielsweise IFS=": " read -r foo <<< "::foobar "
verlässt foo
mit dem Literal ::foobar
. Die Doppelpunkte bleiben erhalten, aber die nachfolgenden Leerzeichen sind weg.