ubuntuusers.de

Linux bash: sed-Ersetzung

Status: Ungelöst | Ubuntu-Version: Xubuntu 20.04 (Focal Fossa)
Antworten |

Umaash

Anmeldungsdatum:
7. Juni 2016

Beiträge: 123

Wieso funktioniert das nicht?

1
2
3
4
$ b='\{\|r\\p\{xx\}'

$ a=$(sed ":a;N;\$!ba;s/$b//g" "$d")
sed: -e Ausdruck #1, Zeichen 29: Der 

Ich habe mir extra vorgängig überlegt, welches die Sonderzeichen sind und dann versucht wirklich alle zu escapen:

b=$(echo "$line" | sed 's/[].;:*^#%$&/()"{}<>\\+-=?!£–|`~[]/\\&/g') 

Wenn ich { und } und | doppelt escape funktioniert es. Aber woher weiss ich, wann ich doppelt escapen muss? Gibt es noch andere Zeichen, die man doppelt escapen muss? Ausserdem vermute ich: Wenn ich doppelt escape, obwohl ich gar nicht muss, dann wird ja auch nicht mehr nach dem Muster gesucht, nach dem ich suchen will, sondern dann hat das Muster wohl ein "\" zu viel.

Es wäre praktisch, wenn es einen Befehl geben würde, der eine beliebige Zeichenkette so auf sed vorbereitet, dass sed keine Probleme mit dem Inhalt mehr hat.

Eine Antwort würde mich freuen. Besten Dank.

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

Umaash schrieb:

Wieso funktioniert das nicht?

1
2
3
4
$ b='\{\|r\\p\{xx\}'

$ a=$(sed ":a;N;\$!ba;s/$b//g" "$d")
sed: -e Ausdruck #1, Zeichen 29: Der 

Sedbefehle sind oft nicht leicht zu schreiben, noch schwieriger jedoch zu lesen. Was soll denn $b beschreiben - im Sedbefehl soll es dann entfernt werden, das ist recht klar. $d ist eine Datei, auf die der Befehl angewendet werden soll, aber was wäre ein Beispielinhalt mit Beispielergebnis.

Ich habe mir extra vorgängig überlegt, welches die Sonderzeichen sind und dann versucht wirklich alle zu escapen:

b=$(echo "$line" | sed 's/[].;:*^#%$&/()"{}<>\\+-=?!£–|`~[]/\\&/g') 

Auch wenn das Dollarzeichen sowohl in der Shell, als auch in sed, kontextabhängig Sonderbedeutungen hat oder haben kann - was für eine Sonderfunktion hat denn das '£'?

Ohne Kenntnis von $line und dem beabsichtigten Output zu kennen, scheint mir aber die ganze Zeile weitgehend irrelevant für das Posting.

Wenn Du nicht sed -r "..." benutzt haben einige der Zeichen für Sed gar keine Sonderbedeutung, nämlich ()+? (ohne Gewähr auf Vollständigkeit).

Wenn ich { und } und | doppelt escape funktioniert es. Aber woher weiss ich, wann ich doppelt escapen muss? Gibt es noch andere Zeichen, die man doppelt escapen muss? Ausserdem vermute ich: Wenn ich doppelt escape, obwohl ich gar nicht muss, dann wird ja auch nicht mehr nach dem Muster gesucht, nach dem ich suchen will, sondern dann hat das Muster wohl ein "\" zu viel.

Musst Du die geschweiften Klammern überhaupt maskieren?

1
echo "foo{bar}" | sed 's/foo{bar}/ozz uxx/g'

Sieht nicht so aus. Außer es dient als Quantor für {von,bis}:

1
echo "foo bar" | sed 's/o\{2\}/ox {lux}/g' 

Es mag Absicht sein, aber in b='\{\|r\\p\{xx\}' gehen zwei geschweifte Klammern auf und nur eine zu. Ist das so gewollt? Wo $b verwendet wird sehe ich keine literale geschweifte Klammer zugehen.

Es wäre praktisch, wenn es einen Befehl geben würde, der eine beliebige Zeichenkette so auf sed vorbereitet, dass sed keine Probleme mit dem Inhalt mehr hat.

Einen Befehl, der errät, was man vorhat und sich dann selbst hinschreibt - ja, das wäre praktisch, würde einem aber die Erfolgserlebnisse rauben, die man hat, wenn man es selbst hinbekommt. ☺

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9708

Wohnort: Münster

Umaash schrieb:

[…]

b=$(echo "$line" | sed 's/[].;:*^#%$&/()"{}<>\\+-=?!£–|`~[]/\\&/g') 

Du verwendest im Substituierungs-Befehl das Zeichen / sowohl als Trennzeichen der Felder als auch als Nutzzeichen im Suchmuster. Das geht gleichzeitig nicht, jedenfalls nicht ohne zusätzliche Quotierung/Maskierung.

Man kann aber als Trennzeichen für die Felder jedes beliebige ASCII-Zeichen benutzen, sogar das Leerzeichen:

