sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Benutzeravatar
MartinV
Beiträge: 790
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von MartinV » 02.08.2018 22:37:28

Beim Parsen von Optionen will ich einen String beim ersten Vorkommen von " -- " teilen.
" -- " mit Leerzeichen davor und dahinter.

Einfaches Beispiel:

Code: Alles auswählen

$ echo "aaa -- bbb -- ccc" | sed -e magie
bbb -- ccc
Das soll auch für komplexere Beispiele mit --Optionen funktionieren:

Code: Alles auswählen

$ echo "aaa --xy -- --bbb bla -- --ccc zw" | sed -e magie
--bbb bla  --  --ccc zw
Mag jemand für mich ein passendes sed zaubern?
Zuletzt geändert von MartinV am 03.08.2018 01:06:05, insgesamt 1-mal geändert.
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

tobo
Beiträge: 2336
Registriert: 10.12.2008 10:51:41

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht

Beitrag von tobo » 03.08.2018 00:55:17

Sowas?

Code: Alles auswählen

sed -r 's/.* -- (.*( --.*))/\1/'

Benutzeravatar
MartinV
Beiträge: 790
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht

Beitrag von MartinV » 03.08.2018 01:05:39

Perfekt, danke! :THX:
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Benutzeravatar
Meillo
Moderator
Beiträge: 9228
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von Meillo » 03.08.2018 06:48:10

Hier noch eine Variante:

Code: Alles auswählen

$ echo "aaa --xy -- --bbb bla -- --ccc zw" | grep -o ' -- .*' | sed s,....,,
--bbb bla -- --ccc zw
Im Gegensatz zu tobos Variante funktioniert die auch mit weiteren `` -- '' im String korrekt:

Code: Alles auswählen

$ echo "aaa --xy -- --bbb bla -- --ccc zw -- --ddd aaa" | sed 's/.* -- \(.*\( --.*\)\)/\1/'
--ccc zw -- --ddd aaa

$ echo "aaa --xy -- --bbb bla -- --ccc zw -- --ddd aaa" | grep -o ' -- .*' | sed s,....,,
--bbb bla -- --ccc zw -- --ddd aaa
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von RobertDebiannutzer » 03.08.2018 07:53:22

Mit der bash in stretch würde auch sowas gehen:

Code: Alles auswählen

~$ variable="aaa --xy -- --bbb bla -- --ccc zw -- --ddd aaa"
~$ echo "${variable#* -- }"
--bbb bla -- --ccc zw -- --ddd aaa
Dokumentation:
aus "man bash"

