Ich habe eine Eingabedatei mit einigen Abschnitten, die mit Start- und End-Tags gekennzeichnet sind, zum Beispiel:
line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
Ich möchte eine Transformation auf diese Datei anwenden, sodass die Zeilen X, Y, Z durch einen Befehl ( nl
zum Beispiel) gefiltert werden , der Rest der Zeilen jedoch unverändert weitergeleitet wird. Beachten Sie, dass nl
(Zahlenzeilen) den Status über Zeilen hinweg akkumuliert, sodass es sich nicht um eine statische Transformation handelt, die auf jede der Zeilen X, Y, Z angewendet wird. ( Bearbeiten : Es wurde darauf hingewiesen, dass nl
in einem Modus gearbeitet werden kann, der keinen akkumulierten Status erfordert, aber ich verwende nur nl
ein Beispiel, um die Frage zu vereinfachen. In Wirklichkeit ist der Befehl ein komplexeres benutzerdefiniertes Skript. Was ich wirklich suche for ist eine allgemeine Lösung für das Problem, einen Standardfilter auf einen Unterabschnitt einer Eingabedatei anzuwenden. )
Die Ausgabe sollte folgendermaßen aussehen:
line A
line B
1 line X
2 line Y
3 line Z
line C
line D
Die Datei kann mehrere solcher Abschnitte enthalten, für die eine Umwandlung erforderlich ist.
Update 2 Ich habe ursprünglich nicht angegeben, was passieren soll, wenn es mehr als einen Abschnitt gibt, zum Beispiel:
line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@inline-code-start
line L
line M
line N
@@inline-code-end
Ich gehe davon aus, dass der Status nur innerhalb eines bestimmten Abschnitts beibehalten werden muss.
line A
line B
1 line X
2 line Y
3 line Z
line C
line D
1 line L
2 line M
3 line N
Ich halte es jedoch für richtig und in vielen Zusammenhängen nützlich, das Problem so zu interpretieren, dass der Zustand über Abschnitte hinweg beibehalten werden muss.
Update beenden 2
Mein erster Gedanke ist, eine einfache Zustandsmaschine zu bauen, die nachverfolgt, in welchem Abschnitt wir uns befinden:
#!/usr/bin/bash
while read line
do
if [[ $line == @@inline-code-start* ]]
then
active=true
elif [[ $line == @@inline-code-end* ]]
then
active=false
elif [[ $active = true ]]
then
# pipe
echo $line | nl
else
# output
echo $line
fi
done
Womit ich laufe:
cat test-inline-codify | ./inline-codify
Dies funktioniert nicht, da jeder Anruf nl
unabhängig ist und sich die Zeilennummern nicht erhöhen:
line A
line B
1 line X
1 line Y
1 line Z
line C
line D
Mein nächster Versuch war, ein FIFO zu verwenden:
#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
if [[ $line == @@inline-code-start* ]]
then
active=true
elif [[ $line == @@inline-code-end* ]]
then
active=false
elif [[ $active = true ]]
then
# pipe
echo $line > myfifo
else
# output
echo $line
fi
done
rm myfifo
Dies ergibt die korrekte Ausgabe, jedoch in der falschen Reihenfolge:
line A
line B
line C
line D
1 line 1
2 line 2
3 line 3
Es ist wahrscheinlich ein Caching im Gange.
Mache ich das falsch? Dies scheint ein ziemlich allgemeines Problem zu sein. Ich bin der Meinung, dass es eine einfache Pipeline geben sollte, die dieses Problem löst.
quelle
nl
muss nicht Zustand akkumulieren . Schauen Sie sichnl -d
und überprüfen Sie Ihreman
/info
Seiten Informationen übernl
‚s Abschnitt Trennzeichen .nl
als Beispiel Filter verwende. Ich dachte, es würde die Frage vereinfachen, indem ich die Details darüber, was genau der Filter tat, beschreibe, aber ich habe wahrscheinlich nur mehr Verwirrung gestiftet. Tatsächlich filtere ich den Unterabschnitt durch einen Code-Textmarker für einen eigens entwickelten statischen Blog-Generator. Im Moment benutze ich Gnusource-highlight
, aber das könnte sich ändern und ich könnte weitere Filter hinzufügen, wie zum Beispiel einen Formatierer.Antworten:
Ich würde mit Ihnen einverstanden - es wahrscheinlich ist ein allgemeines Problem. Einige gängige Versorgungsunternehmen verfügen jedoch über einige Einrichtungen zur Handhabung.
nl
nl
Trennt z. B. Eingaben in logische Seiten, die-d
durch ein Trennzeichen aus zwei Zeichen voneinander getrennt sind . Drei Vorkommen in einer Zeile geben den Anfang einer Überschrift an , zwei den Hauptteil und eines die Fußzeile . Es ersetzt alle in der Eingabe gefundenen Zeilen durch eine Leerzeile in der Ausgabe. Dies sind die einzigen Leerzeilen, die jemals gedruckt werdenIch habe Ihr Beispiel so geändert, dass es einen weiteren Abschnitt enthält und ihn einfügt
./infile
. So sieht es also aus:Dann habe ich folgendes ausgeführt:
nl
Es kann festgelegt werden, dass der Status auf mehreren logischen Seiten akkumuliert werden soll, dies ist jedoch nicht die Standardeinstellung. Stattdessen werden die Zeilen der Eingabe nach Stilen und nach Abschnitten nummeriert . So-ha
bedeutet die Anzahl alle Kopfzeilen und-bn
bedeutet keine Körperlinien - wie es in einem beginnt Körper Zustand.Bis ich das gelernt habe, habe ich es
nl
für jede Eingabe verwendet, aber nachdem ich gemerkt habe , dass diesnl
die Ausgabe gemäß dem Standard--d
Elimiter verzerren könnte, habe\:
ich gelernt, vorsichtiger damit umzugehen und habegrep -nF ''
stattdessen begonnen, es für nicht getestete Eingaben zu verwenden. Aber eine andere Lektion,nl
die wir an diesem Tag gelernt haben, war, dass man sie in anderer Hinsicht sehr nützlich anwenden kann - so wie diese - wenn man ihre Eingabe nur geringfügig ändert - wie ich essed
oben beschrieben habe.AUSGABE
Hier ist noch etwas mehr über
nl
- merkt man oben, wie alle Zeilen außer den nummerierten mit Leerzeichen beginnen? Beinl
Zahlenzeilen wird jeweils eine bestimmte Anzahl von Zeichen in den Kopf eingefügt. Für diese Zeilen wird keine Nummer angegeben - auch keine Leerzeichen - und der Einzug wird immer durch Einfügen von (-w
idth count +-s
eparator len) * Leerzeichen am Anfang von nicht nummerierten Zeilen erreicht. So können Sie den nicht nummerierten Inhalt exakt reproduzieren, indem Sie ihn mit dem nummerierten Inhalt vergleichen - und das mit geringem Aufwand. Wenn Sie bedenken, dassnl
die Eingabe für Sie in logische Abschnitte unterteilt wird und dass Sie beliebige-s
Zeichenfolgen am Anfang jeder Zeile einfügen können, wird es ziemlich einfach, mit der Ausgabe umzugehen:Die oben genannten Drucke ...
GNU
sed
Wenn dies
nl
nicht Ihre Zielanwendung ist, kann eine GNUsed
je nache
Übereinstimmung einen beliebigen Shell-Befehl für Sie ausführen.Oben
sed
sammelt Eingaben im Musterraum, bis genug vorhanden ist, um die Substitution erfolgreich zu bestehenT
und dieb
Ranch zurück zum:l
Abel zu beenden . Wenn es der Fall ist, ese
xecutesnl
mit Eingang als dargestellt<<
hier-Dokument für alle den Rest seines Musterraum.Der Workflow sieht folgendermaßen aus:
/^@@.*start$/!b
^
ganze Zeile$
ist!
nicht/
entspricht/
das obige Muster, dann wird esb
von dem Skript ranched und autoprinted - so von diesem Zeitpunkt an sind wir nur mit einer Reihe von Linien arbeiten , die mit dem Muster begannen.s//nl <<\\@@/
s//
Feld/
steht für die letzte Adressesed
, für die eine Übereinstimmung versucht wurde. Dieser Befehl ersetzt stattdessen die gesamte@@.*start
Zeilenl <<\\@@
.:l;N
:
Befehl definiert eine Verzweigungsbezeichnung - hier habe ich eine mit dem Namen:l
abel festgelegt. Mit demN
Befehl ext wird die nächste Eingabezeile an den Musterbereich\n
angehängt, gefolgt von einem ewline-Zeichen. Dies ist eine der wenigen Möglichkeiten, eine\n
ewline in einemsed
Musterraum zu erhalten - das\n
ewline-Zeichen ist ein sicheres Trennzeichen für einen derer, der es einesed
Weile getan hat.s/\(\n@@\)[^\n]*end$/\1/
s///
ubstitution kann nur erfolgreich sein , nachdem ein Start angetroffen wird und nur auf dem ersten eines nach dem Auftreten Ende Linie. Es wird nur auf einen Musterbereich\n
eingewirkt, in dem unmittelbar nach der letzten ewline@@.*end
das Ende$
des Musterbereichs markiert wird. Wenn es handelt, ersetzt es die gesamte übereinstimmende Zeichenfolge durch die\1
erste\(
Gruppe\)
oder\n@@
.Tl
T
Befehl est verzweigt zu einer Bezeichnung (falls angegeben), wenn seit dem letzten Ziehen einer Eingabezeile in den Musterbereich keine erfolgreiche Ersetzung erfolgt ist (wie bei w /N
) . Dies bedeutet, dass jedes Mal, wenn eine\n
ewline an einen Musterbereich angehängt wird, der nicht mit Ihrem Endbegrenzer übereinstimmt, derT
Befehl est fehlschlägt und zurück zum:l
Abel verzweigt , was dazu führtsed
, dass dieN
ext-Zeile eingezogen und eine Schleife ausgeführt wird, bis sie erfolgreich ist.e
Wenn die Ersetzung für die Endübereinstimmung erfolgreich ist und das Skript nicht nach einem fehlgeschlagenen
T
est verzweigt ,sed
wirde
ein Befehl ausgeführt, derl
wie folgt aussieht :Sie können sich davon überzeugen, indem Sie die letzte Zeile so bearbeiten, dass sie aussieht
Tl;l;e
.Es druckt:
while ... read
Ein letzter Weg, dies zu tun, und vielleicht der einfachste, ist die Verwendung einer
while read
Schleife, aber aus gutem Grund. Die Shell (insbesondere einebash
Shell) ist in der Regel ziemlich miserabel, wenn es darum geht, Eingaben in großen Mengen oder in gleichmäßigen Strömen zu verarbeiten. Dies ist auch sinnvoll - die Shell hat die Aufgabe, Zeichen für Zeichen mit der Eingabe umzugehen und andere Befehle aufzurufen, die die größeren Dinge verarbeiten können.Aber wichtiger ist über seine Rolle ist es , dass die Schale darf nicht
read
allzu viel von der Eingabe - es spezifiziert ist nicht zu puffern Eingang oder Ausgang zu dem Punkt , dass es so viel verbraucht oder Relais nicht genug Zeit , dass die Befehle es Anrufe fehlen links - auf das Byte. Soread
eignet sich hervorragend als Eingangstest - aufreturn
Informationen darüber , ob es eingegeben verbleibende und Sie sollten den nächsten Befehl aufrufen , es zu lesen - aber es ist sonst in der Regel nicht der beste Weg zu gehen.Hier ist jedoch ein Beispiel, wie man
read
und andere Befehle verwenden könnte, um Eingaben synchron zu verarbeiten:Das erste, was bei jeder Iteration passiert, ist das
read
Ziehen einer Linie. Wenn es erfolgreich ist , bedeutet es die Schleife noch nicht EOF getroffen und so in dercase
es entspricht ein Starttrennzeichen desdo
Block wird sofort ausgeführt. Ansonstenprintf
druckt$line
es ausread
undsed
wird aufgerufen.sed
wirdp
jede Zeile rucken , bis er die Begegnungen Start marker - wenn esq
Eingang ganz UITS. Der-u
Schalter nbuffered ist für GNU erforderlich,sed
da er ansonsten ziemlich gierig puffern kann, andere POSIXssed
sollten jedoch - je nach Spezifikation - ohne besondere Berücksichtigung funktionieren - solange<infile
es sich um eine reguläre Datei handelt.Wenn die ersten
sed
q
UITS führt der Schale , die dendo
Block der Schleife - das eine andere Anrufe ,sed
die jede Zeile druckt , bis er die Begegnungen Ende Marker. Es leitet seine Ausgabe an weiterpaste
, da es Zeilennummern jeweils in einer eigenen Zeile ausgibt . So was:paste
Fügt diese dann zu:
Zeichen zusammen, und die gesamte Ausgabe sieht wie folgt aus:Dies sind nur Beispiele - alles kann entweder im Test oder in den do-Blöcken hier getan werden, aber das erste Dienstprogramm darf nicht zu viel Eingabe verbrauchen.
Alle beteiligten Versorgungsunternehmen lesen dieselbe Eingabe - und drucken ihre Ergebnisse aus - jeweils für sich. Diese Art der Sache kann schwierig sein , den Dreh zu bekommen - weil verschiedene Dienstprogramme mehr als andere puffert - aber Sie können in der Regel verlassen sich auf
dd
,head
undsed
das Richtige zu tun (obwohl, für GNUsed
, können Sie die cli-Schalter benötigen) und Darauf sollten Sie sich immer verlassen könnenread
- denn es ist von Natur aus sehr langsam . Aus diesem Grund wird es in der obigen Schleife nur einmal pro Eingabeblock aufgerufen.quelle
sed
Beispiel getestet, das Sie angegeben haben, und es funktioniert, aber ich habe WIRKLICH Probleme, die Syntax zu verstehen. (mein sed ist ziemlich schwach und ist normalerweise auf s / findthis / replacethis / g beschränkt. Ich werde mich anstrengen müssen, um sed wirklich zu verstehen.)Eine Möglichkeit besteht darin, dies mit dem vim-Texteditor zu tun. Es kann beliebige Abschnitte durch Shell-Befehle leiten.
Eine Möglichkeit, dies zu tun, ist die Verwendung von Zeilennummern
:4,6!nl
. Dieser ex-Befehl wird nl in den Zeilen 4 bis 6 einschließlich ausführen, um das zu erreichen, was Sie für Ihre Beispieleingabe wünschen.Eine andere, interaktivere Möglichkeit besteht darin, die entsprechenden Zeilen mit dem Zeilenauswahlmodus (Umschalt-V) und den Pfeiltasten oder der Suche auszuwählen und dann zu verwenden
:!nl
. Eine vollständige Befehlssequenz für Ihre Beispieleingabe könnte seinDies ist nicht sehr gut für die Automatisierung geeignet (Antworten mit zB sed sind besser dafür), aber für einmalige Bearbeitungen ist es sehr nützlich, nicht auf 20-zeilige Shellscripts zurückgreifen zu müssen.
Wenn Sie nicht mit vi (m) vertraut sind, sollten Sie zumindest wissen, dass Sie nach diesen Änderungen die Datei mit speichern können
:wq
.quelle
HOME=$(pwd) vim -c 'call Mf()' f
. Wenn Sie xargs verwenden, können Sie gvim auf einem dedizierten xserver verwenden, um eine Beschädigung Ihres tty zu vermeiden (vnc ist unabhängig von der Grafikkarte und kann überwacht werden).Die einfachste Lösung, die ich mir vorstellen kann, ist,
nl
die Zeilen nicht zu verwenden, sondern selbst zu zählen:Sie führen es dann in der Datei aus:
quelle
Wenn Sie den gesamten Codeblock an eine einzelne Prozessinstanz senden möchten, können Sie die Zeilen akkumulieren und die Weiterleitung verzögern, bis Sie das Ende des Codeblocks erreichen:
Dies erzeugt für eine Eingabedatei, die den Testfall dreimal wiederholt, Folgendes:
Etwas anderes zu tun mit dem Codeblock, beispielsweise umkehren und dann Nummer, gerade Rohr es durch etwas anderes:
echo -E "${acc:1}" | tac | nl
. Ergebnis:Oder Wortzahl
echo -E "${acc:1}" | wc
:quelle
Bearbeiten hat eine Option zum Definieren eines vom Benutzer bereitgestellten Filters hinzugefügt
Standardmäßig ist der Filter "nl". Um den Filter zu ändern, verwenden Sie die Option "-p" mit einem vom Benutzer angegebenen Befehl:
oder
Dieser letzte Filter gibt Folgendes aus:
Update 1 Die Verwendung von IPC :: Open2 hat Skalierungsprobleme: Wenn die Puffergröße überschritten wird, kann dies blockieren. (in meiner Maschine puffert die Pipe, wenn 64K 10_000 x "Linie Y" entsprechen).
Wenn wir größere Dinge brauchen (brauchen wir mehr die 10000 "Linie Y"):
(1) installieren und verwenden
use Forks::Super 'open2';
(2) oder ersetzen Sie die Funktionspipette durch:
quelle
$/
und dass
Flag) verarbeiten und die Verwendung dese
Flags, um den eigentlichen Aufruf des externen Befehls durchzuführen . Ich mag das zweite Beispiel (ASCII-Kunst) wirklich!/s
= ("." bedeutet(.|\n)
);$/
Definiert das Register-Trennzeichen neu.Das ist ein Job für awk.
Wenn das Skript die Startmarkierung sieht, wird darauf hingewiesen, dass es mit dem Piping beginnen soll
nl
. Wenn diepipe
Variable true (ungleich null) ist, wird die Ausgabe an dennl
Befehl weitergeleitet. Wenn die Variable falsch ist (nicht gesetzt oder Null), wird die Ausgabe direkt gedruckt. Der Pipe-Befehl wird beim ersten Auftreten des Pipe-Konstrukts für jede Befehlszeichenfolge verzweigt. Nachfolgende Auswertungen des Pipe-Betreibers mit demselben String verwenden die vorhandene Pipe erneut. Ein anderer String-Wert würde eine andere Pipe erzeugen. Dieclose
Funktion schließt die Pipe für die angegebene Befehlszeichenfolge.Dies ist im Wesentlichen die gleiche Logik wie Ihr Shell-Skript, das eine Named Pipe verwendet, aber viel einfacher zu formulieren und die Logik zum Schließen richtig zu machen. Sie müssen die Pipe zum richtigen Zeitpunkt schließen, damit der
nl
Befehl beendet und die Puffer geleert werden. Ihr Skript schließt die Pipe tatsächlich zu früh: Die Pipe wird geschlossen, sobald die ersteecho $line >myfifo
Ausführung abgeschlossen ist. Dernl
Befehl sieht jedoch nur das Ende der Datei, wenn er vor der nächsten Ausführung des Skripts eine Zeitscheibe erhältecho $line >myfifo
. Wenn Sie ein großes Datenvolumen hatten oder wenn Siesleep 1
nach dem Schreiben hinzufügenmyfifo
, werden Sie feststellen, dassnl
nur die erste Zeile oder das erste schnelle Zeilenbündel verarbeitet wird. Dann wird es beendet, da das Ende der Eingabe angezeigt wird.Wenn Sie Ihre Struktur verwenden, müssen Sie das Rohr offen halten, bis Sie es nicht mehr benötigen. Sie benötigen eine einzige Ausgabeumleitung in die Pipe.
(Ich habe auch die Gelegenheit genutzt, korrekte Anführungszeichen und dergleichen hinzuzufügen - siehe Warum verschluckt sich mein Shell-Skript an Leerzeichen oder anderen Sonderzeichen? )
Wenn Sie das tun, können Sie auch eine Pipeline anstelle einer Named Pipe verwenden.
quelle
do
. (Ich habe nicht den Repräsentanten hier, um eine kleineOK, zuerst; Ich habe verstanden, dass Sie nicht nach einer Möglichkeit suchen, die Zeilen in Abschnitten Ihrer Datei zu nummerieren. Da Sie kein konkretes Beispiel für Ihren Filter angegeben haben (außer
nl
), nehmen wir an, dass dies der Fall istdh konvertieren Sie Text in Großbuchstaben; also für eine Eingabe von
Sie möchten eine Ausgabe von
Hier ist meine erste Annäherung an eine Lösung:
Die Leerzeichen vor den
@@
Zeichenfolgen und am Ende der letzten Zeile sind Tabulatoren. Bitte beachten Sie, dass ichnl
für meine eigenen Zwecke benutze . (Natürlich mache ich das, um Ihr Problem zu lösen , aber nicht, um eine Ausgabe mit Zeilennummer zu erhalten.)Dadurch werden die Zeilen der Eingabe nummeriert, sodass wir sie an den Abschnittsmarkierungen aufteilen und später wieder zusammensetzen können. Der Hauptteil der Schleife basiert auf Ihrem ersten Versuch unter Berücksichtigung der Tatsache, dass die Abschnittsmarkierungen Zeilennummern aufweisen. Die Eingabe wird in zwei Dateien aufgeteilt:
file0
(inaktiv; nicht in einem Abschnitt) undfile1
(aktiv; in einem Abschnitt). So sehen sie für die obige Eingabe aus:Dann durchlaufen wir
file1
(was die Verkettung aller Zeilen in Abschnitten ist) den Großschreibungsfilter. kombiniere das mit den ungefilterten Out-of-Section-Linien; sortieren, um sie wieder in ihre ursprüngliche Reihenfolge zu bringen; und entfernen Sie dann die Zeilennummern. Dies erzeugt die Ausgabe, die oben in meiner Antwort angezeigt wird.Dies setzt voraus, dass Ihr Filter die Zeilennummern unberührt lässt. Wenn dies nicht der Fall ist (z. B. wenn Zeichen am Zeilenanfang eingefügt oder gelöscht werden), kann dieser allgemeine Ansatz meines Erachtens weiterhin verwendet werden, erfordert jedoch eine etwas schwierigere Codierung.
quelle
nl
erledigt dort bereits den größten Teil der Arbeit - dafür gibt es die-d
Elimiter-Option.Ein Shell-Skript, das sed verwendet, um Teile von nicht abgegrenzten Zeilen auszugeben und Teile von abgegrenzten Zeilen in ein Filterprogramm einzufügen:
Ich schrieb dieses Skript in eine Datei mit dem Namen detagger.sh und verwenden es als so:
./detagger.sh infile.txt
. Ich habe eine separate filter.sh-Datei erstellt, um die Filterfunktionalität in der folgenden Frage nachzuahmen:Der Filtervorgang kann jedoch im Code geändert werden.
Ich habe versucht, der Idee einer generischen Lösung zu folgen , damit Operationen wie die Nummerierung von Zeilen keine zusätzliche / interne Zählung erfordern. Das Skript führt eine rudimentäre Überprüfung durch, um festzustellen, ob die Demarkator-Tags paarweise vorliegen und verschachtelte Tags überhaupt nicht ordnungsgemäß verarbeitet werden.
quelle
Vielen Dank für all die tollen Ideen. Ich habe meine eigene Lösung gefunden, indem ich den Unterabschnitt in einer temporären Datei nachverfolgt und alles auf einmal an meinen externen Befehl weitergeleitet habe. Dies ist sehr ähnlich zu dem, was Supr vorgeschlagen hat (jedoch mit einer Shell-Variablen anstelle einer temporären Datei). Auch die Idee, sed zu verwenden, gefällt mir sehr gut, aber die Syntax für diesen Fall scheint für mich etwas übertrieben zu sein.
Meine Lösung:
(Ich benutze
nl
nur als Beispiel Filter)Ich würde es vorziehen, mich nicht mit der Verwaltung der temporären Dateien befassen zu müssen, aber ich verstehe, dass Shell-Variablen ziemlich niedrige Größenbeschränkungen haben können, und ich kenne kein bash-Konstrukt, das wie eine temporäre Datei funktionieren würde, aber automatisch verschwindet, wenn das Prozess endet.
quelle
M
,N
undO
würde zu nummerieren4
,5
und6
. Das macht das nicht. Meine Antwort ist (abgesehen von der Tatsache, dass es in seiner aktuellen Inkarnation nichtnl
als Filter funktioniert ). Wenn Sie mit dieser Antwort die gewünschte Ausgabe erhalten, was haben Sie dann mit "Zustand über mehrere Zeilen hinweg akkumulieren" gemeint? Meinten Sie, dass Sie den Status nur in jedem Abschnitt, nicht aber zwischen (übergreifenden) Abschnitten beibehalten möchten? (Warum haben Sie in Ihre Frage kein Beispiel mit mehreren Abschnitten aufgenommen?)nl -p
zu bekommenM,N,O==4,5,6
.