2016-03-22 26 views
3

Ich habe eine Pipe-getrennte Datei, wo einige Werte/Datensätze in einer der Spalten enthalten Pipes in den Wert selbst so dass es scheint, als ob es mehr Spalten als es tatsächlich sind - Beachten Sie wie "Spalte 8" (fett) hat in der Mitte Rohre. Dies sollte eigentlich als "| col u lm n8 |" mit Räumen anstelle der Rohre.sed zum Suchen und Ersetzen von Chactern zwischen zwei Strings

column1|column2|column3|column4|column5|column6|column7|**col|u|lm|n8**|2016|column10|column11|column12|column13|column14| 

Ich muss diese Pipes innerhalb der Spalte8 durch Leerzeichen ersetzen.

Gute daran ist, dass die Daten in column7 und column9 (| 2016) über die Datei die gleiche ist, damit ich bin in der Lage einen Sed wie dieser

sed 's/|/ /7g;s/.\(|2016\)/|\1/' 

jedoch zu tun, dass alle Rohre ändern wird nach das 7. Rohr bis zum Ende der Linie. Meine Frage ist, wie kann ich erreichen, dass alle Pipes nach der 7. Pipe in Leerzeichen umgewandelt werden, aber bis zur Spalte "| 2016"?

Danke

+0

"en" ist die 7. Spalte also | en | col | u | m | n8 | 2016 ist Spalten 7, 8 und 9 mit Spalte 8 mit den zusätzlichen Leitungen – mk97

+0

Wenn die Datei nur eine Zeile hätte, könnten Sie tun 'col8 = $ (sed 's/\ ([^ |] * | \) \ {7 \} \ (. * \) | 2016. */\ 2 /' Datei); echo "Debug-Zeile: col8 = $ {col8}, behoben $ {col8 // | /}"; sed 's/^ \ (\ ([^ |] * | \) \ {7 \} \). * | 2016/\ 1' "$ {col8 // | /} '' | 2016/'Datei' . Dies ist für Ihr Problem mit einer normalen Datei nicht hilfreich, da Sie eine langwierige While-Schleife erstellen müssen. –

Antwort

1

Mit Ihrer Abtastwerteingang das für mich arbeitet mit GNU 4.2.2 sed:

sed -r ':start s/(column7.)([^\|]*?)\|(.*?.2016)/\1\2 \3/; t start' file 

Es Rohre zwischen column7. und .2016 ersetzt, ein Rohr zu einem Zeitpunkt. Nach einer erfolgreichen Substitution, die tgotos zurück auf die :start Bezeichnung für einen anderen Substitutionsversuch.

+1

Dies funktioniert nicht in nicht-GNU sed. (Ich weiß nicht, ob es in GNU sed funktioniert.) – ghoti

+0

das für mich gearbeitet hat aber auch die rohre mit einem platz für die "2016" spalte ersetzt, muss diese rohre behalten ... also war die ausgabe | column7 | col um n8 2016 – mk97

+0

hast du das '.' vor gestellt des 2016 in '(. *?. 2016)'? –

1

Aufbauend auf Lars zur Verfügung gestellt, die folgende Beschreibung ist in allen Versionen von sed arbeiten:

sed -e ':b' -e 's/\(|column7|\)\(.*\)|\(.*|2016|\)/\1\2 \3/' -e 'tb' inputfile 

Dies funktioniert, indem wiederholt eingebettet Separatoren, bis die Ersatzmuster ersetzen kann nicht gefunden werden. Seds t-Befehl verzweigt nur dann auf die :b-Bezeichnung, wenn die vorherige Ersetzung erfolgreich war.

Wir verwenden die mehr klassischen BRE sowohl für die Kompatibilität und um zu vermeiden, sed interpretieren die vertikalen Balken als "oder" Separatoren in ERE.

Das sed-Skript ist in einzelne -e-Optionen unterteilt, da einige Varianten von sed Label-Verweise auf "am Ende der Zeile" erfordern, und die Beendigung des Arguments -e als gleichwertig mit dem Ende des Linie. (GNU sed erfordert dies nicht, aber eine Reihe anderer seds tun.)

Aber wie anubhava in Kommentaren hervorhebt, ist dies ein minderwertiger Ansatz, weil es fehlschlagen wird, wenn die Eingabedaten eine zweite 2016| irgendwo in der rechts von der Spalte 9.

eine alternative Lösung, wenn Sie bash laufen lassen, könnte die Felder in ein Array zu platzieren, dann Elemente verschmelzen:

#!/usr/bin/env bash 

input="column1|column2|column3|column4|column5|column6|column7|**col|u|lm|n8**|2016|column10|column11|column12|column13|column14|" 

IFS=\| read -a a <<< "$input" 

while [ "${a[8]}" != "2016" ]; do 
    a[7]="${a[7]} ${a[8]}" # merge elements 
    unset a[8]    # delete merged element 
    a=("${a[@]}")   # renumber array 
done 

printf "%s|" "${a[@]}" 

