sappy
Anmeldungsdatum: 24. Januar 2012
Beiträge: 282
|
Hallo, ich habe ein File, das wie folgt aussieht: ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3 String4 String5
ZT3 41.46 017.96 1 String Stringabc Ich möchte die Wörter ganz hinten alle mit einem Underscore ("_") zusammenführen, dass ich danach mit awk besser arbeiten kann und das alles als eine Spalte erkannt wird. Am Ende sollte es also so aussehen:
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3_String4_String5
ZT3 41.46 017.96 1 String_Stringabc Er darf quasi nur das Whitespace zwischen Buchstaben ersetzen. Ich hab es mit sed versucht, jedoch macht es noch Probleme: | head -4 file.txt | sed 's/ */ /g' | sed 's/\b\s\b/_/g'
|
Das zweite sed soll also zwischen zwei Wörtern (\b) mein Whitespace (\s) mit einem Underscore ersetzen.
Klappt nicht. Er haut mir auch in den Zahlen davor ein "_". Jemand eine Idee?
|
frostschutz
Anmeldungsdatum: 18. November 2010
Beiträge: 7657
|
while read oins zwoi droi fier fuennef
do
echo $oins $zwoi $droi $fier ${fuennef// /_}
done < datfile aber 100% geht das in awk direkt irgendwie viel geschickter
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17552
Wohnort: Berlin
|
Ich nehme an, dass Spalte 5 immer mit "String" beginnt ist ein Artefakt, auf das man in der Realität nicht rechnen kann. | cat file | sed -r "s/^(.{17}[^ ]*)( [^ ]*)/\1\2_/g"
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3_String4_String5
ZT3 41.46 017.96 1 String_Stringabc
|
-r erlaubt reguläre Ausdrücke einer bestimmten Form, hier ist es nötig, damit die runden Klammern nicht maskiert werden müssen. Für die geschweiften weiß ich es nicht aus dem Kopf. Der Ausdruck substituiert Gruppe1Gruppe2(BLANK) mit Gruppe1Gruppe2(Underline). Gruppe 1 besteht aus beliebigen Zeichen, verankert am ^ Zeilenanfang, der Länge 17. Dann kommen beliebig viele Nichtleerzeichen (432, 44, ... 1) gefolgt von einem Leerzeichen und wieder Nichtleerzeichen (-, -, String3, String) und dann noch ein Leerzeichen. Das ganze global, d.h. nachdem es erfolgreich versucht wurde wird es nochmal versucht. Wenn aber danach awk zum Einsatz kommt, dann kannst Du das ganze wohl auch mit awk machen - da habe ich aber zuwenig Praxis um eine Lösung aus dem Handgelenk zu schütteln.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
sappy schrieb:
Ich möchte die Wörter ganz hinten alle mit einem Underscore ("_") zusammenführen, dass ich danach mit awk besser arbeiten kann und das alles als eine Spalte erkannt wird.
Dann mach es doch gleich mit awk : | $ awk '{for (i=6;i<=NF;++i) $5=$5 "_" $i; NF=5} {print}' data
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3_String4_String5
ZT3 41.46 017.96 1 String_Stringabc
|
Wie Du in der zweiten Klammer siehst, denkt awk jetzt, dass fünf Spalten da sind und dass $5 die Verkettung der Einträge $5 bis $NF ist. Anstatt des print kannst Du da Deine Verarbeitung unterbringen. Oder Du packst es mit in die erste Klammer.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
user_unknown schrieb: | cat file | sed -r "s/^(.{17}[^ ]*)( [^ ]*)/\1\2_/g"
|
Mit sed ginge das auch noch kürzer:
| $ sed 's/ \+/_/g5' data
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3_String4_String5
ZT3 41.46 017.96 1 String_Stringabc
|
Statt den Whitespace in den Strings nach den ersten vier Feldern zu verändern könnte man sich z.B. auch überlegen die ersten vier Trennzeichen zu ersetzen, also z.B.:
| $ awk -F="" '{for(i=1;i<=4;++i){sub(/ +/, "\t", $0)}; print}' data
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3 String4 String5
ZT3 41.46 017.96 1 String Stringabc
|
Und dann in awk mit einem Tab als Feldtrenner arbeiten.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
seahawk1986 schrieb:
Mit sed ginge das auch noch kürzer:
| $ sed 's/ \+/_/g5' data
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3_String4_String5
ZT3 41.46 017.96 1 String_Stringabc
|
Sehr klasse! Wieder was gelernt. Der Trick mit der Kombination von "g" und einer Zahl ist allerdings hochgradig speziell für GNU sed.
Statt den Whitespace in den Strings nach den ersten vier Feldern zu verändern könnte man sich z.B. auch überlegen die ersten vier Trennzeichen zu ersetzen, also z.B.:
| $ awk -F="" '{for(i=1;i<=4;++i){sub(/ +/, "\t", $0)}; print}' data
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3 String4 String5
ZT3 41.46 017.96 1 String Stringabc
|
Und dann in awk mit einem Tab als Feldtrenner arbeiten.
Auch eine interessante Möglichkeit. Ich muss aber sagen, meine Lösung mit awk gefällt mir besser, weil sie den Feldtrenner erhält und man direkt awk -typisch weiter arbeiten kann.
|
sappy
(Themenstarter)
Anmeldungsdatum: 24. Januar 2012
Beiträge: 282
|
Klasse! Habt alle vielen Dank! Das hilft ungemein.
Hab die sed und awk Varianten getestet. Klappt prima. Nur noch mal zum Schluss zum Verständnis: Warum checkt er das nicht mit dem "\b"? Ich hab irgendwie gefunden das es einer Wortgrenze entspricht. Also: wenn das Zeichen links ein "Wort" und das Zeichen rechts ein "Nicht-Wort" ist bzw. umgekehrt.
Das wäre dann in dem Falle wohl doch nicht passend.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Er sucht damit nach genau einem Leerzeichen zwischen den Wörtern und die Ersetzung macht er bei allen Treffern - da in deinen Daten nicht nur die Strings am Ende durch einzelne Leerzeichen getrennt sind, führt das nicht zum gewünschten Ergebnis:
| $ sed 's/\b\s\b/_/g' data
ZT1_40.47 096.35 432_String1
ZT2_01.46 007.13 44_String2
ZT3_18.11 039.26 981_String3_String4_String5
ZT3_41.46 017.96 1_String_Stringabc
|
Und mit den beiden sed-Befehlen aus dem ersten Post in diesem Thread brichst du erst alles auf ein Leerzeichen zwischen den Wörtern herunter und ersetzt die Leerzeichen dann durch Unterstriche:
| $ sed 's/ */ /g' data
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3 String4 String5
ZT3 41.46 017.96 1 String Stringabc
$ sed 's/ */ /g' data | sed 's/\b\s\b/_/g'
ZT1_40.47_096.35_432_String1
ZT2_01.46_007.13_44_String2
ZT3_18.11_039.26_981_String3_String4_String5
ZT3_41.46_017.96_1_String_Stringabc
|
Ich mache solche Transformationen gerne mit Python 3 - das sähe dann z.B. so aus, wenn man sowohl einen einheitlichen Feldtrenner als auch Unterstriche zwischen den Strings haben wollte - dem Skript kann man entweder die Daten auf stdin oder die Dateinamen als Argumente übergeben:
| #!/usr/bin/env python3
import fileinput
for line in fileinput.input():
f1, f2, f3, f4, *strings = line.rstrip().split()
print(f1, f2, f3, f4, '_'.join(strings), sep='\t')
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
seahawk1986 schrieb:
Ich mache solche Transformationen gerne mit Python 3
Dann müssen wir natürlich auch eine Ruby-Lösung haben. 😉 | $ ruby -nae 's = $F.slice!(4..-1);$F << s.join("_");p $F' data
["ZT1", "40.47", "096.35", "432", "String1"]
["ZT2", "01.46", "007.13", "44", "String2"]
["ZT3", "18.11", "039.26", "981", "String3_String4_String5"]
["ZT3", "41.46", "017.96", "1", "String_Stringabc"]
|
oder so | $ ruby -nae '$F[4..-1] = $F[4..-1].join("_");p $F' data
["ZT1", "40.47", "096.35", "432", "String1"]
["ZT2", "01.46", "007.13", "44", "String2"]
["ZT3", "18.11", "039.26", "981", "String3_String4_String5"]
["ZT3", "41.46", "017.96", "1", "String_Stringabc"]
|
Das Ergebnisarray steckt in $F. Na gut, gute Nacht erst mal.
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17552
Wohnort: Berlin
|
seahawk1986 schrieb: user_unknown schrieb: | cat file | sed -r "s/^(.{17}[^ ]*)( [^ ]*)/\1\2_/g"
|
Mit sed ginge das auch noch kürzer:
| $ sed 's/ \+/_/g5' data
ZT1 40.47 096.35 432 String1
ZT2 01.46 007.13 44 String2
ZT3 18.11 039.26 981 String3_String4_String5
ZT3 41.46 017.96 1 String_Stringabc
|
Den kannte ich noch gar nicht. Ist diese Zahl hinter g eine neuere Errungenschaft (bei mir ist es ca. 20 Jahre her, dass ich sed gelernt habe) oder ist das von Beginn unter meinem Radar durchgetaucht? Das heißt einfach "ab dem 5. Vorkommen"? Gibt es analog ein "bis zum 5. Vorkommen", vielleicht 5g?
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Das ist eine Besonderheit von GNU sed (https://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command): The s command can be followed by zero or more of the following flags: g Apply the replacement to all matches to the regexp, not just the first. number Only replace the numberth match of the regexp.
[...] interaction in s command Note: the POSIX standard does not specify what should happen when you mix the g and number modifiers, and currently there is no widely agreed upon meaning across sed implementations. For GNU sed, the interaction is defined to be: ignore matches before the numberth, and then match and replace all matches from the numberth on.
5g und g5 haben den selben Effekt. Für die ersten n Matches könnte man z.B. einen Ersetzungsbefehl ohne g Flag n-mal wiederholen.
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17552
Wohnort: Berlin
|
Ja, die Seite sieht sehr informativ aus, sollte ich mal durchgehen. Hoffentlich endet das nicht wieder in einem Tab, der 3,5 Jahre offensteht... ☺
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
user_unknown schrieb: Ja, die Seite sieht sehr informativ aus, sollte ich mal durchgehen. Hoffentlich endet das nicht wieder in einem Tab, der 3,5 Jahre offensteht... ☺
Solche Tabs kenne ich auch. ☺ Findest Du auch über info sed .
|