Comme le dit Chris , les arguments du formulaire variablename=anythingsont traités comme des affectations de variables (qui sont effectuées au moment où les arguments sont traités par opposition aux (plus récents) -v var=valuequi sont effectués avant les BEGINinstructions) au lieu des noms de fichiers d'entrée.
Mais cela gêne lorsque vous avez des fichiers dont le nom contient des =caractères.
Maintenant, ce n'est qu'un problème lorsque ce qui reste du premier =est un awknom de variable valide .
Ce qui constitue un nom de variable valide dans awkest plus strict que dans sh.
POSIX exige que ce soit quelque chose comme:
[_a-zA-Z][_a-zA-Z0-9]*
Avec uniquement des caractères du jeu de caractères portable. Cependant, /usr/xpg4/bin/awkSolaris 11 au moins n'est pas conforme à cet égard et autorise tous les caractères alphabétiques dans les paramètres régionaux dans les noms de variables, pas seulement a-zA-Z.
Ainsi, un argument comme x+y=fooou =barou ./foo=barest toujours traité comme un nom de fichier d'entrée et non comme une affectation car ce qui reste du premier =n'est pas un nom de variable valide. Un argument comme Stéphane=Chazelas.txtmay ou may not, selon l' awkimplémentation et les paramètres régionaux.
C'est pourquoi avec awk, il est recommandé d'utiliser:
awk '...'./*.txt
au lieu de
awk '...'*.txt
par exemple pour éviter le problème si vous ne pouvez pas garantir que le nom des txtfichiers ne contiendra pas de =caractères.
Gardez également à l'esprit qu'un argument comme celui-ci -vfoo=bar.txtpeut être traité comme une option si vous utilisez:
awk -f file.awk -vfoo=bar.txt
(applique également awk '{code}' -vfoo=bar.txtavec les awkdes versions busybox avant 1.28.0, voir correspondant rapport de bogue ).
Encore une fois, l'utilisation ./*.txtfonctionne autour de cela (l'utilisation d'un ./préfixe aide également avec un fichier appelé -qui autrement awkcomprend comme signifiant une entrée standard à la place).
C'est aussi pourquoi
#! /usr/bin/awk -f
les bangs ne fonctionnent pas vraiment. Alors que var=valueceux-ci peuvent être contournés en fixant les ARGVvaleurs (ajoutez un ./préfixe) dans une BEGINinstruction:
#! /usr/bin/awk -f
BEGIN {for(i =1; i < ARGC; i++)if(ARGV[i]~/^[_[:alpha:]][_[:alnum:]]*=/)
ARGV[i]="./" ARGV[i]}# rest of awk script
Cela n'aidera pas les options car celles-ci sont vues par awket non le awkscript.
Un problème cosmétique potentiel avec l'utilisation de ce ./préfixe est qu'il se termine FILENAME, mais vous pouvez toujours l'utiliser substr(FILENAME, 3)pour le supprimer si vous ne le souhaitez pas.
L'implémentation GNU awkcorrige tous ces problèmes avec son -Eoption.
Après -E, gawk n'attend que le chemin du awkscript (où -signifie toujours stdin) puis une liste de chemins de fichiers d'entrée uniquement (et là, même pas -traité spécialement).
Il est spécialement conçu pour:
#! /usr/bin/gawk -E
shebangs où la liste des arguments sont toujours des fichiers d'entrée (notez que vous êtes toujours libre de modifier cette ARGVliste dans une BEGINdéclaration).
Vous pouvez également l'utiliser comme:
gawk -e '...awk code here...'-E /dev/null *.txt
Nous utilisons -Eavec un script vide ( /dev/null) juste pour nous assurer que ces *.txtderniers sont toujours traités comme des fichiers d'entrée, même s'ils contiennent des =caractères.
Je ne vois pas en quoi le chemin explicite se terminant dans FILENAME est un problème. Soit le script awk est général, auquel cas il doit gérer tous les types de chemins se terminant par FILENAME (y compris, mais sans s'y limiter ../foo, /path/to/fooet les chemins qui sont dans un codage différent) - dans ce cas, substr(FILENAME,3)ce ne sera pas suffisant, ou c'est un script unique où l'utilisateur sait essentiellement quels sont les noms de fichiers - dans ce cas, il ne devrait probablement pas se soucier de l'un d'eux contenant =non plus ;-)
mosvy
2
@mosvy Je ne pense pas que cela énonce autant de ./problèmes, mais qu'il peut être indésirable dans certaines conditions, telles que les cas où le nom de fichier doit être inclus dans la sortie, auquel cas il ./doit être redondant et inutile, donc vous je vais devoir m'en débarrasser. Voici au moins un exemple . En ce qui concerne l'utilisateur sachant quels sont les noms de fichiers - eh bien, dans ce cas, nous savons également quel est le nom de fichier, mais cela =empêche toujours un traitement approprié. Le leader peut donc se -mettre en travers.
Ce n'est pas seulement le local (par rapport à ce répertoire) ./mais aussi le global (chemin absolu) /qui fait que awk interprète l'argument comme un fichier.
Isaac
21
Dans la plupart des versions d'awk, les arguments après le programme à exécuter sont soit:
Un fichier
Une cession du formulaire x=y
Étant donné que votre nom de fichier est interprété comme le cas # 2, awk attend toujours que quelque chose soit lu sur stdin (car il ne perçoit pas qu'un nom de fichier a été transmis).
L'un des deux types d'arguments suivants peut être mélangé:
fichier: nom de chemin d'un fichier qui contient l'entrée à lire, qui est comparé à l'ensemble des modèles du programme. Si aucun opérande de fichier n'est spécifié, ou si un opérande de fichier est «-», l'entrée standard doit être utilisée.
affectation: un opérande qui commence par un trait de soulignement ou un caractère alphabétique du jeu de caractères portable (voir le tableau dans le volume Définitions de base de IEEE Std 1003.1-2001, Section 6.1, Jeu de caractères portable), suivi d'une séquence de traits de soulignement, de chiffres, et les caractères alphabétiques du jeu de caractères portable, suivis du caractère «=», doivent spécifier une affectation de variable plutôt qu'un nom de chemin.
En tant que tel, de manière portable, vous avez quelques options (# 1 est probablement le moins intrusif):
Utiliser awk ... ./my=file, qui contourne cela car ce .n'est pas "un caractère de soulignement ou alphabétique du jeu de caractères portable".
Mettez le fichier sur stdin en utilisant awk ... < my=file. Cependant, cela ne fonctionne pas bien avec plusieurs fichiers.
Créez temporairement un lien physique vers le fichier et utilisez-le. Vous pouvez faire quelque chose comme ln my=file my_file, puis l'utiliser my_filecomme d'habitude. Aucune copie ne sera effectuée et les deux fichiers seront sauvegardés par les mêmes données et métadonnées d'inode. Après l'avoir utilisé, il est sûr de supprimer le lien créé car le nombre de références à l'inode sera toujours supérieur à 0.
Ça ne ./my=file marche pas ? % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). Cela devrait être portable car ./myn'est pas un nom de variable valide, donc ne devrait pas être analysé de cette façon.
Stephen Harris
2
Comme le dit ce texte POSIX, le problème ne se pose que lorsque le premier =est précédé d' un trait de soulignement ou d' un caractère alphabétique du jeu de caractères portable (voir le tableau dans le volume Définitions de base de IEEE Std 1003.1-2001, Section 6.1, Jeu de caractères portable), suivi d'une séquence de traits de soulignement, de chiffres et d'alphabets du jeu de caractères portable . donc un chemin de fichier comme ++foo=bar.txtou =fooou ./foo=barsont tous OK comme ça .ou +n'est pas un [_a-zA-Z].
Stéphane Chazelas
1
@SergiyKolodyazhnyy awk est externe au shell, donc peu importe celui que vous utilisez. ./my=filesera transmis mot pour mot.
Chris Down
1
@SergiyKolodyazhnyy, idem pour awk '{print $1,$2}' /etc/passwd. Le fait est que le fait d'ouvrir le shell par opposition à awk ne fait aucune différence quant à savoir s'il le rend recherchable ou non. En fait, dans awk '{exit}' < /etc/passwd, vous vous attendez awkà revenir à la fin du premier enregistrement exitpour vous assurer qu'il laisse la position au sein de stdin. POSIX l'exige. /usr/xpg4/bin/awkle fait sur Solaris, mais ni gawkne mawksemble le faire sur GNU / Linux.
Stéphane Chazelas
3
@mosvy, voir la section INPUT FILES sur pubs.opengroup.org/onlinepubs/9699919799/utilities/… Il est utile dans un certain nombre de modèles d'utilisation qui n'ont de sens qu'avec des fichiers normaux comme lorsque vous souhaitez tronquer un fichier ou y écrire des données à l' adresse une position identifiée de awkcette façon.
Tous les arguments supplémentaires sur la ligne de commande sont normalement traités comme des fichiers d'entrée à traiter dans l'ordre spécifié. Cependant, un argument qui a la forme var = value, assigne la valeur value à la variable var — il ne spécifie pas du tout de fichier.
Pourquoi la commande s'arrête-t-elle et attend-elle? Parce que dans le formulaire, awk 'processing_script_here' my=file.txtil n'y a pas de fichier spécifié par la définition ci-dessus - my=file.txtest interprété comme une affectation de variable, et s'il n'y a pas de fichier défini awklira stdin (également évident à partir de stracece qui montre que awk dans une telle commande attend read(0,'...)syscall.
Ceci est également documenté dans les spécifications POSIX awk , voir la section OPERANDS et les affectations qui en font partie)
L'affectation des variables est évidente dans la mesure awk '{print foo}' foo=bar /etc/passwdoù la valeur de fooest imprimée pour chaque ligne dans / etc / passwd. La spécification ./foo=barou le chemin complet fonctionne cependant.
Notez que l' exécution stracesur awk '1' foo=barainsi que de vérifier avec cat foo=barmontre que ce problème est awk spécifique et exec ne nom de fichier show comme argument passé, de sorte que des obus ont rien à voir avec les assignations de variables env dans ce cas.
De plus, veuillez noter que awk '...script...' foo=barcela ne provoquera pas la création de variables d'environnement par le shell, car les affectations de variables d'environnement doivent précéder une commande pour prendre effet. Voir Règles de grammaire du shell POSIX , point numéro 7. De plus, cela peut être vérifié viaawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd
Réponses:
Comme le dit Chris , les arguments du formulaire
variablename=anything
sont traités comme des affectations de variables (qui sont effectuées au moment où les arguments sont traités par opposition aux (plus récents)-v var=value
qui sont effectués avant lesBEGIN
instructions) au lieu des noms de fichiers d'entrée.Cela peut être utile dans des choses comme:
Où vous pouvez spécifier un fichier différent
FS
/RS
par. Il est également couramment utilisé dans:Qui est une version plus sûre de:
(qui ne fonctionne pas s'il
file1
est vide)Mais cela gêne lorsque vous avez des fichiers dont le nom contient des
=
caractères.Maintenant, ce n'est qu'un problème lorsque ce qui reste du premier
=
est unawk
nom de variable valide .Ce qui constitue un nom de variable valide dans
awk
est plus strict que danssh
.POSIX exige que ce soit quelque chose comme:
Avec uniquement des caractères du jeu de caractères portable. Cependant,
/usr/xpg4/bin/awk
Solaris 11 au moins n'est pas conforme à cet égard et autorise tous les caractères alphabétiques dans les paramètres régionaux dans les noms de variables, pas seulement a-zA-Z.Ainsi, un argument comme
x+y=foo
ou=bar
ou./foo=bar
est toujours traité comme un nom de fichier d'entrée et non comme une affectation car ce qui reste du premier=
n'est pas un nom de variable valide. Un argument commeStéphane=Chazelas.txt
may ou may not, selon l'awk
implémentation et les paramètres régionaux.C'est pourquoi avec awk, il est recommandé d'utiliser:
au lieu de
par exemple pour éviter le problème si vous ne pouvez pas garantir que le nom des
txt
fichiers ne contiendra pas de=
caractères.Gardez également à l'esprit qu'un argument comme celui-ci
-vfoo=bar.txt
peut être traité comme une option si vous utilisez:(applique également
awk '{code}' -vfoo=bar.txt
avec lesawk
des versions busybox avant 1.28.0, voir correspondant rapport de bogue ).Encore une fois, l'utilisation
./*.txt
fonctionne autour de cela (l'utilisation d'un./
préfixe aide également avec un fichier appelé-
qui autrementawk
comprend comme signifiant une entrée standard à la place).C'est aussi pourquoi
les bangs ne fonctionnent pas vraiment. Alors que
var=value
ceux-ci peuvent être contournés en fixant lesARGV
valeurs (ajoutez un./
préfixe) dans uneBEGIN
instruction:Cela n'aidera pas les options car celles-ci sont vues par
awk
et non leawk
script.Un problème cosmétique potentiel avec l'utilisation de ce
./
préfixe est qu'il se termineFILENAME
, mais vous pouvez toujours l'utilisersubstr(FILENAME, 3)
pour le supprimer si vous ne le souhaitez pas.L'implémentation GNU
awk
corrige tous ces problèmes avec son-E
option.Après
-E
, gawk n'attend que le chemin duawk
script (où-
signifie toujours stdin) puis une liste de chemins de fichiers d'entrée uniquement (et là, même pas-
traité spécialement).Il est spécialement conçu pour:
shebangs où la liste des arguments sont toujours des fichiers d'entrée (notez que vous êtes toujours libre de modifier cette
ARGV
liste dans uneBEGIN
déclaration).Vous pouvez également l'utiliser comme:
Nous utilisons
-E
avec un script vide (/dev/null
) juste pour nous assurer que ces*.txt
derniers sont toujours traités comme des fichiers d'entrée, même s'ils contiennent des=
caractères.la source
../foo
,/path/to/foo
et les chemins qui sont dans un codage différent) - dans ce cas,substr(FILENAME,3)
ce ne sera pas suffisant, ou c'est un script unique où l'utilisateur sait essentiellement quels sont les noms de fichiers - dans ce cas, il ne devrait probablement pas se soucier de l'un d'eux contenant=
non plus ;-)./
problèmes, mais qu'il peut être indésirable dans certaines conditions, telles que les cas où le nom de fichier doit être inclus dans la sortie, auquel cas il./
doit être redondant et inutile, donc vous je vais devoir m'en débarrasser. Voici au moins un exemple . En ce qui concerne l'utilisateur sachant quels sont les noms de fichiers - eh bien, dans ce cas, nous savons également quel est le nom de fichier, mais cela=
empêche toujours un traitement approprié. Le leader peut donc se-
mettre en travers../
préfixe pour contourner cetteawk
(mauvaise) fonctionnalité, mais vous vous retrouvez avec une./
sortie en sortie que vous voudrez peut-être supprimer. Voir comment vérifier si la première ligne de fichier contient une chaîne spécifique? par exemple../
mais aussi le global (chemin absolu)/
qui fait que awk interprète l'argument comme un fichier.Dans la plupart des versions d'awk, les arguments après le programme à exécuter sont soit:
x=y
Étant donné que votre nom de fichier est interprété comme le cas # 2, awk attend toujours que quelque chose soit lu sur stdin (car il ne perçoit pas qu'un nom de fichier a été transmis).
Portablement, ce comportement est documenté dans POSIX :
En tant que tel, de manière portable, vous avez quelques options (# 1 est probablement le moins intrusif):
awk ... ./my=file
, qui contourne cela car ce.
n'est pas "un caractère de soulignement ou alphabétique du jeu de caractères portable".awk ... < my=file
. Cependant, cela ne fonctionne pas bien avec plusieurs fichiers.ln my=file my_file
, puis l'utilisermy_file
comme d'habitude. Aucune copie ne sera effectuée et les deux fichiers seront sauvegardés par les mêmes données et métadonnées d'inode. Après l'avoir utilisé, il est sûr de supprimer le lien créé car le nombre de références à l'inode sera toujours supérieur à 0.la source
./my=file
marche pas ?% awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory).
Cela devrait être portable car./my
n'est pas un nom de variable valide, donc ne devrait pas être analysé de cette façon.=
est précédé d' un trait de soulignement ou d' un caractère alphabétique du jeu de caractères portable (voir le tableau dans le volume Définitions de base de IEEE Std 1003.1-2001, Section 6.1, Jeu de caractères portable), suivi d'une séquence de traits de soulignement, de chiffres et d'alphabets du jeu de caractères portable . donc un chemin de fichier comme++foo=bar.txt
ou=foo
ou./foo=bar
sont tous OK comme ça.
ou+
n'est pas un[_a-zA-Z]
../my=file
sera transmis mot pour mot.awk '{print $1,$2}' /etc/passwd
. Le fait est que le fait d'ouvrir le shell par opposition à awk ne fait aucune différence quant à savoir s'il le rend recherchable ou non. En fait, dansawk '{exit}' < /etc/passwd
, vous vous attendezawk
à revenir à la fin du premier enregistrementexit
pour vous assurer qu'il laisse la position au sein de stdin. POSIX l'exige./usr/xpg4/bin/awk
le fait sur Solaris, mais nigawk
nemawk
semble le faire sur GNU / Linux.awk
cette façon.Pour citer la documentation de Gawk (note soulignée ajoutée):
Pourquoi la commande s'arrête-t-elle et attend-elle? Parce que dans le formulaire,
awk 'processing_script_here' my=file.txt
il n'y a pas de fichier spécifié par la définition ci-dessus -my=file.txt
est interprété comme une affectation de variable, et s'il n'y a pas de fichier définiawk
lira stdin (également évident à partir destrace
ce qui montre que awk dans une telle commande attendread(0,'...)
syscall.Ceci est également documenté dans les spécifications POSIX awk , voir la section OPERANDS et les affectations qui en font partie)
L'affectation des variables est évidente dans la mesure
awk '{print foo}' foo=bar /etc/passwd
où la valeur defoo
est imprimée pour chaque ligne dans / etc / passwd. La spécification./foo=bar
ou le chemin complet fonctionne cependant.Notez que l' exécution
strace
surawk '1' foo=bar
ainsi que de vérifier aveccat foo=bar
montre que ce problème est awk spécifique et exec ne nom de fichier show comme argument passé, de sorte que des obus ont rien à voir avec les assignations de variables env dans ce cas.De plus, veuillez noter que
awk '...script...' foo=bar
cela ne provoquera pas la création de variables d'environnement par le shell, car les affectations de variables d'environnement doivent précéder une commande pour prendre effet. Voir Règles de grammaire du shell POSIX , point numéro 7. De plus, cela peut être vérifié viaawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd
la source