So entfernen Sie Zeilen, wenn sie genau einmal ein Zeichen enthalten

10

Ich möchte eine Zeile aus einer Datei entfernen, die ein bestimmtes Zeichen nur einmal enthält. Wenn es mehr als einmal vorhanden ist oder nicht, dann behalte die Zeile in der Datei.

Beispielsweise:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

Hier ist das Zeichen, das ich entfernen möchte, Cso, dass der Befehl Zeilen entfernen sollte FGTHDCund JUTDYCweil sie Cgenau einmal haben.

Wie kann ich das entweder sedoder machen awk?

Namz
quelle

Antworten:

20

In können awkSie das Feldtrennzeichen auf alles setzen. Wenn Sie es auf setzen C, haben Sie so viele Felder +1 wie Vorkommen von C.

Wenn Sie also sagen, awk -F'C' '{print NF}' <<< "C1C2C3"Sie erhalten 4: CCCbesteht aus 3 Cs und damit 4 Feldern.

Sie möchten Zeilen entfernen, in denen Cgenau einmal vorkommt. Wenn Sie dies berücksichtigen, möchten Sie in Ihrem Fall die Zeilen entfernen, in denen genau zwei CFelder vorhanden sind. Überspringen Sie sie einfach:

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD
fedorqui
quelle
4
Kluge Verwendung des awkFeldtrenners!
Valentin B.
interresting, wie im Standardfall (FS = ""), ignoriert führende Leerzeichen ($ 1 = das erste Nicht-Leerzeichen in der Zeile) und auch Wiederholungen (Sie können 5 Leerzeichen haben, um Feld 1 und Feld 2 zu trennen) ... Leerzeichen wird wohl speziell behandelt? ( awk 'BEGIN { print "FS={" FS"}","OFS={" OFS "}";} {printf "%d fields : ",NF; for (i=1;i<=NF;i++) {printf "{" $i "} ";}; print "" }'Um es zu sehen, kann man es tun und einige Zeilen füttern, einige mit mehreren Spces, und andere beginnen mit Leerzeichen)
Olivier Dulac
2
@OlivierDulac, ja, Speicherplatz wird speziell wie von POSIX angegeben behandelt .
Wildcard
8

sed Ansatz:

sed -i '/^[^C]*C[^C]*$/d' input

-i Option ermöglicht die direkte Änderung von Dateien

/^[^C]*C[^C]*$/- stimmt mit Zeilen überein, die Cnur einmal enthalten

d - Übereinstimmende Zeilen löschen

RomanPerekhrest
quelle
8

Dies kann erfolgen sedals:

Code:

sed '/C.*C/p;/C/d' file1

Ergebnisse:

DTHGTY
HYTRHD
HTCCYD

Wie?

  1. Ordnen Sie eine beliebige Zeile mit mindestens zwei Kopien von Cvia zu und drucken Sie sie aus/C.*C/p
  2. Löschen Sie eine Zeile mit einem CVia /C/d, einschließlich der Zeilen, die bereits in Schritt 1 gedruckt wurden
  3. Standardmäßig werden die restlichen Zeilen gedruckt
Stephen Rauch
quelle
2
Cleverer alternativer Ansatz; Ich mag das.
Wildcard
6

Dies entfernt die Linien mit genau einem Vorkommen von C.

grep -v '^[^C]*C[^C]*$' file

Der reguläre Ausdruck [^C]entspricht einem Zeichen, das nicht C (oder Zeilenumbruch) ist, und der Wiederholungsoperator (auch bekannt als Kleene-Stern) *gibt null oder mehr Wiederholungen des vorhergehenden Ausdrucks an.

Die Standardausgabe von grep(und den meisten anderen textorientierten Tools) ist die Standardausgabe. Leiten Sie zu einer neuen Datei um und verschieben Sie sie möglicherweise über die Originaldatei, wenn Sie dies wünschen. Dieselbe Regex kann für die direkte sed -iBearbeitung verwendet werden:

sed -i '/^[^C]*C[^C]*$/d' file

(Auf einigen Plattformen, insbesondere * BSD einschließlich macOS, -ierfordert die Option ein Argument wie -i ''.)

Tripleee
quelle
1
sed -i '/^[^C]*C[^C]*$/d' file- klingt wie es vorher gepostet wurde, wie denkst du, Plagiat?
RomanPerekhrest
1
In der Tat gibt es einige Überschneidungen. Ich habe mit der grepAntwort angefangen, aber sie erstreckt sich offensichtlich leicht auf die sed -iVariante. Ich habe Ihre Antwort nicht gesehen, weil ich nach vorherigen grepAntworten gesucht habe .
Tripleee
1
Es ist sicherer, nur deutlich zu vermeiden -imit sedund stattdessen in eine neue Datei umleiten und das Original mit , dass ersetzen , wenn das sedDienstprogramm ohne Fehler beendet.
Kusalananda
2
Odergrep -vx '[^C]*C[^C]*'
Stéphane Chazelas
@Kusalananda Aber dann können Sie es auch verwenden, grepweil es klarer und robuster ist (insbesondere sedhat es einen weniger informativen Exit-Code).
Tripleee
4

