ubuntuusers.de

Linux bash: Befehl sed

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

Umaash

Anmeldungsdatum:
7. Juni 2016

Beiträge: 123

Hallo zusammen

Das ist der Inhalt der Datei ${DateinamenArrey[i]}:

z
z
#
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)

Das ist der Inhalt der Variable ${ZeilenAktion1Arrey[i]}:

z[^\\n]*\n[^\\n]*z[^\\n]*\n

Das ist der Inhalt der Variable ${ZeilenAktion2Arrey[i]}:

a\n

Im Script wird der folgende Befehl ausgeführt:

1
sed -i ":a;N;\$!ba;s/${ZeilenAktion1Arrey[i]}/${ZeilenAktion2Arrey[i]}/g" "${DateinamenArrey[i]}"

Erwartetes Resultat:

a
#
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)

tatsächliches Resultat:

a
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)

Wieso klappt das nicht? Eine Antwort würde mich freuen. Besten Dank.

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1272

Wieso klappt das nicht?

Mit den von dir beschriebenen Daten klappt es. Siehe folgendes Beispiel:

$ cat test.sh 
#!/bin/bash
[ -n "$1" ] && dbg="--debug" || dbg=""
[ -n "$dbg" ] && set -x || set +x
i=1
DateinamenArrey[i]="x.x"
cat >${DateinamenArrey[i]} <<*EOF*
z
z
#
BASESTARTUP=\$(basename "\$STARTUP" | cut -d\  -f1)
*EOF*

ZeilenAktion1Arrey[i]="z[^\\n]*\n[^\\n]*z[^\\n]*\n"
ZeilenAktion2Arrey[i]="a\n"

sed  $dbg -i ":a;N;\$!ba;s/${ZeilenAktion1Arrey[i]}/${ZeilenAktion2Arrey[i]}/g" "${DateinamenArrey[i]}"

cat ${DateinamenArrey[i]}

set +x
$ # -----------------------------------------------------------
$ # erster Aufruf ohne Debug
$ . test.sh
a
#
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)
$ 
$ # das sieht doch korrekt aus. Nun der Aufruf mit DEBUG
$ . test.sh x
++ i=1
++ DateinamenArrey[i]=x.x
++ cat
++ ZeilenAktion1Arrey[i]='z[^\n]*\n[^\n]*z[^\n]*\n'
++ ZeilenAktion2Arrey[i]='a\n'
++ sed --debug -i ':a;N;$!ba;s/z[^\n]*\n[^\n]*z[^\n]*\n/a\n/g' x.x
SED PROGRAM:
  :a
  N
  $! b a
  s/z[^\n]*\n[^\n]*z[^\n]*\n/a
/g
INPUT:   'x.x' line 1
PATTERN: z
COMMAND: :a
COMMAND: N
PATTERN: z\nz
COMMAND: $! b a
COMMAND: :a
COMMAND: N
PATTERN: z\nz\n#
COMMAND: $! b a
COMMAND: :a
COMMAND: N
PATTERN: z\nz\n#\nBASESTARTUP=$(basename "$STARTUP" | cut -d\\  -f1)
COMMAND: $! b a
COMMAND: s/z[^\n]*\n[^\n]*z[^\n]*\n/a
/g
MATCHED REGEX REGISTERS
  regex[0] = 0-4 'z
z
'
PATTERN: a\n#\nBASESTARTUP=$(basename "$STARTUP" | cut -d\\  -f1)
END-OF-CYCLE:
++ cat x.x
a
#
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)
++ set +x
$ 
$ # auch dies sieht korrekt aus

Umaash

(Themenstarter)

Anmeldungsdatum:
7. Juni 2016

Beiträge: 123

Danke.

Aber irgendwie stehe ich auf dem Schlauch:

Inhalt: Datei.txt:

z
z
#
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)

Terminal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ z1='z[^\\n]*\n[^\\n]*z[^\\n]*\n'

$ echo $z1
z[^\\n]*\n[^\\n]*z[^\\n]*\n

$ z2='a\n'

$ echo $z2
a\n

