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:
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.
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 \
:
und für einen Oktalwert:
%Vor%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.
(
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(
-Funktion mit dem regulären Ausdruck match
, length()+2)/^ *[^ ]+[ ]+[^ ]+[ ]+/
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(
nimmt alles auf, beginnend mit dem dritten Feld. (Dies ist wiederum alles Posix-Standard-Verhalten.) printf
, RLENGTH+1)
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.
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.
@Ed Mortons Antwort erklärt das Problem gut.
Eine einfache Problemumgehung ist:
awk
-Variable mithilfe der Befehlsersetzung Verwenden von GNU awk
oder mawk
:
Hinweis:
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. 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. NUL
( awk
) KEINE Option ist, weil split()
dies als leere Zeichenkette interpretiert und BEGIN
die Zeichenkette in einzelne Zeichen aufteilt. awk
des Skripts split(formats, aFormats, "")
teilt %code% dann die kombinierten Formatzeichenfolgen in einzelne Formatzeichenfolgen zurück. 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:
PS. Ich habe diesen Regex nicht erstellt, ich habe Meta-Begriffe gegoogelt und hier
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.
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:
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.
Das sieht extrem hässlich aus, aber es funktioniert für dieses spezielle Problem:
%Vor%'\''
). \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. printf '%b' 'escape'\''d format'
auszuführen, und verwenden Sie die getline-Anweisung von awk, um die Zeile abzurufen. \n
, um eine neue Zeile zu erhalten. Dieser Schritt wäre nicht notwendig, wenn Gaff im POSIX-Modus gut gespielt wird. \n
, um eine neue Zeile zu erhalten. Ansonsten bleibt die gsub-Funktion für jede mögliche Escape-Sequenz offen, was für
, %code% usw. schrecklich ist. %code%2
1
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% Das ist eine coole Frage, ich kenne die Antwort in awk nicht, aber in Perl kannst du eval
:
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?
Kurze Version:
%Vor%