Filtern oder leiten Sie bestimmte Abschnitte einer Datei


14

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 ( nlzum 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 nlin einem Modus gearbeitet werden kann, der keinen akkumulierten Status erfordert, aber ich verwende nur nlein 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 nlunabhä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.


nlmuss nicht Zustand akkumulieren . Schauen Sie sich nl -dund überprüfen Sie Ihre man/ infoSeiten Informationen über nl‚s Abschnitt Trennzeichen .
mikeserv

nl ist nur ein Beispiel. In meinem Fall führe ich ein benutzerdefiniertes Skript anstelle von nl aus.
James Scriven

In diesem Fall klären Sie bitte, was Ihr Skript tut.
Terdon

Ich habe in der Frage klargestellt, dass ich nur nlals 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 Gnu source-highlight, aber das könnte sich ändern und ich könnte weitere Filter hinzufügen, wie zum Beispiel einen Formatierer.
James Scriven

Antworten:


7

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

nlTrennt z. B. Eingaben in logische Seiten, die -ddurch 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 werden

Ich habe Ihr Beispiel so geändert, dass es einen weiteren Abschnitt enthält und ihn einfügt ./infile. So sieht es also aus:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

Dann habe ich folgendes ausgeführt:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nlEs 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 -habedeutet die Anzahl alle Kopfzeilen und -bnbedeutet keine Körperlinien - wie es in einem beginnt Körper Zustand.

Bis ich das gelernt habe, habe ich es nlfür jede Eingabe verwendet, aber nachdem ich gemerkt habe , dass dies nldie Ausgabe gemäß dem Standard- -dElimiter verzerren könnte, habe \:ich gelernt, vorsichtiger damit umzugehen und habe grep -nF ''stattdessen begonnen, es für nicht getestete Eingaben zu verwenden. Aber eine andere Lektion, nldie 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 es sedoben beschrieben habe.

AUSGABE

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

Hier ist noch etwas mehr über nl- merkt man oben, wie alle Zeilen außer den nummerierten mit Leerzeichen beginnen? Bei nlZahlenzeilen 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 ( -width count + -separator 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, dass nldie Eingabe für Sie in logische Abschnitte unterteilt wird und dass Sie beliebige -sZeichenfolgen am Anfang jeder Zeile einfügen können, wird es ziemlich einfach, mit der Ausgabe umzugehen:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

Die oben genannten Drucke ...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

GNU sed

Wenn dies nlnicht Ihre Zielanwendung ist, kann eine GNU sedje nach eÜbereinstimmung einen beliebigen Shell-Befehl für Sie ausführen.

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

Oben sedsammelt Eingaben im Musterraum, bis genug vorhanden ist, um die Substitution erfolgreich zu bestehen Tund die bRanch zurück zum :lAbel zu beenden . Wenn es der Fall ist, es executes nlmit Eingang als dargestellt <<hier-Dokument für alle den Rest seines Musterraum.

Der Workflow sieht folgendermaßen aus:

  1. /^@@.*start$/!b
    • wenn eine ^ganze Zeile $ist !nicht /entspricht /das obige Muster, dann wird es bvon dem Skript ranched und autoprinted - so von diesem Zeitpunkt an sind wir nur mit einer Reihe von Linien arbeiten , die mit dem Muster begannen.
  2. s//nl <<\\@@/
    • Das leere s//Feld /steht für die letzte Adresse sed, für die eine Übereinstimmung versucht wurde. Dieser Befehl ersetzt stattdessen die gesamte @@.*startZeile nl <<\\@@.
  3. :l;N
    • Der :Befehl definiert eine Verzweigungsbezeichnung - hier habe ich eine mit dem Namen :label festgelegt. Mit dem NBefehl ext wird die nächste Eingabezeile an den Musterbereich \nangehängt, gefolgt von einem ewline-Zeichen. Dies ist eine der wenigen Möglichkeiten, eine \newline in einem sedMusterraum zu erhalten - das \newline-Zeichen ist ein sicheres Trennzeichen für einen derer, der es eine sedWeile getan hat.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • diese 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 \neingewirkt, in dem unmittelbar nach der letzten ewline @@.*enddas Ende $des Musterbereichs markiert wird. Wenn es handelt, ersetzt es die gesamte übereinstimmende Zeichenfolge durch die \1erste \(Gruppe \)oder \n@@.
  5. Tl
    • Der TBefehl 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 \newline an einen Musterbereich angehängt wird, der nicht mit Ihrem Endbegrenzer übereinstimmt, der TBefehl est fehlschlägt und zurück zum :lAbel verzweigt , was dazu führt sed, dass die Next-Zeile eingezogen und eine Schleife ausgeführt wird, bis sie erfolgreich ist.
  6. e

    • Wenn die Ersetzung für die Endübereinstimmung erfolgreich ist und das Skript nicht nach einem fehlgeschlagenen Test verzweigt , sedwird eein Befehl ausgeführt, der lwie folgt aussieht :

      nl <<\\@@\nline X\nline Y\nline Z\n@@$

Sie können sich davon überzeugen, indem Sie die letzte Zeile so bearbeiten, dass sie aussieht Tl;l;e.

Es druckt:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

Ein letzter Weg, dies zu tun, und vielleicht der einfachste, ist die Verwendung einer while readSchleife, aber aus gutem Grund. Die Shell (insbesondere eine bashShell) 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. So readeignet sich hervorragend als Eingangstest - auf returnInformationen 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:

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

Das erste, was bei jeder Iteration passiert, ist das readZiehen einer Linie. Wenn es erfolgreich ist , bedeutet es die Schleife noch nicht EOF getroffen und so in der casees entspricht ein Starttrennzeichen des doBlock wird sofort ausgeführt. Ansonsten printfdruckt $linees aus readund sedwird aufgerufen.

sedwird pjede Zeile rucken , bis er die Begegnungen Start marker - wenn es qEingang ganz UITS. Der -uSchalter nbuffered ist für GNU erforderlich, sedda er ansonsten ziemlich gierig puffern kann, andere POSIXs sedsollten jedoch - je nach Spezifikation - ohne besondere Berücksichtigung funktionieren - solange <infilees sich um eine reguläre Datei handelt.

Wenn die ersten sed qUITS führt der Schale , die den doBlock der Schleife - das eine andere Anrufe , seddie jede Zeile druckt , bis er die Begegnungen Ende Marker. Es leitet seine Ausgabe an weiter paste, da es Zeilennummern jeweils in einer eigenen Zeile ausgibt . So was:

1
line M
2
line N
3
line O

pasteFügt diese dann zu :Zeichen zusammen, und die gesamte Ausgabe sieht wie folgt aus:

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

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, headund seddas Richtige zu tun (obwohl, für GNU sed, können Sie die cli-Schalter benötigen) und Darauf sollten Sie sich immer verlassen können read- denn es ist von Natur aus sehr langsam . Aus diesem Grund wird es in der obigen Schleife nur einmal pro Eingabeblock aufgerufen.


Ich habe das zweite sedBeispiel 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.)
James Scriven

@ JamesScriven - Ich habe gerade bearbeitet, um es besser zu erklären. Lass es mich wissen, wenn es nicht hilft. Ich habe auch das Kommando stark verändert - es ist jetzt in kleinere, vernünftigere Teile zerlegt.
mikeserv

4

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 sein

/@@inline-code-start
jV/@@inline-code-end
k:!nl

Dies 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.


Ja, vim ist großartig! Aber ich bin in diesem Fall auf der Suche nach einer skriptfähigen Lösung.
James Scriven

@JamesScriven, wer sagt vim ist nicht skriptfähig in ungenügend bestimmt. Erstellen Sie zunächst ein Projektverzeichnis und kopieren Sie in dieses Verzeichnis alle Startdateien von vim aus Ihrem Ausgangsverzeichnis (ln -s funktioniert einwandfrei, mit Ausnahme von .vimrc, das geändert werden soll, und .viminfo, das möglicherweise mit Rauschen gefüllt ist). Fügen Sie der neuen .vimrc-Datei die Funktionsdefinition hinzu, die die Arbeit erledigt, und rufen Sie dann vim as auf 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).
Hildred