$ echo anton | sed 's a A '
Anton
$

Beachte das Leerzeichen am Ende des Befehls!

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1270

Übrigens kannst du dir das Einlesen der Datei in eine Zeile über ":a;N;\$!ba" auch vereinfachen, indem du den "-z" Qualifier verwendest

$ # statt
$ a=$(sed ":a;N;\$!ba;s/$b//g" "$d")
$ # geht auch
$ a=$(sed -z "s/$b//g" "$d")

Umaash

(Themenstarter)

Anmeldungsdatum:
7. Juni 2016

Beiträge: 123

Danke @shiro für den Hinweis mit -z. Wie muss dann das Muster aussehen, das sich über mehrer Zeilen erstreckt, damit es in der Datei $d gefunden wird? Vielleicht so?:

Zeile1\0Zeile2\0Zeile3

–––––––––––––––––––––––––––––––––––––––––––––––––––––

Danke @kB für den Hinweis mit "/". Das habe ich übersehen.

–––––––––––––––––––––––––––––––––––––––––––––––––––––

@user_unknown:

Um etwas mehr Information zu geben:

In der Datei $d kann irgendetwas sein. Eine beliebige Kombination von Sonderzeichen, Text und Befehlen. Der Inhalt ändert sich natürlich immer wieder, je nachdem, auf welche Datei $d zeigt.

Und $b ist eben das, was in der Datei $d entfernt werden soll. $b ist also in der Datei $d vorhanden.

In $line ist das, was dann in die Variable $b geht. Der Zeilenumbruch wird mit \n gemacht. In $b können unter Umständen auch nicht geschlossene geschweifte Klammern drin stehen. Das soll verdeutlichen, dass eben in $b wie auch in der Datei $d, wie auch in $line jeder denkbare Inhalt vorkommen dürfen soll.

Es wäre praktisch, wenn es einen Befehl geben würde, der eine beliebige Zeichenkette so auf sed vorbereitet, dass sed keine Probleme mit dem Inhalt mehr hat.

Einen Befehl, der errät, was man vorhat und sich dann selbst hinschreibt - ja, das wäre praktisch, würde einem aber die Erfolgserlebnisse rauben, die man hat, wenn man es selbst hinbekommt.

Durch Try and Error habe ich jetzt herausgefunden, dass ich nur folgendes escapen muss damit der Befehl ...

a=$(sed ":a;N;\$!ba;s/$b//g" "$d")

... zuverlässig funktioniert:

Nämlich nur: [, ], * ,/, \ und sonst nichts. Ich zweifle aber noch etwas, ob ich durch try und error wirklich alles gefunden habe.

Frage: Habe ich?

(Der Befehl "a=$(sed ":a;N;\$!ba;s/$b//g" "$d")" funktioniert dann zuverlässig, wenn er das Muster so modifiziert wurde, dass der sed Befehl nicht gestört wird und ordnungsgemäss funktioniert. Und wenn durch die Modifizierung des Musters das Muster nicht so verändert wurde, dass es nicht mehr in der Datei $d gefunden wird, obwohl es dort eigentlich vorhanden wäre.)

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1270

Umaash schrieb:

Danke @shiro für den Hinweis mit -z. Wie muss dann das Muster aussehen, das sich über mehrere Zeilen erstreckt, damit es in der Datei $d gefunden wird? Vielleicht so?:

Zeile1\0Zeile2\0Zeile3

Nein, du hast die Terminatoren, die in der Datei stehen. Bei Unix ist das in der Regel "\n" (NL). Win oder Mac streuen in der Regel auch "\r" (CR) ein, die man bei Bedarf eliminieren kann. Beispiel:

$ echo -e "Zeile 1\r\nZeile 2\n\rZeile 3" | sed --debug -z 'p;s/\r//g;s/\([0-9]\+\)\nZeile/\1-x\nx-Line/g'
SED PROGRAM:
  p
  s/\r//g
  s/\\([0-9]\\+\\)\nZeile/\1-x
x-Line/g
INPUT:   'STDIN' line 1
PATTERN: Zeile 1\r\nZeile 2\n\rZeile 3\n
COMMAND: p
Zeile 1
Zeile 2
Zeile 3
COMMAND: s/\r//g
MATCHED REGEX REGISTERS
' regex[0] = 7-8 '
PATTERN: Zeile 1\nZeile 2\nZeile 3\n
COMMAND: s/\\([0-9]\\+\\)\nZeile/\1-x
x-Line/g
MATCHED REGEX REGISTERS
  regex[0] = 6-13 '1
Zeile'
  regex[1] = 6-7 '1'
PATTERN: Zeile 1-x\nx-Line 2-x\nx-Line 3\n
END-OF-CYCLE:
Zeile 1-x
x-Line 2-x
x-Line 3
$ 

Wie man sieht kann man elegant das Zeilenende im Suchbegriff mit verwenden. Das Zeilenende "$" ist dann das Dateiende.

Antworten |