Das POSIX-Tool für Skriptbearbeitungen einer Datei (anstatt den geänderten Inhalt als Standardausgabe auszudrucken) ist ex.

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

Natürlich können Sie verwenden,sed -i wenn Ihre Version von Sed dies unterstützt. Beachten Sie jedoch, dass dies nicht portierbar ist, wenn Sie ein Skript schreiben, das auf verschiedenen Systemtypen ausgeführt werden soll.


David Foerster fragte in den Kommentaren:

Gibt es einen Grund, warum Sie verwenden printfund nicht echooder so etwas ex -c COMMAND?

Antwort: Ja.

Für printfvs. ist echoes eine Frage der Portabilität; siehe Warum ist printf besser als echo? Außerdem ist es einfacher, Zeilenumbrüche zwischen Befehlen mit zu verteilen printf.

Für printf ... | exvs. ex -c ...ist es eine Frage der Fehlerbehandlung. Für diesen speziellen Befehl wäre es nicht wichtig, aber im Allgemeinen ist es so; Versuchen Sie zum Beispiel Putten

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

in einem Skript. Vergleichen Sie Folgendes:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

Der erste hängt und wartet auf die Eingabe. Die zweite wird beendet, wenn EOF vom exBefehl empfangen wird , sodass das Skript fortgesetzt wird. Es gibt alternative Problemumgehungen, z. B. s///e, die jedoch nicht von POSIX angegeben werden. Ich bevorzuge die Verwendung des oben gezeigten tragbaren Formulars.

Für den gBefehl muss am Ende eine neue Zeile stehen, und ich bevorzuge es printf, die Befehle zu umbrechen, anstatt eine neue Zeile in einfache Anführungszeichen einzubetten.

Platzhalter
quelle
1
Gibt es einen Grund, warum Sie verwenden printfund nicht echooder so etwas ex -c COMMAND?
David Foerster
@ DavidFoerster, ja. Ich habe angefangen, Ihnen in Kommentaren zu antworten, aber es wurde lang, also habe ich es der Antwort hinzugefügt.
Wildcard
Danke und +1! Ich wusste von printfvs. echo(obwohl ich es normalerweise nur vorziehe, echowenn das Argument fest codiert ist), aber ich habe es bisher nicht exausgiebig verwendet .
David Foerster
2

Hier sind einige Optionen für die Verwendung von Perl.

Da Sie nur mit einem einzelnen Zeichen übereinstimmen, können Sie tr/C//(eine Übersetzung ohne Ersatz) verwenden, um die Anzahl der Übereinstimmungen von C:

perl -lne 'print if tr/C// != 1' file

Wenn Sie einer mehrstelligen Zeichenfolge oder einem regulären Ausdruck entsprechen möchten, können Sie im Allgemeinen Folgendes verwenden:

perl -lne 'print if (@m = /C/g) != 1' file

Dadurch werden die Übereinstimmungen des regulären Ausdrucks /C/geiner Liste @mzugewiesen und Zeilen gedruckt, wenn die Länge dieser Liste nicht beträgt 1.

Der -iSchalter kann hinzugefügt werden, um "an Ort und Stelle" zu bearbeiten.

Tom Fenech
quelle
2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'

quelle
Beachten Sie, dass davon ausgegangen wird sed, dass GNU t #...normalerweise zu dem #...in den meisten anderen sedImplementierungen aufgerufenen Label verzweigt .
Stéphane Chazelas
Sogar das !bist GNU sed, da Branch nichts außer einem Label oder einer Newline danach mag.
Ja, b, t, :, }(und r file, w file...) kann nicht einen Befehl nach dem sie auf der gleichen Linie haben. Sie können auch separate -eOptionen verwenden.
Stéphane Chazelas
Ihre Perl-Option erzeugt nicht die richtige Ausgabe. Ich denke, Sie haben vergessen, den gModifikator hinzuzufügen .
Tom Fenech
@ TomFenech Du bist richtig. Ich repariere das. Vielen Dank.
1

Für alle, die awkspeziell wollen , würde ich anbieten

awk '/C[^C]*C/{next}//{print}'

Überspringen Sie die Zeile, wenn sie mit dem Muster übereinstimmt, und drucken Sie sie anderweitig aus. Sie brauchen nicht wirklich {print}, Sie können einen //Standarddruck verwenden, aber ich denke, es ist klarer formuliert.

Mein erster Gedanke war, egrep -vmit dem gleichen Muster zu arbeiten, aber das beantwortet die gestellte Frage nicht wirklich.

nigel222
quelle
1
Was bringt es, irgendetwas danach zusammenzubringen {next}? Sagen Sie einfach awk '/pattern/ {next} 1'und alle Zeilen, die nicht zum Muster passen, werden gedruckt. Oder besser, awk '!/pattern/'um diese direkt auszudrucken.
Fedorqui
@fedorqui guter Punkt über !/pattern/(was mir irgendwie durch den Kopf ging), aber ich würde viel lieber eine selbsterklärende //{print}als eine kryptische sehen 1. Nehmen Sie an, dass die nächste Person die geringste Kompetenz und Geläufigkeit für die Pflege Ihres Codes besitzt, um ihn nicht ernsthaft weniger effizient oder effektiv zu machen.
nigel222