Verbinden Sie Textzeilen mit wiederholtem Anfang


7

Ich habe eine lange Textdatei (eine Tab-Datei für den Stardict-Editor), die aus Zeilen im folgenden Format besteht:

word1  some text
word1  some other text
word2  more text
word3  even more

und möchte es umwandeln

word1  some text<br>some other text
word2  more text
word3  even more

Dies bedeutet, dass nachfolgende Zeilen (die Datei ist sortiert), die mit demselben Wort beginnen, zu einer einzigen zusammengeführt werden sollten (hier werden die Definitionen durch getrennt <br>). Linien mit gleichem Anfang können auch häufiger als nur zweimal erscheinen. Das Zeichen, das Wort und Definition trennt, ist ein Tabulatorzeichen und in jeder Zeile eindeutig. word1, word2, word3Sind natürlich Platzhalter für etwas willkürlich (außer Tab und Zeilenumbrüchen) , die ich weiß nicht im Voraus.

Ich kann mir einen längeren Perl-Code vorstellen, der dies tut, aber ich frage mich, ob es in Perl eine kurze Lösung oder etwas für die Befehlszeile gibt. Irgendwelche Ideen?

Antworten:


3

Dies ist das Standardverfahren für awk

awk '
{
  k=$2
  for (i=3;i<=NF;i++)
    k=k " " $i
  if (! a[$1])
    a[$1]=k
  else
    a[$1]=a[$1] "<br>" k
}
END{
  for (i in a)
    print i "\t" a[i]
}' long.text.file

Wenn die Datei nach dem ersten Wort in der Zeile sortiert ist, kann das Skript einfacher sein

awk '
{
  if($1==k)
    printf("%s","<br>")
  else {
    if(NR!=1)
      print ""
    printf("%s\t",$1)
  }
  for(i=2;i<NF;i++)
    printf("%s ",$i)
  printf("%s",$NF)
  k=$1
}
END{
print ""
}' long.text.file

Oder nur bash

unset n
while read -r word definition
do
    if [ "$last" = "$word" ]
    then
        printf "<br>%s" "$definition"
    else 
        if [ "$n" ]
        then
            echo
        else
            n=1
        fi
        printf "%s\t%s" "$word" "$definition"
        last="$word"
     fi
done < long.text.file
echo

Sieht gut aus! Nur wenn ich es ausführe, enthält die Ausgabe keine Tabulatorzeichen. Zwischen jedem Wort und seiner Definition sollte eines stehen.
Highsciguy

@highsciguy Habe beide Skripte bearbeitet.
Costas

Costas, Ihr Code ändert die Daten; nicht nur das TAB (wie bereits in einem vorherigen Kommentar erwähnt), sondern auch Folgen von Leerzeichen. Dies ist wahrscheinlich unerwünschtes Verhalten.
Janis

3
perl -p0E 'while(s/^((.+?)\t.*)\n\2\t/$1<br>/gm){}' 

(Es dauert 2 Sekunden, um ein Wörterbuch mit 23 MB und 1,5 Zeilen in meinem 6 Jahre alten Laptop zu verarbeiten.)


Ich kann bestätigen, dass dies viel schneller ist als die sed-Lösung . Bei einer Datei wurde die Ausführungszeit von ca. 8 Minuten auf unter eine Sekunde verringert.
PCworld

3

Mit sed:

sed '$!N;/^\([^\t]*\t\)\(.*\)\(\n\)\1/!P;s//\3\1\2<br>/;D' <<\IN
word1  some text
word1  some other text
word1  some other other text
word2  more text
word3  even more
word3  and still more
IN

(Hinweis: Bei vielen seds ist das obige \tEscape ungültig und <tab>an seiner Stelle sollte ein wörtliches Zeichen verwendet werden.)

Und wenn Sie GNU haben sed, können Sie es etwas einfacher schreiben:

sed -E '$!N;/^(\S+\t)(.*)\n\1/!P;s//\n\1\2<br>/;D' <infile

Es funktioniert, indem die Eingabe beim Lesen schrittweise gestapelt wird. Wenn zwei aufeinanderfolgende Zeilen nicht mit derselben Nicht-Leerzeichenfolge beginnen, wird die erste davon Pgedruckt. Andernfalls wird die dazwischenliegende neue Zeile an den Kopf der Zeile verschoben und die unmittelbar darauf folgende übereinstimmende Zeichenfolge (einschließlich der Registerkarte) durch die Zeichenfolge ersetzt <br>.

Beachten Sie, dass die Stapel hier verwendete Methode könnte Auswirkungen auf die Leistung haben , wenn die Linie , dass sedzusammenbaut sehr lange wächst. Wenn es länger als 8 KB wächst, überschreitet es die von POSIX angegebene Mindestgröße des Musterspeicherpuffers.

