Wie kann ich in awk eine Datei verwenden, die mehrere Formatstrings mit printf enthält?

8

Ich habe einen Fall, wo ich die Eingabe aus einer Datei als Format für printf() in awk verwenden möchte. Meine Formatierung funktioniert, wenn ich sie in eine Zeichenfolge innerhalb des Codes einstelle, aber sie funktioniert nicht, wenn ich sie aus der Eingabe lade.

Hier ist ein kleines Beispiel für das Problem:

%Vor%

Also ... Format Substitutionen arbeiten (" %s "), aber keine Sonderzeichen wie Tab und Newline. Irgendeine Idee, warum das passiert? Und gibt es eine Möglichkeit, "etwas zu tun", um Daten einzugeben, um sie als Formatzeichenfolge nutzbar zu machen?

UPDATE # 1:

Als ein weiteres Beispiel betrachten Sie folgendes mit bash hiertext:

%Vor%

Soweit ich sehen kann, passiert das Gleiche mit mehreren verschiedenen awk-Interpretern, und ich konnte keine Dokumentation finden, die erklärt, warum.

UPDATE # 2:

Der Code, den ich gerade ersetzen möchte, sieht ungefähr so ​​aus, mit verschachtelten Schleifen in der Shell. Momentan wird awk nur für das printf verwendet und könnte durch ein Shell-basiertes printf ersetzt werden:

%Vor%

Beispieleingabe wäre:

%Vor%

Meine Hoffnung war, dass ich in der Lage wäre, so etwas zu konstruieren, um die ganze Sache mit einem einzigen Aufruf von awk zu erledigen, anstatt verschachtelte Schleifen in der Shell zu haben:

%Vor%

Offensichtlich funktioniert das nicht, sowohl wegen des eigentlichen Themas dieser Frage als auch weil ich noch nicht herausgefunden habe, wie man elegant awk $ 2 .. $ n in eine einzelne Variable einfügt. (Aber das ist das Thema einer möglichen zukünftigen Frage.)

FWIW, ich benutze FreeBSD 9.2 mit seinem eingebauten, aber ich bin offen für die Verwendung von gawk, wenn eine Lösung damit gefunden werden kann.

    
Graham 04.07.2014, 13:59
quelle

10 Antworten

4

Warum so lang und kompliziert ein Beispiel? Dies zeigt das Problem:

%Vor%

Im ersten Fall ist die Zeichenkette "a \ t% s" ein String-Literal und wird daher zweimal interpretiert - einmal wenn das Skript von awk gelesen wird und dann erneut, wenn es ausgeführt wird, wird \t erweitert im ersten Durchlauf und dann bei der Ausführung awk hat ein literal Tab-Char in der Formatierungszeichenfolge.

Im zweiten Fall hat awk noch die Zeichen backslash und t in der Formatierungszeichenfolge - daher das unterschiedliche Verhalten.

Sie brauchen etwas, um diese Buchstaben zu interpretieren. Eine Möglichkeit ist, die printf der Shell aufzurufen und die Ergebnisse zu lesen (korrigiert nach @ EtanReisers exzellenter Beobachtung, dass ich doppelte Anführungszeichen verwendet habe, wo ich einfache Anführungszeichen gehabt hätte) by \ 047, um Shell-Erweiterungen zu vermeiden:

%Vor%

Wenn Sie das Ergebnis nicht in einer Variablen benötigen, können Sie einfach system() aufrufen.

Wenn Sie möchten, dass die Escape-Zeichen so erweitert werden, dass Sie die %s -Argumente nicht im Shell printf -Aufruf angeben müssen, müssen Sie nur alle % s ausschließen (auf die Sie bereits achten) -escaped % s).

Sie könnten awk anstatt der Shell printf aufrufen, wenn Sie bevorzugen.