Code: Alles auswählen

       ${parameter#word}
       ${parameter##word}
              Remove matching prefix pattern.  The word is expanded to produce a pattern just as in pathname expansion.  If the pattern matches the beginning of the value of parameter, then the result of the expansion is the expanded
              value  of  parameter  with  the  shortest matching pattern (the ``#'' case) or the longest matching pattern (the ``##'' case) deleted.  If parameter is @ or *, the pattern removal 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 pattern removal operation is applied to each member of the array in turn, and the expansion  is
              the resultant list.

Benutzeravatar
Meillo
Moderator
Beiträge: 9228
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von Meillo » 03.08.2018 09:40:31

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
03.08.2018 07:53:22
Mit der bash in stretch würde auch sowas gehen:

Code: Alles auswählen

~$ variable="aaa --xy -- --bbb bla -- --ccc zw -- --ddd aaa"
~$ echo "${variable#* -- }"
--bbb bla -- --ccc zw -- --ddd aaa
Schoene Idee! :THX:

Das geht nicht nur in der Bash, sondern mit jeder POSIX-kompatiblen Shell.


Als Einzeiler auch so moeglich:

Code: Alles auswählen

$ sh -c 'echo "${0#* -- }"' "aaa --xy -- --bbb bla -- --ccc zw -- --ddd aaa"
--bbb bla -- --ccc zw -- --ddd aaa
Use ed once in a while!

tobo
Beiträge: 2336
Registriert: 10.12.2008 10:51:41

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von tobo » 03.08.2018 13:46:14

Meillo hat geschrieben: ↑ zum Beitrag ↑
03.08.2018 06:48:10
Im Gegensatz zu tobos Variante funktioniert die auch mit weiteren `` -- '' im String korrekt:
Ja, da bin ich über den ersten gierigen Quantor gestolpert!? In Ermangelung an einer nichtgierigen Auswertung oder einer negierten Mehrzeichen-Zeichenklasse muss also Eindeutigkeit her:

Code: Alles auswählen

sed 's/ -- /XXX/;s/.*XXX//'
PS:
Vielleicht dann noch eher sowas:

Code: Alles auswählen

sed 'ta;s/ -- /\n/;D;:a'

Benutzeravatar
Meillo
Moderator
Beiträge: 9228
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von Meillo » 03.08.2018 23:39:49

@tobo: Coole Loesungen! :THX:
Use ed once in a while!

Benutzeravatar
MartinV
Beiträge: 790
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von MartinV » 04.08.2018 15:37:37

Meillo hat geschrieben: ↑ zum Beitrag ↑
03.08.2018 06:48:10
Im Gegensatz zu tobos Variante funktioniert die auch mit weiteren `` -- '' im String korrekt:
Gut beobachtet!

Danke für die weiteren Vorschläge. Ich nehme jetzt das mir verständliche und elegante:

Code: Alles auswählen

sh -c 'echo "${0#* -- }"' "aaa -- bbb -- ccc -- ddd"
------
tobo hat geschrieben: ↑ zum Beitrag ↑
03.08.2018 13:46:14
sed 's/ -- /XXX/;s/.*XXX//'
Auch sehr schön, und mir verständlich. Könnte nur schiefgehen, wenn XXX im String auftaucht, z.B. im Pfad zu einem *hust* Bilderordner. XXX müßte also durch etwas eindeutigeres ersetzt werden.
tobo hat geschrieben: ↑ zum Beitrag ↑
03.08.2018 13:46:14
Ja, da bin ich über den ersten gierigen Quantor gestolpert!? In Ermangelung an einer nichtgierigen Auswertung oder einer negierten Mehrzeichen-Zeichenklasse muss also Eindeutigkeit her:
[...]

Code: Alles auswählen

sed 'ta;s/ -- /\n/;D;:a'
äh? (Ich fühle mich geehrt, derart intelligente Antworten zu bekommen ... :mrgreen: )
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

tobo
Beiträge: 2336
Registriert: 10.12.2008 10:51:41

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von tobo » 04.08.2018 17:36:34

MartinV hat geschrieben: ↑ zum Beitrag ↑
04.08.2018 15:37:37
tobo hat geschrieben: ↑ zum Beitrag ↑
03.08.2018 13:46:14
sed 's/ -- /XXX/;s/.*XXX//'
Auch sehr schön, und mir verständlich. Könnte nur schiefgehen, wenn XXX im String auftaucht, z.B. im Pfad zu einem *hust* Bilderordner. XXX müßte also durch etwas eindeutigeres ersetzt werden.
Das könnte man ja beliebig eindeutig gestalten und eigentlich dachte ich, dass es das auch wäre. Aber ok, auf das gehustete Beispiel bin ich jetzt nicht gekommen und das ist auch ein Gegenargument.
MartinV hat geschrieben:
tobo hat geschrieben: ↑ zum Beitrag ↑
03.08.2018 13:46:14
Ja, da bin ich über den ersten gierigen Quantor gestolpert!? In Ermangelung an einer nichtgierigen Auswertung oder einer negierten Mehrzeichen-Zeichenklasse muss also Eindeutigkeit her:
[...]

Code: Alles auswählen

sed 'ta;s/ -- /\n/;D;:a'
äh?
Das Problem an

Code: Alles auswählen

sed -r 's/.* -- (.*( --.*))/\1/'
ist, dass das erste Quantor ".*" so "gierig" mit der längsten Übereinstimmung abgleicht (matched), so dass der Rest der Regex immer auf 2 " -- "-Sequenzen maximiert ist, egal wie lang der String ist. Notwendig wäre also eine nichtgierige Auswertung des ersten ".* -- " wie z.B. in Perl mit ".*? -- ", was nach der kürzesten Übereinstimmung matched, oder aber ein Konstrukt ähnlich dieser Form

Code: Alles auswählen

sed 's/^[^X]*X//'
wobei X dann aber sinngemäß für " -- " steht. Als einzelnes Zeichen geht das, aber als ganzes Wort nicht.
Was das zweite sed angeht:

Code: Alles auswählen

sed 'ta;s/ -- /\n/;D;:a'
-- :a ist ein Sprungziel mit Name "a"
-- ta ist ein bedingter Sprung nach "a", wenn auf dem aktuellen Arbeitspuffer eine s- oder y-Substitution stattgefunden hat.
-- D löscht den Arbeitspuffer bis zum ersten Zeilenumbruch und beginnt direkt einen neuen Zyklus (Skript geht wieder von vorne los) auf dem aktuellen Arbeitspuffer, falls dieser nicht leer ist, ansonsten mit der nächsten Zeile (falls vorhanden).
Der Ablauf ist also:
- "ta" bedeutungslos, da noch keine Substitution stattgefunden hat.
- "s/ -- /\n/" ersetzt erstes Vorkommen mit einem newline (im Arbeitspuffer sind jetzt also praktisch 2 Zeilen).
- "D" löscht diese 1. Zeile und springt an den Anfang des Skripts.
- "ta" Substitution gab es auf aktuellem Puffer, gehe nach Marke "a".
- ":a" Ende des Skripts und Ausgabe des Arbeitspuffers, da kein -n als Option mitgegeben wurde.

Benutzeravatar
Meillo
Moderator
Beiträge: 9228
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von Meillo » 06.08.2018 09:49:50

tobo hat geschrieben: ↑ zum Beitrag ↑
04.08.2018 17:36:34
MartinV hat geschrieben: ↑ zum Beitrag ↑
04.08.2018 15:37:37
tobo hat geschrieben: ↑ zum Beitrag ↑
03.08.2018 13:46:14
sed 's/ -- /XXX/;s/.*XXX//'
Auch sehr schön, und mir verständlich. Könnte nur schiefgehen, wenn XXX im String auftaucht, z.B. im Pfad zu einem *hust* Bilderordner. XXX müßte also durch etwas eindeutigeres ersetzt werden.
Das könnte man ja beliebig eindeutig gestalten und eigentlich dachte ich, dass es das auch wäre. Aber ok, auf das gehustete Beispiel bin ich jetzt nicht gekommen und das ist auch ein Gegenargument.
Ich kenne fuer diesen Fall diese Herangehensweise:

Code: Alles auswählen

a="`echo a | tr a \\01`"
sed "s/ -- /$a/;s/.*$a//"
ASCII 0x01 (SOH) kommt in Textdaten quasi nie vor. (Mit der Bash kann man das Zeichen bestimmt auch anders direkt angeben. Die Verwendung von tr(1) dafuer habe ich irgendwo mal gesehen und fuer mich uebernommen.)



Bezuglich Gierigkeit: POSIX fordert bei Regexps immer den fruehesten, laengsten Match. Das ist gieriges Matchen. Darum kann man den vorderen Teil, der so klein wie moeglich sein soll und folglich ungierig gematched werden muesste, nicht auswaehlen. Man koennte das Problem trotz gierigem Matching mit Lookaheads loesen, aber die gibt's in POSIX auch nicht. Das sind so Gruende, warum PCREs maechtiger sind als POSIX REs.



Was ich immer wieder etwas anstrengend finde, bei sed(1) sind diese Sonderverhalten wie bei `D', das nicht nur eine Aktion ausfuehrt (naemlich bis zum ersten Newline zu loeschen), sondern gleichzeitig auch noch einen Sprung an den Scriptbeginn macht. Das ist eine Un-Orthogonalitaet, die mich immer wieder irritiert. Bei `d' macht das Starten des naechsten Durchgangs Sinn, bei `D' aber nur manchmal, zumal man da normalerweise noch Text im Patternspace hat (weil sonst haette man ja auch `d' verwenden koennen). Ich finde, sed(1) waere bei derartigen Scripten einfacher verstaendlich, wenn man Spruenge/Labels nur fuer explizite Spruenge braeuchte und nicht um implizite Spruenge auszugleichen.
Use ed once in a while!

tobo
Beiträge: 2336
Registriert: 10.12.2008 10:51:41

Re: sed: string an erstem Keyword teilen, 2. Hälfte gebraucht [gelöst]

Beitrag von tobo » 07.08.2018 18:07:58

Meillo hat geschrieben: ↑ zum Beitrag ↑
06.08.2018 09:49:50
Ich kenne fuer diesen Fall diese Herangehensweise:

Code: Alles auswählen

a="`echo a | tr a \\01`"
sed "s/ -- /$a/;s/.*$a//"
Auf sowas bin ich jetzt nicht gekommen, aber eigentlich ist ein nicht darstellbares Zeichen natürlich logisch!? Könnte man vielleicht abkürzen:

Code: Alles auswählen

a=`printf '\1'`
Und für die sed-Versionen, bei denen

Code: Alles auswählen

sed "s/ -- /\x1/;s/.*\x1//"
nicht funktioniert, das darüberstehende adaptieren:

Code: Alles auswählen

sed "s/ -- /`printf '\1'`/;s/.*`printf '\1'`//"
echo "aaa -- bbb -- ccc -- ddd" | sed "s/ -- /`printf '\1'`/;l"
echo "aaa -- bbb -- ccc -- ddd" | sed "s/ -- /`printf '\1'`/;s/.*`printf '\1'`//"
Was die Mächtigkeit von sed angeht (z.B. PCRE), da wurde ja mit Debianssed schon ein Weg beschritten, was sich aber offensichtlich nicht durchsetzen will?!
http://sed.sourceforge.net/grabbag/ssed/
Trotzdem eine sehr informative Seite.

Antworten