Unabhängig davon, welche der beiden Möglichkeiten aufgetreten ist, wird als letztes sed Dbis zum ersten vorkommenden \nEwline-Zeichen im Musterraum eletiert und beginnt mit dem, was übrig bleibt. Wenn also zwei aufeinanderfolgende Zeilen nicht mit identischen Zeichenfolgen beginnen, wird die erste gedruckt und gelöscht, andernfalls wird die Ersetzung durchgeführt und das Delete löscht nur die \newline, die sie zuvor getrennt hat.

Und so druckt der obige Befehl:

word1  some text<br>some other text<br>some other other text
word2  more text
word3  even more<br>and still more

Ich habe oben eine <<\HERE_DOCEingabe verwendet, aber Sie sollten wahrscheinlich alles von <<\INon ablegen und </path/to/infilestattdessen verwenden.


Entschuldigung, was ist die Option `sed -E '?
JJoao

2
@JJoao - Entschuldigung für was? Die -EOption zu GNU sedist eine undokumentierte Alternative zur Verwendung -r, mit der Ausnahme, dass 1. sie sinnvoller ist (was sollte eigentlich -rjemals bedeuten?) , 2. Sie funktioniert auch in BSD sed, 3. POSIX hat eine geplante Änderung, die angewendet werden soll die nächste Version der Spezifikation, die offiziell -Eals die richtige Syntax segnet , um erweiterte reguläre Ausdrücke in a zu ermöglichen sed.
Mikeserv

2

Dies ist in der Tat Standard für awk. Hier ist eine knappe Lösung, die die Betriebsdaten nicht ändert:

awk 'BEGIN { FS="\t" }
     $1!=key { if (key!="") print out ; key=$1 ; out=$0 ; next }
     { out=out"<br>"$2 }
     END { print out }'

Es ist. Da das OP sagte, dass es eine eindeutige TAB zwischen dem "Wort" und dem Rest der Daten gibt. Achten Sie auf die FSDefinition!
Janis

Nein, ich ordne das Ganze $0(das das TAB enthält) zu out.
Janis

Die beiden Kommentare von mir sehen jetzt etwas seltsam aus, weil derjenige, auf den ich geantwortet habe, seine Kommentare gelöscht hat. Zusammenfassen; Das Wesentliche ist, dass die vorgestellte Lösung alle Anforderungen berücksichtigt (einschließlich der Aufrechterhaltung von Daten und TAB-Separatoren). - Das gesagt; Ich frage mich, warum es (neben dem Upvote) auch ein Downvote gab. Würde der Wähler bitte eine Begründung dafür hinzufügen.
Janis

Scheint die kürzeste zu sein, aber aus irgendeinem Grund schlägt es in vielen Zeilen fehl. Vielleicht, weil die Datei viele utf-8-Sonderzeichen enthält?
Highsciguy

Mit einem nicht zu alten GNU awk hätten Sie UTF-8 unterstützt. Ist Ihr Gebietsschema richtig eingestellt? (So ​​etwas wie LC_ALL=en_US.UTF-8.) Andernfalls wäre es hilfreich, einige der Beispielzeilen zu erhalten, in denen Probleme auftreten. Es kann auch sein, dass das Datenformat nicht überall so ist, wie Sie es erwartet haben. Ihr Feedback wird geschätzt, um zu verfolgen, wo in der Verarbeitungskette das Problem liegt. Irgendwo wird ein Fix notwendig sein.
Janis

1

In Python:

import sys

def join(file_name, join_text):
    prefix = None
    current_line = ''
    for line in open(file_name):
        if line and line[-1] == '\n':
            line = line[:-1]
        try:
            first_word, rest = line.split('\t', 1)
        except:
            first_word = None  # empty line or one without tab
            rest = line
        if first_word == prefix:
            current_line += join_text + rest
        else:
            if current_line:
                print current_line
            current_line = line
            prefix = first_word

    if current_line:  # do the last line(s)
        print current_line


join(sys.argv[2], sys.argv[1])

Dies erwartet das Trennzeichen ( <br>) als erstes Argument für das Programm und den Dateinamen als zweites Argument


-1

Versuchen

awk 'BEGIN { before="" } 
{ if ( $1 == before ) { $1="" ; printf "<br>%s",$0 ; } 
  else { printf "\n%s",$0 ;} ; before=$1 ; } 
END { printf "\n"  ;}'

die mit Ihrer Eingabe geben

word1  some text<br> some other text
word2  more text
word3  even more

tha awk merke dir im Grunde das erste Wort in der vorherigen Zeile und drucke keine neue Zeile.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.