@hildred Hmmm ... Könnte ich nicht einfach [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) verwenden, um Mausklicks zu simulieren, um zu vim?
James Scriven

2

Die einfachste Lösung, die ich mir vorstellen kann, ist, nldie Zeilen nicht zu verwenden, sondern selbst zu zählen:

#!/usr/bin/env 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
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

Sie führen es dann in der Datei aus:

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D

Vielen Dank, Terdon. Ich habe die Frage aktualisiert, um zu verdeutlichen, dass ich nach einer generischen Lösung zum Filtern eines Unterabschnitts einer Eingabe suche, und nicht nach dem spezifischen Beispiel für die Nummerierung von Zeilen. Vielleicht wäre ein besserer Beispielbefehl "tac" (reverse lines) gewesen
James Scriven

2

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:

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

Dies erzeugt für eine Eingabedatei, die den Testfall dreimal wiederholt, Folgendes:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D

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:

line A
line B
     1  line Z
     2  line Y
     3  line X
line C
line D

Oder Wortzahl echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D

2

Bearbeiten hat eine Option zum Definieren eines vom Benutzer bereitgestellten Filters hinzugefügt

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

Standardmäßig ist der Filter "nl". Um den Filter zu ändern, verwenden Sie die Option "-p" mit einem vom Benutzer angegebenen Befehl:

codify -p="wc" file

oder

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

Dieser letzte Filter gibt Folgendes aus:

line A
line B
 ╓─
  line X
  line Y
  line Z
 ╙─
line C
line D

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:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}

