Comment utiliser plusieurs arguments pour awk avec un shebang (ie #!)?

118

Je voudrais exécuter un script gawk en --re-intervalutilisant un shebang. L'approche «naïve» de

#!/usr/bin/gawk --re-interval -f
... awk script goes here

ne fonctionne pas, car gawk est appelé avec le premier argument "--re-interval -f"(non divisé autour de l'espace blanc), qu'il ne comprend pas. Y a-t-il une solution de contournement pour cela?

Bien sûr, vous pouvez soit ne pas appeler directement gawk mais l'envelopper dans un script shell qui divise le premier argument, soit créer un script shell qui appelle ensuite gawk et place le script dans un autre fichier, mais je me demandais s'il y avait un moyen de faire ceci dans un fichier.

Le comportement des lignes shebang diffère d'un système à l'autre - du moins dans Cygwin, il ne divise pas les arguments par des espaces. Je me soucie juste de savoir comment le faire sur un système qui se comporte comme ça; le script n'est pas destiné à être portable.

Hans-Peter Störr
la source
1
Une expérience idiote que je viens de faire était avec un script utilisant un autre script sur la ligne shebang, qui a divisé les arguments correctement.
Hasturkun
@Hasturkun, cela soulève un autre problème, à savoir que le comportement des lignes shebang diffère également d'un système à l'autre, si le programme appelé peut lui-même être un script.
dubiousjim
Avec les versions récentes de gawk (> = 4.0), ce --re-intervaln'est plus nécessaire (voir [ gnu.org/software/gawk/manual/… ).

Réponses:

25

Cela semble fonctionner pour moi avec (g) awk.

#!/bin/sh
arbitrary_long_name==0 "exec" "/usr/bin/gawk" "--re-interval" "-f" "$0" "$@"


# The real awk program starts here
{ print $0 }

Notez les #!exécutions /bin/sh, donc ce script est d'abord interprété comme un script shell.

Au début, j'ai simplement essayé "exec" "/usr/bin/gawk" "--re-interval" "-f" "$0" "$@", mais awk a traité cela comme une commande et imprimé chaque ligne d'entrée sans condition. C'est pourquoi j'ai mis le arbitrary_long_name==0- il est censé échouer tout le temps. Vous pouvez le remplacer par une chaîne de charabia. En gros, je cherchais une fausse condition dans awk qui n'affecterait pas négativement le script shell.

Dans le script shell, le arbitrary_long_name==0définit une variable appelée arbitrary_long_nameet la définit égale à =0.

Aaron McDaid
la source
C'est ma réponse, mais je me demande s'il est suffisamment portable et robuste. Cela dépend-il spécifiquement de bash, ou fonctionnera-t-il avec n'importe quel POSIX sh? Et je n'utilise pas awksouvent, donc je ne suis pas sûr que mon truc sur la deuxième ligne soit un bon moyen de forcer awkà ignorer la ligne.
Aaron McDaid du
Juste ce que je me demandais, +1, mais probablement déconseillé (d'où les votes relatifs).
Aaron Hall
Pouvez-vous expliquer les problèmes que cela pourrait avoir, @AaronHall? Tant que la variable arbitrary_long_namen'entre pas en conflit avec une variable utilisée dans le vrai programme awk, je ne vois aucun problème. Y a-t-il quelque chose qui me manque?
Aaron McDaid
Utilisez à la #!/bin/sh -place de #!/bin/shpour empêcher le script de se comporter de manière dangereuse s'il est invoqué avec un argument zéro qui a -comme premier caractère. Cela peut arriver accidentellement dans les langages de programmation comme C, où il est facile de gâcher accidentellement en oubliant de passer le nom du programme invoqué dans le cadre du tableau d'arguments à execveet des fonctions similaires, et si les gens oublient habituellement de se protéger contre cela, cela peut aussi finissent par être la dernière étape d'une vulnérabilité exploitable de manière malveillante qui permet à un attaquant d'obtenir un shell interactif.
mtraceur
161

La ligne shebang n'a jamais été spécifiée dans le cadre de POSIX, SUS, LSB ou toute autre spécification. AFAIK, il n'a même pas été correctement documenté.

Il existe un consensus approximatif sur ce qu'il fait: prendre tout entre le !et le \net execlui. L'hypothèse est que tout entre le !et le \nest un chemin absolu complet vers l'interpréteur. Il n'y a pas de consensus sur ce qui se passe s'il contient des espaces.

  1. Certains systèmes d'exploitation traitent simplement l'ensemble comme le chemin. Après tout, dans la plupart des systèmes d'exploitation, les espaces ou les tirets sont légaux dans un chemin.
  2. Certains systèmes d'exploitation se divisent en espaces et traitent la première partie comme le chemin vers l'interpréteur et le reste comme des arguments individuels.
  3. Certains systèmes d'exploitation se divisent au premier espace et traitent la partie avant comme le chemin vers l'interpètre et le reste comme un argument unique (ce que vous voyez).
  4. Certains ont même ne prennent pas en charge les lignes Shebang du tout .

Heureusement, 1. et 4. semblent avoir disparu, mais 3. est assez répandu, vous ne pouvez donc tout simplement pas vous fier à pouvoir passer plus d'un argument.

Et puisque l'emplacement des commandes est également non spécifiée dans POSIX ou SUS, vous utilisez généralement jusqu'à ce seul argument en passant de l'exécutable nom de envsorte que il peut déterminer l'emplacement de l'exécutable; par exemple:

#!/usr/bin/env gawk

[De toute évidence, cela suppose toujours un chemin particulier pour env, mais il n'y a que très peu de systèmes où il vit /bin, donc c'est généralement sûr. L'emplacement de envest beaucoup plus standardisé que l'emplacement de gawkou pire quelque chose comme pythonou rubyou spidermonkey.]

Ce qui signifie que vous ne pouvez pas utiliser effectivement des arguments du tout .

Jörg W Mittag
la source
1
L'environnement de FreeBSD a un -Scommutateur qui aide ici, mais il n'est pas présent sur mon Linux env, et je soupçonne qu'il n'est pas disponible sur gygwin non plus. @hstoerr, d'autres utilisateurs avec des situations différentes peuvent lire vos questions plus tard, donc en général, des réponses portables sont préférables, même si vous n'avez pas besoin de portabilité.
dubiousjim
4
Nous ne pouvons donc pas utiliser d'arguments de manière portative dans un shebang. Mais que faire si nous avons besoin d'arguments par tous les moyens nécessaires? Je suppose que la solution est d'écrire un script shell wrapper contenant #!/bin/shet /usr/bin/env gawk --re-interval -f my-script.awk. Est-ce exact?
Rory O'Kane
1
Je ne suis pas d'accord. Vous pouvez utiliser un seul argument de manière portable. Tout système où vous ne pouvez utiliser aucun argument échoue lamentablement à implémenter cet Unixisme traditionnel, qui est ce qu'est le hash-bang. Si les non-implémentations sont équitables, alors nous pouvons affirmer en toute sécurité qu'elle #!n'est pas portable. Par exemple, Windows ne reconnaît pas du tout cette convention "nativement". Un seul argument a bang est nécessaire sur Unix pour pouvoir le faire #!/usr/bin/awk -f.
Kaz le
7
@Kaz: Oui, mais comme les chemins de nombreux binaires ne sont pas standardisés, vous utilisez votre seul argument pour #!/usr/bin/env rubyou les goûts.
Jörg W Mittag
3
@Pacerier: modifiez la spécification POSIX et attendez 20 à 30 ans jusqu'à ce que tous les systèmes aient été mis à jour pour être conformes à la spécification.
Jörg W Mittag
18

Bien que pas exactement portable, à partir de coreutils 8.30 et selon sa documentation, vous pourrez utiliser:

#!/usr/bin/env -S command arg1 arg2 ...

Alors donné:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

tu auras:

% ./test.sh 
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'

et au cas où vous êtes curieux, showargsc'est:

#!/usr/bin/env sh
echo "\$0 is '$0'"

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done

Réponse originale ici .

unode
la source
1
Pour info, FreeBSD a eu -S pendant des années (depuis 6.0). Il s'agit d'un ajout de portabilité bienvenu à coreutils.
Juan
12

Je suis tombé sur le même problème, sans solution apparente à cause de la façon dont les espaces blancs sont traités dans un shebang (au moins sous Linux).

Cependant, vous pouvez passer plusieurs options dans un shebang, du moment qu'il s'agit d' options courtes et qu'elles peuvent être concaténées (à la manière GNU).

Par exemple, vous ne pouvez pas avoir

#!/usr/bin/foo -i -f

mais tu peux avoir

#!/usr/bin/foo -if

De toute évidence, cela ne fonctionne que lorsque les options ont des équivalents courts et ne prennent aucun argument.

ℝaphink
la source
11

Sous Cygwin et Linux, tout ce qui suit le chemin du shebang est analysé dans le programme comme un argument.

Il est possible de contourner cela en utilisant un autre awkscript à l'intérieur du shebang:

#!/usr/bin/gawk {system("/usr/bin/gawk --re-interval -f " FILENAME); exit}

Cela s'exécutera {system("/usr/bin/gawk --re-interval -f " FILENAME); exit}dans awk.
Et cela s'exécutera /usr/bin/gawk --re-interval -f path/to/your/script.awkdans votre shell système.

Moritz
la source
2
cela ne fonctionnera pas si vous avez passé des arguments au script
Steven Penny
4
#!/bin/sh
''':'
exec YourProg -some_options "$0" "$@"
'''

L'astuce de shébang de coquille ci-dessus est plus portable que /usr/bin/env.

user3123730
la source
Le '' ':' est un hold-over parce que ma solution originale était pour un script python donc le '' ':' dit à l'interpréteur python d'ignorer la partie exec.
user3123730
4
Je pense que vous êtes défavorisé parce que votre solution est pour python, mais cette question concerne awk.
Aaron McDaid le
1
Excellent hack pour python.
Zaar Hai
3

Dans le manuel de gawk (http://www.gnu.org/manual/gawk/gawk.html), la fin de la section 1.14 note que vous ne devez utiliser qu'un seul argument lorsque vous exécutez gawk depuis une ligne shebang. Il dit que le système d'exploitation traitera tout après le chemin de gawk comme un seul argument. Peut-être existe-t-il une autre façon de spécifier l' --re-intervaloption? Peut-être que votre script peut référencer votre shell dans la ligne shebang, s'exécuter en gawktant que commande et inclure le texte de votre script en tant que "document ici".

bta
la source
Il semble qu'il n'y ait pas d'autre moyen de spécifier l'option. Vous avez raison: gawk -f - << EOF, quelques lignes de scripts, EOF fonctionne, mais cela m'empêche de lire l'entrée standard avec gawk.
Hans-Peter Störr
Le document ici consomme le flux d'entrée standard pour gawk, mais vous pouvez toujours être en mesure de diriger quelque chose vers stderr (c'est-à-dire rediriger stdout vers stderr avant de rediriger vers ce script). Je n'ai jamais vraiment essayé cela, mais tant que le premier processus n'émet rien sur stderr, cela peut fonctionner. Vous pouvez également créer un tube nommé ( linuxjournal.com/content/using-named-pipes-fifos-bash ) si vous voulez vous assurer que rien d'autre ne l'utilise.
bta
3

Pourquoi ne pas utiliser bashet gawklui - même, pour ignorer shebang, lire le script et le transmettre en tant que fichier à une deuxième instance de gawk [--with-whatever-number-of-params-you-need]?

#!/bin/bash
gawk --re-interval -f <(gawk 'NR>3' $0 )
exit
{
  print "Program body goes here"
  print $1
}

(-la même chose pourrait naturellement être accomplie avec par exemple sedou tail, mais je pense qu'il y a une sorte de beauté qui ne dépend que de bashet d' gawkelle - même;)

conny
la source
0

Juste pour le plaisir: il existe la solution assez étrange suivante qui redirige stdin et le programme via les descripteurs de fichier 3 et 4. Vous pouvez également créer un fichier temporaire pour le script.

#!/bin/bash
exec 3>&0
exec <<-EOF 4>&0
BEGIN {print "HALLO"}
{print \$1}
EOF
gawk --re-interval -f <(cat 0>&4) 0>&3

Une chose est ennuyeuse à ce sujet: le shell fait l'expansion des variables sur le script, vous devez donc citer chaque $ (comme cela est fait dans la deuxième ligne du script) et probablement plus que cela.

Hans-Peter Störr
la source
-1

Pour une solution portable, utilisez awk plutôt que gawk, invoquez le shell BOURNE standard ( /bin/sh) avec votre shebang, et invoquez awkdirectement, en passant le programme sur la ligne de commande en tant que document here plutôt que via stdin:

#!/bin/sh
gawk --re-interval <<<EOF
PROGRAM HERE
EOF

Remarque: aucun -fargument pour awk. Cela laisse stdindisponible pour awklire les entrées. En supposant que vous ayez gawkinstallé et sur votre PATH, cela réalise tout ce que je pense que vous essayiez de faire avec votre exemple d'origine (en supposant que vous vouliez que le contenu du fichier soit le script awk et non l'entrée, ce que je pense que votre approche shebang l'aurait traité comme ).

lharper71
la source
3
Cela n'a pas fonctionné pour moi. L'homme bash dit <<< blabla met blabla sur stdin. Vouliez-vous dire << - EOF? Dans tous les cas, cela met également le programme sur stdin.
Hans-Peter Störr