Beachten Sie, dass Arrays mit dem Index starten bash 0 durch Standard. Das readarray Built-in erlaubt es Ihnen, einen alternativen Startpunkt für Ihren Index anzugeben (-O), aber dieser Built-In begann mit der Bash-Version 4, und es gibt immer noch eine Menge von Version 3 in freier Wildbahn. Also für Portabilität, read -a ist es.

Beachten Sie auch, dass das obige Skript ohne weitere Fehlerprüfung in eine Endlosschleife geht, wenn Sie aus irgendeinem Grund in Ihren Eingabedaten kein "2016" -Feld haben. :-)

+0

Das wird brechen, wenn ein anderes '| 2016' auf RHS von' | 2016' von 'col | um | n8' erscheint. – anubhava

+0

Diese Lösung funktionierte nicht für mich, indem ich Spalte7 durch den tatsächlichen Wert der Spalte" en "' sed - e ': b' -e 's/\ (| en | \) \ (. * \) | \ (. * | 2016 | \)/\ 1 \ 2 \ 3 /' -e 'tb' Eingabedatei ' sind noch Rohre innerhalb des Werts der Spalte 8. – mk97

+0

@ mk97, vielleicht wäre es einfacher, die Erfahrung zu debuggen, die Sie haben, wenn wir mit echten Daten testen könnten. Das sed-Skript, das ich zur Verfügung gestellt habe, funktionierte für mich in FreeBSDs sed unter Verwendung der Beispieldaten, die in deiner Frage zur Verfügung gestellt wurden. – ghoti

0

Diese Frage hat mich wirklich interessiert ist, upvoted ich es und scheiterte es in sed oder awk

Ich versuchte es in Python und machte es zu lösen. Ich bin kein official answer aber einige Ideen :)

$cat sample.csv 
column1|column2|column3|column4|column5|column6|column7|col|u|lm|n8|2016|column10|column11|column12|column13|column14| 

Mein Code bereitstellt:

$cat test.py                                           
import re 
REGEX = ur"column7\|(.+?)\|2016+?" 

with open("sample.csv", "r") as inputs: 
    for line in inputs: 
     matches = re.findall(REGEX, line) 
     column8 = matches[0] 
     new_column8 = column8.replace("|", "") 
     print line.replace(column8, new_column8) 

Ergebnis: Hier

$python test.py                                          
column1|column2|column3|column4|column5|column6|column7|colulmn8|2016|column10|column11|column12|column13|column14| 
1

ist perl Lösung, die für Fall funktioniert sogar, wenn |2016 erscheint wieder in der Zeile:

cat file 
column1|column2|column3|column4|column5|column6|en|col|u|lm|n8|2016|column10|column11|2016| 

perl -pe 's/(en\|[^|]*|(?<!^)\G[^|]*)\|(?!2016)/$1 /g' file 

column1|column2|column3|column4|column5|column6|en|col u lm n8|2016|column10|column11|2016| 

Dieser Regex verwendet das PCRE-Konstrukt \G, das die Position am Ende der vorherigen Übereinstimmung oder den Anfang der Zeichenfolge für die erste Übereinstimmung bestätigt.

RegEx Demo

+1

Uplooted this as perl Lösung funktionierte auch, aber korrekte Antwort oben, da meine Frage für sed war ... aber danke und jeder, der geantwortet hat! – mk97

0

Mit GNU awk für die 3. arg übereinstimmen():

$ awk 'match($0,/(([^|]*[|]){7})(.*)(\|2016\|.*)/,a){gsub(/\|/," ",a[3]); $0=a[1] a[3] a[4]} 1' file 
column1|column2|column3|column4|column5|column6|column7|**col u lm n8**|2016|column10|column11|column12|column13|column14| 
0

Wenn die Datei nur eine Zeile haben, würden Sie CoL8 = $ tun konnte (sed ‚s/([^ |] |) {7} (.) | 2016. /\ 2/'Datei) echo "Debug-Zeile: col8 = $ {col8}, behoben $ {col8 // | /}" sed 's/^ (([^ |] |) {7}). * | 2016/\ 1' "$ {col8 // | /}" '| 2016 /' Datei

Wenn Sie ein eindeutiges Zeichen oder eine Zeichenfolge kennen, können Sie bei einer Datei mit mehreren Zeilen ungefähr dasselbe tun. Ich werde mk97 als eindeutige Zeichenfolge verwenden:

0

Dies könnte für Sie arbeiten (GNU sed):

sed 's/|/&\n/7;:a;ta;s/\n\(|2016|\)/\1/;s/\n|/ \n/;ta;s/\n\(.\)/\1\n/;ta' file 

anhängen eine neue Zeile an den Anfang des Feldes acht. Wenn sich der Zeilenumbruch vor Feld neun darstellt, löschen Sie ihn. Wenn auf die neue Zeile eine | folgt, ersetzen Sie die | durch ein Leerzeichen und mischen Sie die neue Zeile auf einem Zeichen. Wenn auf die neue Zeile kein | folgt, mischen Sie den Zeilenumbruch für ein Zeichen.

N.B. Bei jeder erfolgreichen Substitution Schleife an den Platzhalter :a.

Verwandte Themen