$ sed ":a;N;\$!ba;s/$z1/$z2/g" "Datei.txt"
a
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1272

Aber irgendwie stehe ich auf dem Schlauch:

Brauchst du nicht. Es liegt an der Art des Quoting bzw an der Art des doppelten Escapen. Beispiel:

$ # Dein Beispiel, wie von dir beobachtet:
$ z1='z[^\\n]*\n[^\\n]*z[^\\n]*\n'
$ z2='a\n'
$ sed ":a;N;\$!ba;s/$z1/$z2/g" "Datei.txt"
a
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)
$ 
$ # Nun ändere ich das Quoting
$ z1="z[^\\n]*\n[^\\n]*z[^\\n]*\n"
$ sed ":a;N;\$!ba;s/$z1/$z2/g" "Datei.txt"
a
#
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)
$ 
$ # Nun ändere ich das doppelte Escapen:
$ z1='z[^\n]*\n[^\n]*z[^\n]*\n'
$ sed ":a;N;\$!ba;s/$z1/$z2/g" "Datei.txt"
a
#
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1)
$ 

Wenn du dir das Verhalten mit "--debug" anschaust, wird das ev. klarer.

Umaash

(Themenstarter)

Anmeldungsdatum:
7. Juni 2016

Beiträge: 123

Danke für den Hinweis. Jetzt ist mir klar, dass es daran liegt, dass z1='z[^\n]*\n[^\n]*z[^\n]*\n' sein muss, damit es funktioniert. Mit z1='z[^\\n]*\n[^\\n]*z[^\\n]*\n' funktioniert es nicht.

Mir ist klar, was mit z1='z[^\n]*\n[^\n]*z[^\n]*\n' abläuft. Aber ich verstehe nicht was mit z1='z[^\\n]*\n[^\\n]*z[^\\n]*\n' eigentlich genau abläuft. D. h. ich kann nicht nachvollziehen, was eigentlich passiert ist bei der unerwünschten Variante.

Der relevante Debug-Teil ist folgender (mit z1='z[^\n]*\n[^\n]*z[^\n]*\n'):

COMMAND: s/z[^\n]*\n[^\n]*z[^\n]*\n/a
/g
MATCHED REGEX REGISTERS
  regex[0] = 0-4 'z
z
'

Der relevante Debug-Teil ist folgender (mit z1='z[^\\n]*\n[^\\n]*z[^\\n]*\n'):

COMMAND: s/z[^\\\\n]*\n[^\\\\n]*z[^\\\\n]*\n/a
/g
MATCHED REGEX REGISTERS
  regex[0] = 0-6 'z
z
#
'

