• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

Bash string global substitution

Kann mir das bitte jemand erklären

Code:
STR="A:<> B:<x> C:<xy>"
SBS="Hallo"

echo "$STR"
echo ${STR//<?>/<$SBS>}	# ok
echo ${STR//<*>/<$SBS>}	# BUG?

A:<> B:<x> C:<xy>
A:<> B:<Hallo> C:<xy>
A:<Hallo>

Danke
 

marce

Guru
Auf den ersten Blick ohne tiefer Dokustudien:
<?> matcht genau das <x> - damit wird das x ersetzt.
<*> matcht "> B:<x> C:<xy" - damit wird der komplette Rest des Strings ersetzt.
 
OP
Gräfin Klara

Gräfin Klara

Hacker
marce schrieb:
Auf den ersten Blick ohne tiefer Dokustudien:
<?> matcht genau das <x> - damit wird das x ersetzt.
<*> matcht "> B:<x> C:<xy" - damit wird der komplette Rest des Strings ersetzt.

Damit hast du mit hoher Wahrschelinlichkeit Recht.
Aber wie erreiche ich das?:
A:<Hallo> B:<Hallo> C:<Hallo>

Ich dachte,
echo ${STR/<*>/<$SBS>} # Abbruch nach erstem match
echo ${STR//<*>/<$SBS>} # alle matches und nicht gesamter string

Muß wohl so sein wie du schreibst
 

marce

Guru
naja, mit <?> sagst Du ja, daß zwischen dem > und dem > ein Zeichen stehen muss - und damit matcht nur <x>, <> und <xy> aber nicht.

Wo ich so gerade drüber nachdenke - eigentlich müsste es .? oder .* sein anstatt ? oder * - kommt aber ggf. darauf an, auf welche Kompatiblitäts-Form die RegEx-Engine eingestellt ist. *, + und ? sind eigentlich nur Multiplikatoren auf die eigentliche RegEx, welche dann . (also "Any Char") wäre. Siehe auch http://tldp.org/LDP/abs/html/x17129.html
 
OP
Gräfin Klara

Gräfin Klara

Hacker
marce schrieb:
naja, mit <?> sagst Du ja, daß zwischen dem > und dem > ein Zeichen stehen muss - und damit matcht nur <x>, <> und <xy> aber nicht.

Wo ich so gerade drüber nachdenke - eigentlich müsste es .? oder .* sein anstatt ? oder * - kommt aber ggf. darauf an, auf welche Kompatiblitäts-Form die RegEx-Engine eingestellt ist. *, + und ? sind eigentlich nur Multiplikatoren auf die eigentliche RegEx, welche dann . (also "Any Char") wäre. Siehe auch http://tldp.org/LDP/abs/html/x17129.html

Wenn es regex wäre, dann hättest du Recht, aber .. Bash kocht intern seine eigene Suppe.
Bash ? = single char (wie in regex dot .)
Bash * = chars null bis x (wie in regex .*)
Bash dot . gibt es nicht

RegEx-Engine einstellen:
# shopt -s extglob
ändert nichts für bash intener, eigener regexp

Verwendet man in bash ein externes tool wie z.B. sed, (unterstützt natürlich posix regexp) dann ist die Lösung einfach.
Ich will aber eine reine Bash_Lösung! Heute ist Sonntag und es regnet ...

Gruß
 

uhelp

Member
Bash ist kein Ramsch.
Und die RegExes sind ganz normal. Kommen aber nur beim Matchingoperator =~ zur Anwendung.
Fast ganz normal.
Wie sagte mal jemand so treffend: Unix ist ein Hochhaus auf dessen Etagen je fünf verschiedene RegExFamilien wohnen. Oder so.

In der Parameter Expansion führt die Bash aber die gleiche Expansion aus, wie sie es für Dateinamen macht.

Ein ? steht zwar in RegExes für ein "Vielleicht ein Zeichen",
also optional KANN da IRGENDEIN Zeichen stehen, oder halt auch keines.

Aber eben nicht bei der Filename Expansion. Dort steht es für genau ein beliebiges Zeichen.

Und der Stern steht -wie in fast jeder RegEx Variante für Null, oder beliebig viele Zeichen.
Und damit sieht der dieser RegEx: STR="A:<> B:<x> C:<xy>" und ersetzt das Rotgrüne mit <$SBS>
Genau, wie marce schon geschrieben hat.
Der Stern ist eben einfach greedy.
Deshalb tun beide Varianten das gleiche:
Sie ersetzen einmal den ganzen String.
Die erste Variante bricht nicht ab, sondern ist schlicht erledigt.
Und die zweiter Variante hat nach der ersten Ersetzung einfach nichts mehr was es ersetzen könnte.
Der zweite Slash steht einsam im Nichts.

Welches Problem wolltest du eigentlich lösen?
Du kannst auch Variablen als Search und Replacement verwenden.
Code:
s=something ; r=anotherthing
a="and now to something completly different"
echo ${a/$s/$r}

Aus der Manpage:
${PARAMETER/PATTERN/STRING}'
The PATTERN is expanded to produce a pattern just as in filename
expansion.
PARAMETER is expanded and the longest match of PATTERN
against its value is replaced with STRING. If PATTERN begins with
`/', all matches of PATTERN are replaced with STRING. Normally
only the first match is replaced. If PATTERN begins with `#', it
must match at the beginning of the expanded value of PARAMETER.
If PATTERN begins with `%', it must match at the end of the
expanded value of PARAMETER. If STRING is null, matches of
PATTERN are deleted and the `/' following PATTERN may be omitted.
If PARAMETER is `@' or `*', the substitution operation is applied
to each positional parameter in turn, and the expansion is the
resultant list. If PARAMETER is an array variable subscripted
with `@' or `*', the substitution operation is applied to each
member of the array in turn, and the expansion is the resultant
list.
 

abgdf

Guru
@uhelp: Ich komme mit den Details der Shell auch nicht so gut zurecht (vor allem, wenn irgendwo eine Subshell aufgeht, und meine Variablen plötzlich verschwinden), Perl finde ich da viel klarer (und deshalb einfacher).

Aber Du hast das gut erklärt. Steht das irgendwo auch im ABS-Guide? Oder gibt es noch ein genaueres Dokument zur Shell als den ABS-Guide?
 

uhelp

Member
Ob das alles auch im ABS steht, weiß ich nicht.
Der war mal ziemlich buggy und hat teilweise ziemlich viel Falsches erklärt,
was sich jedoch in letzter Zeit gebessert haben soll.
Ich nehme den also nicht.

Es ist halt mal ein Grundprinzip der Bash (und vieler anderer Shells auch),
jede Zeile zu parsen, und sie dann in einer Subshell auszuführen.
Da eine Subshell ein eigener Prozess ist, sind die halt strikt getrennt.
(Erst recht nicht, kann ein Kindprozess die Variablen im Vaterprozess ändern. No way never.)
Zwar werden die Variablen an den Kindprozess vererbt und können dort geändert werden,
nur, wenn dann halt diese Subshell endet, gibt es die nicht mehr,
und man sieht wieder die ungeänderten "Originale" des Vaterprozesses.

Dann kennt die Bash noch verschiedene Arten von Variablen. Was auch ziemlich verwirren kann.
Es gibt besondere Variablen, wie SECONDS (bei Expansion die Anzahl der Sekunden seit Start dieser Shellinstanz) oder RANDOM (pro Aufruf eine Integer Zufallszahl), die ihre magischen Eigenschaften bei der ersten Zuweisung verlieren.
Und es gibt exportierte Variablen, die via env dann sichtbar sind. ( export someVar=something )
Das waren aber auch schon die relavanten Sachverhalte, um dieses Verhalten zu verstehen.
(Es gibt noch mehr besondere Variablen, die für diesen Verstehen aber nichts beitragen; sie verhalten sich "normal")

Und es ist nur sehr bedingt möglich mit dem Schlüsselwort local eine Variable in einer function lokal zu machen.
Die Bash kennt keine Namensräume. (Ein declare var=something innerhalb einer function ist das gleiche, wie local var;)
Guck ma http://stackoverflow.com/documentation/bash/6835/namespace#t=201609191437103579329

Ein paar Links, bei denen man ziemlich gut die Bash lernen kann:
http://guide.bash.academy/oder die alte Version davon : http://mywiki.wooledge.org/BashGuide
http://wiki.bash-hackers.org/
http://shelldorado.com/

Nachtrag: Die http://mywiki.wooledge.org/BashFAQ bietet für die häufigsten "Probleme" Lösungen.
Dort finden sich auch alle Standardsituationen, in denen Variablen scheinbar verschwinden.
 

Boreas

Member
abgdf schrieb:
Gräfin Klara schrieb:
Eine so simple Aufgabe und doch nicht lösbar
Bash ist Ramsch
Seh' ich auch so. Aber Perl macht sowas sehr gut.
Eine solche Bemerkung drückt in diesem Fall eher aus, dass man mit der bash nicht wirklich vertraut ist. Und genau das sollte man auch sagen und nicht ein Programm für die eigenen Defizite verantwortlich machen. Sorry für die deutlichen Worte.
Eine m. E. recht gute Quelle zum Thema bash findet sich hier.
 

uhelp

Member
Wenn das Problem genannt werden würde,
könnte man bestimmt auch eine Bash Lösung angeben.

Es ist ein prinzipieller Unterschied zwischen einer Shell und einer Programmiersprache.
Man kann die nicht miteinander vergleichen.
 

abgdf

Guru
Ich verstehe ja, warum bash so ist, wie sie ist. Z.B., daß bei "a=1" keine Leerzeichen dazwischen sein dürfen, weil Leerzeichen eben zur Abgrenzung von Befehlen, Optionen und Dateinamen verwendet werden.
Einmal bin ich an for verzweifelt: Wollte einem Freund auf 'nem Mac kurz 'ne Schleife von 1 bis 10 zeigen, also for mit C-Syntax. Muß in bash aber so aussehen:
Code:
for (( c=1; c<=5; c++ ))
Mit den Doppelklammern und den Leerzeichen. Ohne das schnell nachgucken zu können, war das nicht zu erraten.
Boreas schrieb:
Eine solche Bemerkung drückt in diesem Fall eher aus, dass man mit der bash nicht wirklich vertraut ist. Und genau das sollte man auch sagen und nicht ein Programm für die eigenen Defizite verantwortlich machen.
Also, bash hat bestimmte Gründe, Dinge auf eine sehr ausgefallene Art zu machen. Das ist wenig intuitiv. Da kann man doch sagen, daß einem das nicht gefällt.
Z.B. mag ich schon das Prinzip nicht, Daten zu verarbeiten, indem man den Input einem Tool zuleitet, und dann über eine Pipe einem weiteren. Das ist irgendwie Patchwork. Außerdem werden dann eben sehr viele Prozesse erzeugt. Aber es ist klar, warum eine Shell das so macht. Turing-kompatibel dürfte sie sein, insofern ist sie schon auch eine Programmiersprache. Nur eben eine ziemlich merkwürdige. Ausgerechnet mit der kommen dann Linux-Anwender zuerst in Berührung ...
 

uhelp

Member
Probier das da:
Code:
for ((i=0;3>i;i++)); do 
   echo i=$i 
done
Ich weiß nicht, was du probiert hast, falsch ist deine Aussage darüber.

Eine Shell hat zuallererst die Aufgabe als Schnittstelle zwischen Kernel und Anwender Programme zu starten.
Dass damit ZUSÄTZLICH programmiert werden kann, ist eine Zugabe.

Die allermeisten seltsamen Konstrukte sind diesem Dogma geschuldet
und resultieren aus dem Ziel diese primäre Aufgabe gut zu erledigen.

Ein Vergleich ist mit richtigen Programmiersprachen ist sinnlos.

Um Shellprogrammierung gut zu verstehen, ist es unerlässlich die Arbeitsweise der Shell zu verstehen.
Sie liest Statement für Statement, parst diese und nimmt dann der Reihe nach bestimmte Schritte von Subsitutionen und Expansionen vor, bevor ein neu erzeugter Subprozess das Parsingresultat ausführt.
Das ist alles.

Mit der Denkhaltung, dass eine Shell mit Programmiersprachen vergleichbar sein müsse, oder Erwartungen, die man an eine Programmiersprache stellt, zu erfüllen wären, verneint man den ursprünglichen Zweck.
Und als Schnittstelle zwischen Kernel und User, die die Programme startet, ist die Bash eine seit sehr langer Zeit hochgezüchtete spezielle Shell.
Sie macht ihren Job sehr gut,
wenn man keine falschen Erwartungen an sie stellt.
 

uhelp

Member
Die sind nicht seltsam.

Sie unterscheiden einen arithemtischen Kontext, von der normalen Klammerexpansion.

Code:
echo $(  doSomeThings  )
echo $((4+6))

Und der arithmetische Kontext ist halt nun mal so definiert.
Die andere Expansion ist deutlich häufiger anzutreffen.
Die Arithmetik ist auch ein sehr viel jüngeres Feature.
 

}-Tux-{

Hacker
Gräfin Klara schrieb:
RegEx-Engine einstellen:
# shopt -s extglob
ändert nichts für bash intener, eigener regexp
Damit stehen dir die "extended pattern matching operators" zur Verfuegung.
Code:
S="abc <foo> def <bar>"
while test "$S" != "${S/<+([!>])>/XXX}"; do
  S="${S/<+([!>])>/XXX}";
done
echo $S
Liefert "abc XXX def XXX" (geht bestimmt auch eleganter...).


}-Tux-{
 

uhelp

Member
Da es hier kein Strikethrough gibt:
FALSCH überflüssig zu lesen: Es ist zwar richtig, dass ein shopt -s extglob einschaltet und man dann die extended globs verwenden kann.

Nur ändert das Nullkommanix an dieser Parameterexpansion.
Probier das einmal der Reihe nach.
Jeweils nach
shopt -u extglob
shopt -s extglob
Das macht hier nix. Das Ergebnis bleibt einfach gleich.
Denn eine Parameter Expansion ist nun mal keine Filename Expansion.

Das hat nur Einfluss auf das Fileglobbing.
Und nicht auf die RegEx Engine der Bash.

FALSCH END

Damit kann man, so man die Bash Dateinamen vervollständigen lassen will, auch einen RegExdialekt verwenden.
Mit solchen Konstrukten: +(RegEx) ?(RegEx) *(RegEx) !(RegEx)
(Die wiederum so komisch aussehen, weil sie der CLI Syntax unterliegen und längst in alten Shellzeiten eingeführten Konstrukten nicht in die Quere kommen dürfen.
Kann man gut nachlesen im Woolegde Wiki: http://mywiki.wooledge.org/glob#extglob
Dort ist es auch wichtig nullglob nachzulesen. Das wird man in diesem Zusammenhang immer brauchen.
Lernt man das gleich mit, spart man sich viel Ärger.
 
Oben