PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Komplizierte Ersetzung mit "sed"



DieterH
18-05-2008, 14:15
Hallo,

Ich habe eine Textdatei, in der auch Zeichenketten innerhalb einem Paar geschweifter Klammern vorkommen:

Beispiel:
bla bla blub {Beliebiger Text in geschweiften Klammern} bla bla blub

Ich möchte hinter JEDEM Zeichen der Zeichenkette in der geschweiften Klammer einen Doppelpunkt anbringen. Der Text außerhalb der geschweiften Klammer soll unberührt bleiben.

Meine Versuch mit:
sed -e 's/{[^{}]/&:/' < input > output, bzw.
sed -e 's/[^{}]}/:&/' < input > output

fügt nur nach dem ersten Zeichen, bzw. vor dem letzten Zeichen einen Doppelpunkt ein.

Wer hat eine Idee, wie man das Vorhaben mit "sed" lösen kann?

Vielen Dank im voraus.
Gruß
Dieter

WeenerChild
18-05-2008, 15:24
Hm, ich befürchte fast, dass das mit sed so nicht möglich ist. Hier mal ein paar perl-Varianten:

Mit zwei lookaheads:

echo 'bla bla blub {Beliebiger Text in geschweiften Klammern} bla bla blub'|
perl -pe 's/[^{](?=.*})(?!.*{)/$&:/g'

Mit dem Text in curly-braces in eine eigene Variable ($b) gepackt:

echo 'bla bla blub {Beliebiger Text in geschweiften Klammern} bla bla blub'|
perl -pe '$b=$1 if m|[{](.*)[}]|; $b=~s/./$&:/g; s/[{].*[}]/{$b}/'

Oder die Zeile in durch curly-braces geteilte Felder zerteilen:

echo 'bla bla blub {Beliebiger Text in geschweiften Klammern} bla bla blub'|
perl -anF'(?<={)|(?=})' -e '$F[1]=~s/./$&:/g; print @F'

Aber kA, ich bin auch nicht so der sed-Crack, vielleicht geht das ja doch. Würde mich ehrlich gesagt auch mal interessieren.

DieterH
19-05-2008, 16:39
Ich möchte die Lösung gerne mit klassischen Unix-Bordmitteln erstellen. Da "sed" eine elegante Lösung versagt, behelfe ich mir mit in Serie geschalteten Ersetzfunktionen:

-e 's/{[^{}]\{20,20\}/&:/g' \
-e 's/{[^{}]\{19,19\}/&:/g' \
-e 's/{[^{}]\{18,18\}/&:/g' \
...
-e 's/{[^{}]\{3,3\}/&:/g' \
-e 's/{[^{}]\{2,2\}/&:/g' \
-e 's/{[^{}]\{1,1\}/&:/g' \

Dieser Ansatz geht davon aus, dass der Text in den geschweiften Klammer jeweils nicht länger als 20 Zeichen ist; andernfalls ist er im dargestellten Sinne zu erweitern.

Nun ja, mühsam ernährt sich bekanntlich das Eichhörnchen

Gruß
Dieter

WeenerChild
19-05-2008, 21:28
Sind diese "Unix-Boardmittel" klassisch genug?

line='foo bar zomg {abc def 123} baz meh asdf'
pre_brace=`echo $line|cut -d\{ -f1`
post_brace=`echo $line|cut -d\} -f2`
brace=`echo $line|sed -e 's!.*{\(.*\)}.*!\1!'`
brace=`echo $brace|sed -e 's/./&:/g'`
echo "${pre_brace}{${brace}}${post_brace}"
Sollte bourne-shell kompatibel sein. (Wenn auch imho sehr unelegant, aber immernoch attraktiver als dein "Vorschlag". awk hab ich bewusst ausgelassen. (Nicht klassisch genug? kA))
Ich weiß auch nicht inwiefern du dich vor perl sträubst. Mit einem kleinen (non-cli) Skript könnte man das recht einfach und vor allem auch les-/nachvollziehbar erledigen. Aber whatever floats your boat.

jan61
19-05-2008, 23:10
Moin,

das folgende Monstrum ist sicher noch nicht 100% perfekt (dazu müsste man mal mit ein paar wildgewordenen Daten testen), aber zur Veranschaulichung meiner Idee sollte es reichen. Ich präsentiere erstmal ein funktionierendes Beispiel und nehme es dann auseinander (wegen der Tradition habe ich auch auf erweiterte Regex verzichtet - macht den Code nicht unbedingt schöner, sollte aber mit jedem Unix-sed laufen):

jan@jack:~/tmp> echo -e "12345{678abc}90\n12{34}56{7}890" | \
sed ': X;s/\({[^:{}]*\)\([^:{}]\)\([^:{}]\)/\1\2:\3/g;t X;s/\([^:{}]\)}/\1:}/g'
12345{6:7:8:a:b:c:}90
12{3:4:}56{7:}890
Der echo gibt einfach erstmal 2 Zeilen mit verschiedenen Varianten Deiner Aufgabe aus. Auf gehts in die sed-Interna:

jan@jack:~/tmp> echo -e "12345{678abc}90\n12{34}56{7}890" | \
> sed ': X # Sprungmarke (Label) mit dem Namen "X" definieren
> s/\({[^:{}]*\)\([^:{}]\)\([^:{}]\)/\1\2:\3/g # ersetzt diese Stellen:
> # ("{" & beliebig viele Zeichen ausser ":{}") = Puffer1
> # & (1 beliebiges Zeichen ausser ":{}") = Puffer2
> # & (1 beliebiges Zeichen ausser ":{}") = Puffer3
> # durch: Puffer1 & Puffer2 & ":" & Puffer3
> t X # wenn die letzte Ersetzung erfolgreich war, dann zurueck zum
> # Label "X"
> s/\([^:{}]\)}/\1:}/g' # zum Schluss nach dem letzten Zeichen vor "}"
> # ein ":" einfügen
12345{6:7:8:a:b:c:}90
12{3:4:}56{7:}890
Der Trick beruht also darauf, eine Schleife zu produzieren, in der alle 2-Zeichen-Konstruktionen, die nach einem "{" folgen und noch keinen ":" enthalten, nacheinander ersetzt werden. Der Sonderfall für das letzte Zeichen vor dem "}" wird separat behandelt. Der Rücksprung zum Label "X" wird nur dann ausgeführt, wenn das s/.../.../-Konstrukt mindestens eine Ersetzung vorgenommen hat.

Jan

DieterH
20-05-2008, 11:05
Wiener Kind (WeenerChild),

vielen Dank für Deinen Vorschlag. Ich sträube mich nicht gegen Perl, ich wollte lediglich eine reine Unix-Lösung.



Jan61,

vielen Dank für Deinen Vorschlag.

Gruss
Dieter

rais
21-05-2008, 00:13
Wiener Kind
hmm, so es denn eine geographische Anspielung ist, würde ich `Weener' weg von der Donau, dafür dichter an der Ems ansiedeln.;-)

MfG