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 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 .
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.
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:
Eine Datei
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).
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):
Verwenden Sie awk ... ./my=file, was dies umgeht, da .es sich nicht um "einen Unterstrich oder ein alphabetisches Zeichen aus dem portablen Zeichensatz" handelt.
Legen Sie die Datei mit auf stdin awk ... < my=file. Dies funktioniert jedoch nicht gut mit mehreren Dateien.
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.
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 .
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.txtkeine 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
Antworten:
Wie Chris sagt , werden Argumente des Formulars
variablename=anything
als Variablenzuweisung behandelt (die zum Zeitpunkt der Verarbeitung der Argumente ausgeführt wird, im Gegensatz zu den (neueren) Argumenten-v var=value
, die vor denBEGIN
Anweisungen ausgeführt werden), anstelle von Eingabedateinamen.Das kann nützlich sein in Dingen wie:
Wo können Sie eine andere
FS
/RS
pro Datei angeben . Es wird auch häufig verwendet in:Welches ist eine sicherere Version von:
(was nicht funktioniert wenn
file1
leer 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ültigerawk
Variablenname übrig ist .Was einen gültigen Variablennamen in ausmacht,
awk
ist strenger als insh
.POSIX erfordert Folgendes:
Mit nur Zeichen des portablen Zeichensatzes. Das
/usr/xpg4/bin/awk
von 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=foo
oder=bar
oder./foo=bar
wird also immer noch als Eingabedateiname und nicht als Zuweisung behandelt, da das, was vom ersten übrig=
bleibt, kein gültiger Variablenname ist. Ein Argument wieStéphane=Chazelas.txt
kann oder kann nicht, je nachawk
Implementierung und Gebietsschema.Aus diesem Grund wird bei awk empfohlen, Folgendes zu verwenden:
anstatt
Zum Beispiel, um das Problem zu vermeiden, wenn Sie nicht garantieren können, dass der Name der
txt
Dateien keine=
Zeichen enthält .Beachten Sie auch, dass ein Argument wie
-vfoo=bar.txt
dieses als Option behandelt werden kann, wenn Sie Folgendes verwenden:(Gilt auch für
awk '{code}' -vfoo=bar.txt
dieawk
von Busybox-Versionen vor 1.28.0, siehe entsprechenden Fehlerbericht ).Wiederum
./*.txt
funktioniert using um dieses Problem herum (die Verwendung eines./
Präfix hilft auch bei einer aufgerufenen Datei,-
die ansonstenawk
als Standardeingabe interpretiert wird).Das ist auch der Grund
Shebangs funktionieren nicht wirklich. Während
var=value
diese durch Festlegen derARGV
Werte (Hinzufügen eines./
Präfixes) in einerBEGIN
Anweisung umgangen werden können:Das hilft nicht bei den Optionen, da diese von
awk
und nicht vomawk
Skript gesehen werden.Ein potenzielles kosmetisches Problem bei der Verwendung dieses
./
Präfixes ist , dass esFILENAME
immer auftritt. Sie können es jedoch jederzeitsubstr(FILENAME, 3)
zum Entfernen verwenden, wenn Sie es nicht möchten.Die GNU-Implementierung von
awk
behebt alle diese Probleme mit ihrer-E
Option.Danach
-E
erwartet gawk nur den Pfad desawk
Skripts (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:
Shebangs, bei denen die Liste der Argumente immer Eingabedateien sind (beachten Sie, dass Sie diese
ARGV
Liste in einerBEGIN
Anweisung noch bearbeiten können ).Sie können es auch verwenden als:
Wir verwenden
-E
ein leeres Skript (/dev/null
), um sicherzustellen, dass diese*.txt
anschließend immer als Eingabedateien behandelt werden, auch wenn sie=
Zeichen enthalten .quelle
../foo
,/path/to/foo
und Pfade , die in einer anderen Kodierung) sind - in diesem Fallsubstr(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 ;-)./
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../
Präfix verwenden möchten, um dieseawk
(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../
sondern auch der globale (absolute Pfad),/
der awk veranlasst, das Argument als Datei zu interpretieren.In den meisten Versionen von awk lauten die Argumente nach dem auszuführenden Programm entweder:
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 :
Als solches haben Sie portabel ein paar Möglichkeiten (Nr. 1 ist wahrscheinlich die am wenigsten aufdringliche):
awk ... ./my=file
, was dies umgeht, da.
es sich nicht um "einen Unterstrich oder ein alphabetisches Zeichen aus dem portablen Zeichensatz" handelt.awk ... < my=file
. Dies funktioniert jedoch nicht gut mit mehreren Dateien.ln my=file my_file
und dannmy_file
wie 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.quelle
./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./my
es sich nicht um einen gültigen Variablennamen handelt, und sollte daher nicht auf diese Weise analysiert werden.=
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.txt
oder=foo
oder./foo=bar
ist alles OK als das.
oder+
ist nicht ein[_a-zA-Z]
../my=file
wird wörtlich durchgereicht.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ächlichawk '{exit}' < /etc/passwd
würden Sie in erwartenawk
, bis zum Ende des ersten Datensatzes danach zu suchenexit
, um sicherzustellen, dass er die Position innerhalb von stdin dort belässt. POSIX benötigt das./usr/xpg4/bin/awk
tut es auf Solaris, aber weder ,gawk
nochmawk
scheint es auf GNU / Linux zu tun.awk
diese Weise identifizierte Position .So zitieren Sie die Gawk-Dokumentation (Hervorhebung hinzugefügt):
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 Definitionmy=file.txt
definiert wurde,awk
wird dies als Variablenzuweisung interpretiert, und wenn keine Datei definiert ist, wird stdin gelesen (diesstrace
zeigt auch, dass awk in einem solchen Befehl aufread(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/passwd
dass der Wert vonfoo
für jede Zeile in / etc / passwd gedruckt wird. Das Angeben./foo=bar
oder des vollständigen Pfads funktioniert jedoch.Beachten Sie, dass Laufen
strace
aufawk '1' foo=bar
sowie die Überprüfung mitcat foo=bar
zeigt , 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=bar
die 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
quelle