Wieso genau ist jetzt hier noch das Kreuz (#) dabei?

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1272

Wieso genau ist jetzt hier noch das Kreuz (#) dabei?

Gar nicht.

Das Konstrukt "[^\\\n]*" sucht nach dem nicht Auftreten von entweder "\" (escaped: \\) oder dem Buchstaben "n". Das erste Auftreten ist der Buchstabe "n" in der Zeichenkette "basename". Da danach ein NL (NewLine=\n) kommen soll, erfolgt ein Rollback zum ersten "\n", also vor dem 2. "z". Danach erhält regex wieder einen Match beim "n" von "basename". Da danach ein "z" kommen soll, erfolgt wieder ein Rollback bis zum 2. "z". Danach wird wieder bis zum "n" gescannt. Da aber danach ein "\n" kommen soll erfolgt wieder ein Rollback bis zum "\n" nach der Raute "#", vor dem "B" von "BASESTARTUP". Die so gefundene Selektion wird durch "a" ersetzt.

Ich hoffe, dich nun nicht zu sehr verwirrt zu haben, wenn ich den Ablauf verbal beschreibe. Ich glaube es gibt irgendwo einen regex explorer oder coach (?), wo man Schritt für Schritt das Verhalten des Algorithmus nachvollziehen kann, wenn du meiner Beschreibung nicht ganz folgen kannst.

Die Wertzuweisung über " klappt, weil bereits bei der Zuweisung der \\ zu \ umgesetzt wird und später "\n" als Newline interpretiert wird. Das Verhalten kannst du auch bei Variablen-Substitutionen sehen. Ein "$var" wird bei der Zuweisung aufgelöst, ein '$var' nicht.

Umaash

(Themenstarter)

Anmeldungsdatum:
7. Juni 2016

Beiträge: 123

Vielen Dank für die detaillierte Beschreibung. Genau das wollte ich wissen. Ich kann Deiner Beschreibung folgen und verstehe jetzt, wie das unerwünschte Verhalten zu Stande kommt.

Allerdings hätte ich erwartet, dass es mit [^\\\\n]* anders funktioniert: Ich hätte gedacht, dass auch eine "NewLine" in der Datei "Datei.txt" vom Konstrukt [^\\\\n]* als ein Auftreten von "\" erkannt wird. So hätte ich erwartet, dass das erste Auftreten von "\" in "Newline" nach dem ersten "z" ist.

Ich liste hier den Inhalt der Datei.txt noch einmal auf, ergänzt durch die Sichtbarmachung der "Newlines":

z [Newline \n]
z [Newline \n]
# [Newline \n]
BASESTARTUP=$(basename "$STARTUP" | cut -d\  -f1) [Newline \n]

Wieso erkennt das Regex-Konstruckt [^\\\\n]* das Auftreten von "\" in den "Newlines" jeweils nicht?

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1272

Wieso erkennt das Regex-Konstruckt [^\\\\n]* das Auftreten von "\" in den "Newlines" jeweils nicht?

Ja, das ist manchmal schwer zu verstehen, da die Interpretation in mehreren Schritten erfolgt (bash, sed usw).

Ich möchte es am Beispiel von "echo" unter "bash" versuchen zu erklären:

$ echo -n "\n" | od -c		# 2 einzelne Zeichen (\) (n)
0000000   \   n
0000002
$ echo -n -e "\n" | od -c	# 1 Zeichen (NL)
0000000  \n
0000001
$ echo -n "\\n" | od -c		# 2 Zeichen (\) (n)
0000000   \   n
0000002
$ echo -n -e "\\n" | od -c	# 1 Zeichen (NL)
0000000  \n
0000001
$ echo -n "\\\n" | od -c	# 3 Zeichen (\) (\) (n)
0000000   \   \   n
0000003
$ echo -n -e "\\\n" | od -c	# 2 Zeichen (\) (n)
0000000   \   n
0000002
$ echo -n "\\\\n" | od -c	# 3 Zeichen (\) (\) (n)
0000000   \   \   n
0000003
$ echo -n -e "\\\\n" | od -c	# 2 Zeichen (\) (n)
0000000   \   n
0000002
$ echo -n "\\\\\n" | od -c	# 4 Zeichen (\) (\) (\) (n)
0000000   \   \   \   n
0000004
$ echo -n -e "\\\\\n" | od -c	# 2 Zeichen (\) (NL)
0000000   \  \n
0000002
$ 

Bei "-e" wir der 2. Schritt (interpretation of backslash escapes) auch bei "echo" umgesetzt (NL). Ist es nun klarer?

Umaash

(Themenstarter)

Anmeldungsdatum:
7. Juni 2016

Beiträge: 123

Verstehe. Mit anderen Worten: sed interpretiert zuerst den Inhalt von der "Datei.txt", dann interpretiert sed den Inhalt von der Variablen $z1 und erst danach findet die Mustererkennung statt.

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1272

Umaash schrieb:

Verstehe. Mit anderen Worten: sed interpretiert zuerst den Inhalt von der "Datei.txt", dann interpretiert sed den Inhalt von der Variablen $z1 und erst danach findet die Mustererkennung statt.

Prinzipiell richtig. Der beste Tip ist, sich die Ausgabe von "--debug" anzuschauen. Das was aus der Datei gelesen wurde steht in "PATTERN", der anzuwendende Befehl steht in "COMMAND". Das, was in "COMMAND" steht, wird auf "PATTERN" angewendet.

Antworten |