Horst Felder wrote:
> Hallo Bernd!
Isch 'eise abär gar nischd Bernd, signor.
> Bernd Hohmann <
bernd.hohma...@freihaendler.com> schrieb:
>> replace (kommt aus dem MySQL Utilities):
>>
>> replace '$kellert' $kellert '$gartent' $gartent -- temperatur.html
>
> Vielen Dank! Habe mich für diese Variante entschieden, da es
> ein Einzeiler ist und "replace" in meiner Standardinstallation drin
> ist...
Diese Lösung funktioniert natürlich _nicht_ (zuverlässig), denn wenn der
Variablenwert Leerzeichen, Tabulaturzeichen oder Zeilenumbrüche enthält,
dann wird das dem Leerzeichen folgende Wort als eigener Parameter für
replace(1) expandiert (per Default ist IFS=$' \t\n'). Im schlimmsten Fall
werden so mit
kellert="foo bar"
gartent="baz bla"
replace '$kellert' $kellert '$gartent' $gartent -- temperatur
sämtliche Vorkommen von `$kellert' durch `foo', von `bar' durch `$gartent'
und von `baz' durch `bla' ersetzt, und es gäbe nicht einmal eine
Fehlermeldung von replace(1), da Obiges äquivalent ist zu
replace '$kellert' 'foo' 'bar' '$gartent' 'baz' 'bla' -- temperatur
(Leerzeichen zur Verdeutlichung eingefügt). Korrekt wäre daher
replace '$kellert' "$kellert" '$gartent' "$gartent" -- temperatur
Allerdings gibt es in einer GNU-Standardinstallation wie der Deinigen¹ und
vermutlich in allen Unices (da in POSIX spezifiziert) zum Ersetzen von
Strings in der Eingabe oder in Dateien bereits das Tool sed(1) (Stream
Editor) aus den textutils. Zu Obigem äquivalent wäre daher
sed -ie '
s/\$kellert/'"$kellert"'/g;
s/\$gartent/'"$gartent"'/g
' temperatur
Jedoch ist hier zu beachten, dass der Variablenwert keine speziellen
Sequenzen wie etwa `\0' enthalten darf, da sonst dieser expandiert wird.
Umgekehrt kann man sich das natürlich auch zunutze machen, um z. B. an
Wörter bestimmte Zeichenfolgen anzuhängen:
bar='\0baz'
sed -e 's/foo/'"$bar"'/g'
hängt an alle `foo's `baz' an.
Mit GNU awk(1) – welches Du bereits benutzt hast – ist ebenfalls Ersetzung
möglich. Da man in der Shell nicht die Datei überschreiben kann, die man
gerade liest, kann man entweder mit einer temporären Datei arbeiten, oder
(wie im Beispiel) den originalen Datei-Inhalt in einer Variablen speichern
(siehe `man bash', Abschnitt "Command Substitution" und natürlich `man
awk'):
# save file content in variable
content=$(cat temperatur)
# replace template variables
content=$(printf '%s' "$content" |
awk -v "kellert=$kellert" -v "gartent=$gartent" '
{
gsub("\\$kellert", kellert, $0);
gsub("\\$gartent", gartent, $0);
print;
}
')
# update file
printf '%s' "$content" > temperatur
Ausserdem beherrscht die GNU bash spätestens seit Version 4.1-alpha
ebenfalls die unbedingte globale Textersetzung [1] (von der KornShell
übernommen [2]), so dass Du mit
content=$(cat temperatur)
content=${content//'$kellert'/$kellert}
content=${content//'$gartent'/$gartent}
printf '%s' "$content" > temperatur
– von cat abgesehen – ganz ohne weitere Tools und ohne Escaping auskommst
(siehe `man bash', Abschnitt "Parameter Expansion"). (Die Ersetzung mit
einer Schleife zu automatisieren ist mir noch nicht gelungen, da bash
${$var} nicht akzeptiert. Ideen?)
Schliesslich geht es – weniger performant als Obiges, dafür aber weniger
anfällig für Stringlängenbegrenzung – auch noch ohne cat(1):
printf '%s' "$(
IFS='' while read -r line
do
line=${line//'$kellert'/$kellert}
line=${line//'$gartent'/$gartent}
printf '%s\n' "$line"
done < temperatur
)" > temperatur
Weil die Schleife in einer Subshell ausgeführt wird, bevor die schreibende
Umleitung stattfindet, sollte es hier keine Race Condition geben (zuerst
muss das Ergebnis der Command Substitution ermittelt werden). In GNU bash,
version 4.2.20(1)-release (i486-pc-linux-gnu) funktioniert das jedenfalls.
[Weshalb IFS=''? `read' ist zum Einlesen tabellarischer Daten gedacht. Es
muss daher verhindert werden, dass Leerzeichen am Anfang ignoriert werden.
Durch Setzen des internen Feldtrenners (Internal Field Separator) auf die
leere Zeichenfolge wird die gesamte Zeile als ein Feld betrachtet und in
`line' gespeichert. (Mit `-r' wird ausserdem verhindert, dass `\' in der
Zeile als Escape-Zeichen interpretiert wird.)
Weshalb printf(1) und nicht echo(1)? echo(1) gibt immer einen Zeilenumbruch
am Ende aus und `echo -n' – was das verhindert – ist *nicht portabel*
(schlimmstenfalls wird `-n' mit ausgegeben). printf(1) hängt hingegen
nichts an und ist immer portabel; es ist in POSIX spezifiziert und gehört
auch zur GNU-Standardinstallation.
Weshalb nicht einfacher `printf "$content"'? Der Wert von `content' könnte
`%…' enthalten und es gibt keinen Parameter, um das zu expandieren. printf
'%s' gibt _nichts_ aus.]
_______
¹ siehe Headerfeld `X-Newsreader'
[1] <
http://tiswww.case.edu/php/chet/bash/CHANGES>
[2] <
http://www2.research.att.com/sw/download/man/man1/ksh.html>
--
PointedEars
Please do not Cc: me. / Bitte keine Kopien per E-Mail.