Das ist wirklich cool. Ich denke, die Tricks sind, dass Sie nicht Zeile für Zeile (durch Redefinieren $/und das sFlag) verarbeiten und die Verwendung des eFlags, um den eigentlichen Aufruf des externen Befehls durchzuführen . Ich mag das zweite Beispiel (ASCII-Kunst) wirklich!
James Scriven

Mir ist allerdings aufgefallen, dass dies nicht über ein paar tausend Zeilen im Unterabschnitt hinaus zu skalieren scheint. Ich vermute, das hat damit zu tun, dass der Unterabschnitt wie ein einziger großer Textblock behandelt wird.
James Scriven

Vielen Dank. Ja: `/ e` = eval; /s= ("." bedeutet (.|\n)); $/Definiert das Register-Trennzeichen neu.
JJoao,

@JamesScriven, du hast recht (die Pipe blockiert). Lassen Sie mich testen, was los ist ...
Joao

@ JamesScriven, siehe bitte mein Update ...
Joao

1

Das ist ein Job für awk.

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

Wenn das Skript die Startmarkierung sieht, wird darauf hingewiesen, dass es mit dem Piping beginnen soll nl. Wenn die pipeVariable true (ungleich null) ist, wird die Ausgabe an den nlBefehl 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. Die closeFunktion 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 nlBefehl beendet und die Puffer geleert werden. Ihr Skript schließt die Pipe tatsächlich zu früh: Die Pipe wird geschlossen, sobald die erste echo $line >myfifoAusführung abgeschlossen ist. Der nlBefehl sieht jedoch nur das Ende der Datei, wenn er vor der nächsten Ausführung des Skripts eine Zeitscheibe erhält echo $line >myfifo. Wenn Sie ein großes Datenvolumen hatten oder wenn Sie sleep 1nach dem Schreiben hinzufügen myfifo, werden Sie feststellen, dass nlnur 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.

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

(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.

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done

Ihre awk-Lösung ist wirklich nett! Ich denke, das ist bei weitem die prägnanteste (und doch sehr lesbare) Lösung. Ist das Verhalten der awk bei der Wiederverwendung der Pipe für nl garantiert, oder könnte awk entscheiden: "Hey, du hast jetzt genug Pipe. Ich werde diese Pipe schließen und eine neue öffnen." Ihre "Pipeline" -Lösung ist auch wirklich nett. Ich habe einen Ansatz mit eingebetteten while-Schleifen ursprünglich abgelehnt, da ich dachte, dass er etwas verwirrend ist, aber ich denke, dass das, was Sie haben, großartig ist. Vor dem fehlt ein Semikolon do. (Ich habe nicht den Repräsentanten hier, um eine kleine
James Scriven

1
... Ich konnte Ihre Named Pipe-Lösung nicht zum Laufen bringen. Es scheint eine Rennbedingung zu geben, so dass der Abschnitt, der zu nl geleitet wird, manchmal vollständig verloren geht. Auch wenn es einen zweiten @@ Inline-Code-Start- / Endabschnitt gibt, geht dieser immer verloren.
James Scriven

0

OK, 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 ist

tr "[[:lower:]]" "[[:upper:]]"

dh konvertieren Sie Text in Großbuchstaben; also für eine Eingabe von

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

Sie möchten eine Ausgabe von

line A
line B
LINE X
LINE Y
LINE Z
line C
line D

Hier ist meine erste Annäherung an eine Lösung:

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

Die Leerzeichen vor den @@Zeichenfolgen und am Ende der letzten Zeile sind Tabulatoren. Bitte beachten Sie, dass ich nl 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) und file1(aktiv; in einem Abschnitt). So sehen sie für die obige Eingabe aus:

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

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.


nlerledigt dort bereits den größten Teil der Arbeit - dafür gibt es die -dElimiter-Option.
mikeserv

0

Ein Shell-Skript, das sed verwendet, um Teile von nicht abgegrenzten Zeilen auszugeben und Teile von abgegrenzten Zeilen in ein Filterprogramm einzufügen:

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

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:

#!/bin/bash
awk '{ print "\t" NR " " $0}'

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.


-1

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 nlnur als Beispiel Filter)

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

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.


Ich dachte , Sie zu „akkumulieren Zustand über die Leitungen“, so zum Beispiel in der Lage sein wollte, Mikes Testdaten, Linien M, Nund Owürde zu nummerieren 4, 5und 6. Das macht das nicht. Meine Antwort ist (abgesehen von der Tatsache, dass es in seiner aktuellen Inkarnation nicht nlals 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?)
Scott,

@Scott - benutzen um nl -pzu bekommen M,N,O==4,5,6.
mikeserv

Ich habe die Frage aktualisiert, um zu verdeutlichen, dass ich nur daran interessiert bin, den Status innerhalb des Unterabschnitts beizubehalten, obwohl ich die andere Interpretation für gleichermaßen interessant halte.
James Scriven
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.