Beachten Sie, dass dieser Ansatz, obwohl ungeschickt, viel sicherer ist als das Aufrufen von eval , das möglicherweise nur eine Eingabezeile wie rm -rf /*.* ! ausführt.

Mit Hilfe von Arnold Robbins (dem Schöpfer von Gawk) und Manuel Collado (ein anderer bekannter awk-Experte), hier ist ein Skript, das einzelne Zeichen-Escape-Sequenzen erweitern wird:

%Vor%

.

%Vor%

Alternativ sollte dies funktionell äquivalent, aber nicht gawk-spezifisch sein:

%Vor%

Wenn Sie möchten, können Sie das Konzept auf oktale und hexadezimale Escape-Sequenzen erweitern, indem Sie split () RE in

ändern %Vor%

und für einen Hexwert nach dem \ :

%Vor%

und für einen Oktalwert:

%Vor%     
Ed Morton 04.07.2014, 15:04
quelle
3

Da die Frage explizit nach einer awk-Lösung fragt, hier ist eine, die auf allen mir bekannten Aufgaben funktioniert. Es ist ein Proof-of-Concept; Fehlerbehandlung ist miserabel. Ich habe versucht, Orte anzugeben, wo das verbessert werden könnte.

Wie von verschiedenen Kommentatoren angemerkt, ist der Schlüssel, dass awk printf - wie die zugrunde liegende C -Standardfunktion - Backslash-Escapes nicht in der Formatzeichenfolge interpretiert. % Co_de% interpretiert sie jedoch in Befehlszeilen-Zuweisungsargumenten.

%Vor%

( Was hier passiert, ist, dass die Klausel 'FNR == NR' (die nur für die erste Datei ausgeführt wird) die Werte ( awk , fmtid ) aus jeder Zeile der ersten Datei als Befehlszeilenzuweisungen hinzufügt und dann fügt den Datendateinamen als Befehlszeilenargument ein. In fmt werden Zuordnungen als Befehlszeilenargumente einfach so ausgeführt, als wären sie Zuweisungen aus einer Stringkonstanten mit impliziten Anführungszeichen, einschließlich der Backslash-Escape-Verarbeitung (außer, wenn das letzte Zeichen im Argument ein umgekehrter Schrägstrich ist, entkommt es nicht das implizite schließende Doppelzitat). Dieses Verhalten wird von Posix vorgegeben, ebenso wie die Reihenfolge, in der Argumente verarbeitet werden, wodurch es möglich ist, Argumente hinzuzufügen, während Sie fortfahren.

Wie geschrieben, muss das Skript mit genau zwei Argumenten versehen werden: den Formaten und den Daten (in dieser Reihenfolge). Es gibt natürlich noch Raum für Verbesserungen.

Das Snippet zeigt auch zwei Möglichkeiten zum Verketten von nachgestellten Feldern.

In der Formatdatei nehme ich an, dass die Zeilen sich gut benehmen (keine führenden Leerzeichen; genau ein Leerzeichen nach der Format-ID). Mit diesen Einschränkungen ist awk genau der Teil der Zeile nach dem ersten Feld und einem einzelnen Leerzeichen.

Wenn Sie die Datendatei verarbeiten, müssen Sie dies unter Umständen mit weniger Einschränkungen durchführen. Zuerst wird die eingebaute substr(match, length()+2) -Funktion mit dem regulären Ausdruck /^ *[^ ]+[ ]+[^ ]+[ ]+/ aufgerufen, der mit führenden Leerzeichen (falls vorhanden) und zwei durch Leerzeichen getrennten Feldern zusammen mit den folgenden Leerzeichen übereinstimmt. (Es wäre auch besser, Registerkarten zuzulassen.) Sobald die Regex übereinstimmt (und Matching nicht angenommen werden sollte, also gibt es noch etwas zu beheben), werden die Variablen RSTART und RLENGTH gesetzt, also substr(printf, RLENGTH+1) nimmt alles auf, beginnend mit dem dritten Feld. (Dies ist wiederum alles Posix-Standard-Verhalten.)

Ehrlich gesagt, würde ich die Shell printf für dieses Problem verwenden, und ich verstehe nicht, warum Sie denken, dass diese Lösung irgendwie nicht optimal ist. Die Shell read -r interpretiert Backslash-Escapes in Formaten, und die Shell %code% teilt die Zeile wie gewünscht. Es gibt also keinen Grund für awk, soweit ich das beurteilen kann.

    
rici 05.07.2014 05:27
quelle
3

Ed Morton zeigt das Problem deutlich (Bearbeiten: und es ist jetzt fertig, also geh einfach und akzeptiere es ): awks String Literale Verarbeitung behandelt die Escapes und Datei-E / A-Code ist kein lexikalischer Analysator.

Es ist eine einfache Lösung: Entscheiden Sie, welche Escapes Sie unterstützen möchten, und unterstützen Sie sie. Hier ist ein One-Liner-Formular, wenn Sie Spezialarbeiten ausführen, die nicht mit Backslashes umgehen müssen

%Vor%

, aber für die innere Ruhe und Geborgenheit verwenden Sie einfach das vollständige Formular in der verknüpften Antwort.

    
jthill 04.07.2014 16:12
quelle
2

@Ed Mortons Antwort erklärt das Problem gut.

Eine einfache Problemumgehung ist:

  • Übergeben Sie den Inhalt der Formatstring-Datei über eine awk -Variable mithilfe der Befehlsersetzung
  • unter der Annahme, dass die Datei nicht zu groß ist, um vollständig in den Speicher gelesen zu werden.

Verwenden von GNU awk oder mawk :

%Vor%

Hinweis:

  • Der Vorteil dieser Lösung besteht darin, dass sie generisch funktioniert - Sie müssen bestimmte Escape-Sequenzen nicht antizipieren und sie speziell behandeln.
  • Auf FreeBSD awk funktioniert das fast , aber - leider - split() wird immer noch durch Zeilenumbrüche geteilt, obwohl ein explizites Trennzeichen angegeben wurde - das riecht nach einem Fehler. Wird in den Versionen 20070501 (OS X 10.9.4) und 20121220 (FreeBSD 10.0) beobachtet.
  • Das obige löst das Kernproblem (aus Platzgründen wird die ID von der Vorderseite der Formatzeichenfolgen weggelassen und die Erstellungslogik der Ausgabedatei weggelassen).

Erläuterung:

  • tr '\n' '' <fmtStrings ersetzt aktuelle Zeilenumbrüche in der Datei format-strings mit ( 0x3 ) Zeichen, um sie später von den in die Zeilen eingebetteten \n Escape-Sequenzen unterscheiden zu können , die awk in tatsächliche Zeilenumbrüche verwandelt, wenn sie der Variablen formats (wie gewünscht) zugewiesen wird.
    ( 0x3 ) - das ASCII-Zeichenendezeichen. - wurde willkürlich als Hilfstrennzeichen gewählt, von dem angenommen wird, dass es nicht in der Eingabedatei vorhanden ist.
    Beachten Sie, dass die Verwendung von NUL ( awk ) KEINE Option ist, weil split() dies als leere Zeichenkette interpretiert und BEGIN die Zeichenkette in einzelne Zeichen aufteilt.
  • Innerhalb des Blocks awk des Skripts split(formats, aFormats, "") teilt %code% dann die kombinierten Formatzeichenfolgen in einzelne Formatzeichenfolgen zurück.
mklement0 04.07.2014 17:16
quelle
1

Ich musste eine andere Antwort erstellen, um sauber zu starten, ich glaube, ich bin zu einer guten Lösung gekommen, wieder mit Perl:

%Vor%

Dieser böse Junge s/((?:\[a-zA-Z\])+)/qq[qq[]]/eeg wird jeden denkbaren Meta-Charakter übersetzen, lass uns einen Blick auf cat -A werfen:

%Vor%

PS. Ich habe diesen Regex nicht erstellt, ich habe Meta-Begriffe gegoogelt und hier

    
Tiago 04.07.2014 23:13
quelle
1

Was Sie zu tun versuchen, heißt Templating. Ich würde vorschlagen, dass Shell-Tools nicht die besten Werkzeuge für diesen Job sind. Eine sichere Methode wäre die Verwendung einer Vorlagenbibliothek wie Template Toolkit für Perl oder Jinja2 für Python.

    
Chris Seymour 04.07.2014 18:23
quelle
0

Das Problem liegt in der Nicht-Interpretation der Sonderzeichen \t und \n by echo : es stellt sicher, dass sie als Ist-Zeichenfolgen und nicht als Tabellen und Zeilenumbrüche verstanden werden. Dieses Verhalten kann durch das -e -Flag, das Sie einem Echo zuweisen, gesteuert werden, ohne Ihr awk-Skript zu ändern:

%Vor%

Tada !! :)

BEARBEITEN: OK, also nach dem von Chrono richtig aufgeworfenen Punkt können wir diese andere Antwort entsprechend der ursprünglichen Anfrage entwickeln, das Muster aus einer Datei lesen zu lassen:

%Vor%

Natürlich müssen wir bei der obigen Beschreibung vorsichtig sein, da $(cat myfile) nicht von awk gesehen, sondern von der Shell interpretiert wird.

    
jaybee 04.07.2014 14:19
quelle
0

Das sieht extrem hässlich aus, aber es funktioniert für dieses spezielle Problem:

%Vor%
  1. Ersetzen Sie alle einfachen Anführungszeichen durch von der Shell abgefangene einfache Anführungszeichen ( '\'' ).
  2. Ersetzen Sie alle Zeilenumbrüche mit Escapezeichen, die normalerweise als \n angezeigt werden, durch die Sequenz, die als \\n angezeigt wird. Es würde ausreichen, \\n als die tatsächliche Ersetzungszeichenfolge zu verwenden (das heißt \n würde drucken, wenn Sie es gedruckt haben), aber die Version von gawk habe ich im POSIX-Modus vermasselt.
  3. Rufen Sie die Shell auf, um printf '%b' 'escape'\''d format' auszuführen, und verwenden Sie die getline-Anweisung von awk, um die Zeile abzurufen.
  4. Unescape \n , um eine neue Zeile zu erhalten. Dieser Schritt wäre nicht notwendig, wenn Gaff im POSIX-Modus gut gespielt wird.
  5. Unescape \n , um eine neue Zeile zu erhalten.

Ansonsten bleibt die gsub-Funktion für jede mögliche Escape-Sequenz offen, was für %code%21 , %code% usw. schrecklich ist.

    
Chrono Kitsune 04.07.2014 16:23
quelle
0

Graham,

Ed Mortons Lösung ist die beste (und vielleicht einzige), die es gibt.

Ich füge diese Antwort für eine bessere Erklärung ein, WARUM du siehst, was du siehst.

Eine Zeichenfolge ist eine Zeichenfolge. Der verwirrende Teil hier ist WHERE awk die Übersetzung von \t zu einem Tab, \n zu einem Newline, etc. Es scheint NICHT der Fall zu sein, dass der Backslash und t übersetzt werden, wenn sie in einem printf verwendet werden. Format. Stattdessen erfolgt die Übersetzung bei Zuweisung , so dass awk die Registerkarte als Teil des Formats speichert und nicht beim Ausführen von printf übersetzt.

Und deshalb funktioniert Eds Funktion. Beim Lesen von stdin oder einer Datei wird keine Zuweisung ausgeführt, die die Übersetzung von Sonderzeichen implementiert. Sobald Sie den Befehl s="a\tb"; in awk ausgeführt haben, haben Sie eine aus drei Zeichen bestehende Zeichenfolge, die keinen umgekehrten Schrägstrich oder t enthält.

Beweis:

%Vor%

vs

%Vor%

Und da gehst du.

Wie gesagt, Eds Antwort bietet eine ausgezeichnete Funktion für das, was Sie brauchen. Aber wenn Sie vorhersagen können, wie Ihre Eingabe aussehen wird, können Sie wahrscheinlich mit einer einfacheren Lösung durchkommen. Wenn Sie wissen, wie dieses Zeug geparst wird, können Sie, wenn Sie nur eine begrenzte Anzahl von Zeichen übersetzen müssen, mit etwas Einfachem wie:

überleben %Vor%     
ghoti 18.07.2014 21:44
quelle
-1

Das ist eine coole Frage, ich kenne die Antwort in awk nicht, aber in Perl kannst du eval :

verwenden %Vor%

PS. Beachten Sie die Code-Injection-Gefahr, wenn Sie eval in einer beliebigen Sprache verwenden, nicht nur eval irgendein Systemaufruf kann nicht blind getan werden.

Beispiel in Awk:

%Vor%

Was wäre, wenn die Eingabe $(rm -rf /) wäre? Sie können raten, was passieren würde:)

ikegami fügt hinzu:

Warum sollten Sie sogar eval verwenden, um \n in neue Zeilen und \t in Tabs zu konvertieren?

%Vor%

Kurze Version:

%Vor%     
Tiago 04.07.2014 15:14
quelle

Tags und Links