Warum stoppt awk und wartet, wenn der Dateiname = enthält, und wie kann man das umgehen?

25
awk 'processing_script_here' my=file.txt

scheint anzuhalten und auf unbestimmte Zeit zu warten ...
Was ist hier los und wie lasse ich es funktionieren?

don_crissti
quelle

Antworten:

19

Wie Chris sagt , werden Argumente des Formulars variablename=anythingals Variablenzuweisung behandelt (die zum Zeitpunkt der Verarbeitung der Argumente ausgeführt wird, im Gegensatz zu den (neueren) Argumenten -v var=value, die vor den BEGINAnweisungen ausgeführt werden), anstelle von Eingabedateinamen.

Das kann nützlich sein in Dingen wie:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

Wo können Sie eine andere FS/ RSpro Datei angeben . Es wird auch häufig verwendet in:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

Welches ist eine sicherere Version von:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

(was nicht funktioniert wenn file1leer ist)

Das stört jedoch, wenn Sie Dateien haben, deren Name =Zeichen enthält .

Nun, das ist nur dann ein Problem, wenn vom ersten =noch ein gültiger awkVariablenname übrig ist .

Was einen gültigen Variablennamen in ausmacht, awkist strenger als in sh.

POSIX erfordert Folgendes:

[_a-zA-Z][_a-zA-Z0-9]*

Mit nur Zeichen des portablen Zeichensatzes. Das /usr/xpg4/bin/awkvon Solaris 11 ist jedoch zumindest in dieser Hinsicht nicht kompatibel und lässt alle alphabetischen Zeichen im Gebietsschema in Variablennamen zu, nicht nur a-zA-Z.

Ein Argument wie x+y=foooder =baroder ./foo=barwird also immer noch als Eingabedateiname und nicht als Zuweisung behandelt, da das, was vom ersten übrig =bleibt, kein gültiger Variablenname ist. Ein Argument wie Stéphane=Chazelas.txtkann oder kann nicht, je nach awkImplementierung und Gebietsschema.

Aus diesem Grund wird bei awk empfohlen, Folgendes zu verwenden:

awk '...' ./*.txt

anstatt

awk '...' *.txt

Zum Beispiel, um das Problem zu vermeiden, wenn Sie nicht garantieren können, dass der Name der txtDateien keine =Zeichen enthält .

Beachten Sie auch, dass ein Argument wie -vfoo=bar.txtdieses als Option behandelt werden kann, wenn Sie Folgendes verwenden:

awk -f file.awk -vfoo=bar.txt

(Gilt auch für awk '{code}' -vfoo=bar.txtdie awkvon Busybox-Versionen vor 1.28.0, siehe entsprechenden Fehlerbericht ).

Wiederum ./*.txtfunktioniert using um dieses Problem herum (die Verwendung eines ./Präfix hilft auch bei einer aufgerufenen Datei, -die ansonsten awkals Standardeingabe interpretiert wird).

Das ist auch der Grund

#! /usr/bin/awk -f

Shebangs funktionieren nicht wirklich. Während var=valuediese durch Festlegen der ARGVWerte (Hinzufügen eines ./Präfixes) in einer BEGINAnweisung umgangen werden können:

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

Das hilft nicht bei den Optionen, da diese von awkund nicht vom awkSkript gesehen werden.

Ein potenzielles kosmetisches Problem bei der Verwendung dieses ./Präfixes ist , dass es FILENAMEimmer auftritt. Sie können es jedoch jederzeit substr(FILENAME, 3)zum Entfernen verwenden, wenn Sie es nicht möchten.

Die GNU-Implementierung von awkbehebt alle diese Probleme mit ihrer -EOption.

Danach -Eerwartet gawk nur den Pfad des awkSkripts (wo -immer noch stdin bedeutet) und dann nur eine Liste der Eingabedateipfade (und wird dort nicht einmal -speziell behandelt).

Es wurde speziell entwickelt für:

#! /usr/bin/gawk -E

Shebangs, bei denen die Liste der Argumente immer Eingabedateien sind (beachten Sie, dass Sie diese ARGVListe in einer BEGINAnweisung noch bearbeiten können ).

Sie können es auch verwenden als:

gawk -e '...awk code here...' -E /dev/null *.txt

Wir verwenden -Eein leeres Skript ( /dev/null), um sicherzustellen, dass diese *.txtanschließend immer als Eingabedateien behandelt werden, auch wenn sie =Zeichen enthalten .

Stéphane Chazelas
quelle
Ich sehe nicht, wie der explizite Pfad, der in FILENAME endet, ein Problem ist. Entweder der awk - Skript ist allgemein, in diesem Fall sollten sie alle Arten von Pfaden in DATEI enden behandeln (einschließlich , aber nicht beschränkt auf ../foo, /path/to/foound Pfade , die in einer anderen Kodierung) sind - in diesem Fall substr(FILENAME,3)wird nicht genug sein, oder es ist ein One - Shot - Skript , in dem der Benutzer im Grunde weiß , was die Dateinamen - in diesem Fall sollte er / sie wahrscheinlich mit einem von ihnen , die nicht stören =entweder ;-)
mosvy
2
@mosvy Ich denke nicht, dass es so viel aussagt, ./was ein Problem ist, aber dass es unter bestimmten Umständen unerwünscht sein kann, beispielsweise in Fällen, in denen Dateiname in der Ausgabe enthalten sein muss. In diesem Fall ./sollten Sie redundant und unnötig sein muss es irgendwie loswerden. Hier ist mindestens ein Beispiel . Was den Benutzer anbelangt, der weiß, was Dateinamen sind - nun, in diesem Fall wissen wir auch, was Dateinamen sind, behindern aber =dennoch die ordnungsgemäße Verarbeitung. So kann das Führen -in die Quere kommen.
Sergiy Kolodyazhnyy
@mosvy, ja, die Idee ist, dass Sie das ./Präfix verwenden möchten, um diese awk(falsche) Funktion zu umgehen, aber dann erhalten Sie eine ./Ausgabe, die Sie möglicherweise entfernen möchten. Sehen Sie, wie Sie überprüfen, ob die erste Zeile der Datei eine bestimmte Zeichenfolge enthält? als Beispiel.
Stéphane Chazelas
Ist nicht nur der lokale (relativ zu diesem Verzeichnis), ./sondern auch der globale (absolute Pfad), /der awk veranlasst, das Argument als Datei zu interpretieren.
Isaac
21

In den meisten Versionen von awk lauten die Argumente nach dem auszuführenden Programm entweder:

  1. Eine Datei
  2. Eine Zuordnung des Formulars x=y

Da Ihr Dateiname als Fall # 2 interpretiert wird, wartet awk immer noch auf etwas zum Lesen von stdin (da es nicht wahrnimmt, dass ein Dateiname übergeben wurde).

Portabel ist dieses Verhalten in POSIX dokumentiert :

Beide der folgenden Argumenttypen können miteinander vermischt werden:

  • Datei: Ein Pfadname einer Datei, die die zu lesende Eingabe enthält, die mit den Mustern im Programm abgeglichen wird. Wenn keine Dateioperanden angegeben sind oder wenn ein Dateioperand '-' ist, muss die Standardeingabe verwendet werden.
  • Zuweisung: Ein Operand, der mit einem Unterstrich oder einem alphabetischen Zeichen aus dem portablen Zeichensatz beginnt (siehe die Tabelle im Band Basisdefinitionen von IEEE Std 1003.1-2001, Abschnitt 6.1, Portabler Zeichensatz), gefolgt von einer Folge von Unterstrichen, Ziffern, Alphabetische Zeichen aus dem portablen Zeichensatz, gefolgt vom Zeichen "=", geben eine variable Zuweisung anstelle eines Pfadnamens an.

Als solches haben Sie portabel ein paar Möglichkeiten (Nr. 1 ist wahrscheinlich die am wenigsten aufdringliche):

  1. Verwenden Sie awk ... ./my=file, was dies umgeht, da .es sich nicht um "einen Unterstrich oder ein alphabetisches Zeichen aus dem portablen Zeichensatz" handelt.
  2. Legen Sie die Datei mit auf stdin awk ... < my=file. Dies funktioniert jedoch nicht gut mit mehreren Dateien.
  3. Erstellen Sie vorübergehend einen Hardlink zu der Datei, und verwenden Sie diesen. Sie können so etwas tun ln my=file my_fileund dann my_filewie gewohnt verwenden. Es wird kein Kopieren durchgeführt und beide Dateien werden mit denselben Daten und Inode-Metadaten gesichert. Nach der Verwendung kann der erstellte Link sicher entfernt werden, da die Anzahl der Verweise auf den Inode immer noch größer als 0 ist.
Chris Down
quelle
6
Nicht ./my=file funktioniert? % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). Dies sollte portierbar sein, da ./myes sich nicht um einen gültigen Variablennamen handelt, und sollte daher nicht auf diese Weise analysiert werden.
Stephen Harris
2
Wie im POSIX-Text angegeben, liegt das Problem nur dann vor, wenn dem ersten ein Unterstrich oder ein alphabetisches Zeichen aus dem portablen Zeichensatz= vorangestellt ist (siehe die Tabelle im Band Basisdefinitionen von IEEE Std 1003.1-2001, Abschnitt 6.1, Portabler Zeichensatz). gefolgt von einer Folge von Unterstrichen, Ziffern und Buchstaben aus dem portablen Zeichensatz . also ein Dateipfad wie ++foo=bar.txtoder =foooder ./foo=barist alles OK als das .oder +ist nicht ein [_a-zA-Z].
Stéphane Chazelas
1
@SergiyKolodyazhnyy awk befindet sich außerhalb der Shell, es spielt also keine Rolle, welche Sie verwenden. ./my=filewird wörtlich durchgereicht.
Chris Down
1
@SergiyKolodyazhnyy, das gleiche für awk '{print $1,$2}' /etc/passwd. Der Punkt ist, dass es keinen Unterschied macht, ob die Shell die Datei öffnet, im Gegensatz zu awk, ob sie durchsuchbar ist oder nicht. Tatsächlich awk '{exit}' < /etc/passwdwürden Sie in erwarten awk, bis zum Ende des ersten Datensatzes danach zu suchen exit, um sicherzustellen, dass er die Position innerhalb von stdin dort belässt. POSIX benötigt das. /usr/xpg4/bin/awktut es auf Solaris, aber weder , gawknoch mawkscheint es auf GNU / Linux zu tun.
Stéphane Chazelas
3
@mosvy, siehe Abschnitt INPUT FILES unter pubs.opengroup.org/onlinepubs/9699919799/utilities/…. Dies ist in einer Reihe von Verwendungsmustern nützlich, die nur bei regulären Dateien sinnvoll sind, wenn Sie eine Datei abschneiden oder Daten in diese schreiben möchten eine auf awkdiese Weise identifizierte Position .
Stéphane Chazelas
3

So zitieren Sie die Gawk-Dokumentation (Hervorhebung hinzugefügt):

Alle zusätzlichen Argumente in der Befehlszeile werden normalerweise als Eingabedateien behandelt, die in der angegebenen Reihenfolge verarbeitet werden sollen. Ein Argument mit der Form var = value weist der Variablen var jedoch den Wert value zu - es gibt überhaupt keine Datei an.

Warum stoppt der Befehl und wartet? Da in dem Formular awk 'processing_script_here' my=file.txt keine Datei angegeben ist, die durch die obige Definition my=file.txtdefiniert wurde, awkwird dies als Variablenzuweisung interpretiert, und wenn keine Datei definiert ist, wird stdin gelesen (dies stracezeigt auch, dass awk in einem solchen Befehl auf read(0,'...)syscall wartet .

Dies ist auch in den POSIX awk-Spezifikationen dokumentiert (siehe Abschnitt OPERANDS und dazugehörige Zuweisungen ).

Die Zuweisung von Variablen ist darin ersichtlich, awk '{print foo}' foo=bar /etc/passwddass der Wert von foofür jede Zeile in / etc / passwd gedruckt wird. Das Angeben ./foo=baroder des vollständigen Pfads funktioniert jedoch.

Beachten Sie, dass Laufen straceauf awk '1' foo=barsowie die Überprüfung mit cat foo=barzeigt , dass dies awk-spezifisches Problem, und execve Zeigt Dateinamen als Argument übergeben, so Schalen nichts mit env Variablenzuweisungen in diesem Fall zu tun.

Beachten Sie außerdem, dass awk '...script...' foo=bardie Umgebungsvariablen nicht durch die Shell erstellt werden, da die Zuweisung von Umgebungsvariablen vor einem Befehl erfolgen sollte, damit sie wirksam wird. Siehe POSIX-Shell-Grammatikregeln , Punkt 7. Zusätzlich kann dies über überprüft werdenawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

Sergiy Kolodyazhnyy
quelle