Como usar vários argumentos para awk com um shebang (ou seja, #!)?

118

Eu gostaria de executar um script gawk--re-interval usando um shebang. A abordagem "ingênua" de

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

não funciona, uma vez que gawk é chamado com o primeiro argumento "--re-interval -f"(não dividido em torno do espaço em branco), que ele não entende. Existe uma solução alternativa para isso?

Claro que você não pode chamar o gawk diretamente, mas envolvê-lo em um script de shell que divide o primeiro argumento ou fazer um script de shell que chama o gawk e coloca o script em outro arquivo, mas eu queria saber se havia alguma maneira de fazer isso dentro de um arquivo.

O comportamento das linhas shebang difere de sistema para sistema - pelo menos no Cygwin ele não divide os argumentos por espaços em branco. Eu só me preocupo em como fazer isso em um sistema que se comporta assim; o script não foi feito para ser portátil.

Hans-Peter Störr
fonte
1
Um experimento bobo que acabei de fazer foi com um script usando outro script na linha shebang, que dividiu os argumentos corretamente.
Hasturkun
@Hasturkun, que levanta outra questão, que o comportamento das linhas shebang também difere de sistema para sistema, se o programa invocado pode ser um script.
dubiousjim
Com as versões recentes do gawk (> = 4.0), --re-intervalnão é mais necessário (consulte [ gnu.org/software/gawk/manual/… ).

Respostas:

25

Isso parece funcionar para mim com (g) awk.

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


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

Observe as #!execuções /bin/sh, portanto, este script é primeiro interpretado como um script de shell.

No início, eu simplesmente tentei "exec" "/usr/bin/gawk" "--re-interval" "-f" "$0" "$@", mas awk tratou isso como um comando e imprimiu todas as linhas de entrada incondicionalmente. É por isso que eu coloquei arbitrary_long_name==0- é para falhar o tempo todo. Você pode substituí-lo por algum barbante sem sentido. Basicamente, eu estava procurando por uma condição falsa no awk que não afetaria adversamente o script de shell.

No script de shell, o arbitrary_long_name==0define uma variável chamada arbitrary_long_namee a define igual a =0.

Aaron McDaid
fonte
Esta é a minha resposta, mas me pergunto se é suficientemente portátil e robusto. Depende especificamente do bash, ou funcionará com qualquer POSIX sh? E não uso com awkfrequência, então não tenho certeza se meu truque na segunda linha é uma boa maneira de forçar awka ignorar a linha.
Aaron McDaid
Exatamente o que eu estava pensando, +1, mas provavelmente desaconselhável (daí os votos relativos).
Aaron Hall
Você pode explicar quais problemas isso pode ter, @AaronHall? Contanto que a variável arbitrary_long_namenão entre em conflito com uma variável usada no programa awk real, não vejo nenhum problema. Tem algo que estou perdendo?
Aaron McDaid
Use em #!/bin/sh -vez de #!/bin/shpara proteger o script de possivelmente se comportar mal de maneira perigosa se invocado com um argumento zero que tem -como primeiro caractere. Isso pode acontecer acidentalmente em linguagens de programação como C, onde é fácil bagunçar acidentalmente, esquecendo-se de passar o nome do programa invocado como parte da matriz do argumento para execvefunções semelhantes, e se as pessoas costumam esquecer de se proteger contra isso, também pode acabam sendo a última etapa em uma vulnerabilidade que pode ser explorada de forma mal-intencionada, permitindo que um invasor obtenha um shell interativo.
mtraceur
161

A linha shebang nunca foi especificada como parte de POSIX, SUS, LSB ou qualquer outra especificação. AFAIK, nem mesmo foi devidamente documentado.

Há um consenso aproximado sobre o que ele faz: pegar tudo entre o !e o \ne execisso. A suposição é que tudo entre o !e o \né um caminho completo e absoluto para o intérprete. Não há consenso sobre o que acontece se contiver espaços em branco.

  1. Alguns sistemas operacionais simplesmente tratam a coisa toda como o caminho. Afinal, na maioria dos sistemas operacionais, espaços em branco ou travessões são permitidos em um caminho.
  2. Alguns sistemas operacionais se dividem em espaços em branco e tratam a primeira parte como o caminho para o interpretador e o resto como argumentos individuais.
  3. Alguns sistemas operacionais se dividem no primeiro espaço em branco e tratam a parte frontal como o caminho para o intérprete e o resto como um único argumento (que é o que você está vendo).
  4. Alguns até não suportam linhas shebang em tudo .

Felizmente, 1. e 4. parecem ter morrido, mas 3. é bastante difundido, então você simplesmente não pode confiar em ser capaz de passar mais de um argumento.

E como a localização dos comandos também não é especificada em POSIX ou SUS, você geralmente usa esse único argumento passando o nome do executável para envpara que ele possa determinar a localização do executável; por exemplo:

#!/usr/bin/env gawk

[Obviamente, isso ainda pressupõe um caminho específico para env, mas existem apenas alguns sistemas onde ele vive /bin, então geralmente é seguro. A localização de envé muito mais padronizada do que a localização de gawkou, pior ainda, de algo como pythonou rubyou spidermonkey.]

O que significa que você não pode realmente usar quaisquer argumentos em tudo .

Jörg W Mittag
fonte
1
O env do FreeBSD tem um -Sswitch que ajuda aqui, mas ele não está presente no meu Linux env, e suspeito que também não esteja disponível no gygwin. @hstoerr, outros usuários em diferentes situações podem ler suas perguntas mais tarde, portanto, em geral, respostas portáteis são preferíveis, mesmo se você não precisar de portabilidade agora.
dubiousjim
4
Portanto, não podemos usar argumentos portáteis em uma shebang. Mas e se precisarmos de argumentos por qualquer meio necessário? Estou supondo que a solução é escrever um script de shell wrapper contendo #!/bin/she /usr/bin/env gawk --re-interval -f my-script.awk. Isso está correto?
Rory O'Kane
1
Eu não concordo. Você pode usar facilmente um argumento. Qualquer sistema onde você não pode usar nenhum argumento falha miseravelmente em implementar este Unixismo tradicional, que é o que é o hash-bang. Se as não implementações forem um jogo justo, então podemos dizer com segurança que em #!si não é portátil. Por exemplo, o Windows não reconhece essa convenção "nativamente" de forma alguma. Um argumento único é necessário no Unix tradicionalmente para ser capaz de fazer #!/usr/bin/awk -f.
Kaz
7
@Kaz: Sim, mas como os caminhos de muitos binários não são padronizados, você usa seu único argumento para #!/usr/bin/env rubyou outros semelhantes.
Jörg W Mittag
3
@Pacerier: Mude a especificação POSIX e espere 20-30 anos até que todos os sistemas tenham sido atualizados para estarem em conformidade com a especificação.
Jörg W Mittag de
18

Embora não seja exatamente portátil, a partir do coreutils 8.30 e de acordo com sua documentação, você poderá usar:

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

Dado assim:

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

você vai ter:

% ./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'

e caso você esteja curioso showargsé:

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

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

Resposta original aqui .

unode
fonte
1
Para sua informação, o FreeBSD teve -S por anos (desde 6.0). Esta é uma adição de portabilidade bem-vinda para coreutils.
Juan
12

Eu me deparei com o mesmo problema, sem solução aparente por causa da forma como os espaços em branco são tratados de uma forma simples (pelo menos no Linux).

No entanto, você pode passar várias opções de uma vez, desde que sejam opções curtas e possam ser concatenadas (do jeito GNU).

Por exemplo, você não pode ter

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

mas você pode ter

#!/usr/bin/foo -if

Obviamente, isso só funciona quando as opções têm equivalentes curtos e não aceitam argumentos.

ℝaphink
fonte
11

No Cygwin e no Linux, tudo após o caminho do shebang é analisado no programa como um argumento.

É possível contornar isso usando outro awkscript dentro do shebang:

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

Isso será executado {system("/usr/bin/gawk --re-interval -f " FILENAME); exit}no awk.
E isso será executado /usr/bin/gawk --re-interval -f path/to/your/script.awkno shell do seu sistema.

Moritz
fonte
2
isso não funcionará se você tiver passado argumentos para o script
Steven Penny
4
#!/bin/sh
''':'
exec YourProg -some_options "$0" "$@"
'''

O truque do shell shebang acima é mais portátil do que /usr/bin/env.

user3123730
fonte
O '' ':' é uma demora porque minha solução original era para um script python, então o '' ':' diz ao interpretador python para ignorar a parte exec.
user3123730
4
Acho que você está sendo rejeitado porque sua solução é a favor python, mas esta questão é sobre awk.
Aaron McDaid
1
Excelente hack para python.
Zaar Hai
3

No manual do gawk (http://www.gnu.org/manual/gawk/gawk.html), no final da seção 1.14, observe que você deve usar apenas um único argumento ao executar o gawk a partir de uma linha shebang. Ele diz que o sistema operacional tratará tudo após o caminho para gawk como um único argumento. Talvez haja outra maneira de especificar a --re-intervalopção? Talvez o seu script possa referenciar seu shell na linha shebang, executar gawkcomo um comando e incluir o texto do seu script como um "documento aqui".

bta
fonte
Parece que não há outra maneira de especificar a opção. Você está certo: gawk -f - << EOF, algumas linhas de scripts, EOF funciona, mas me impede de ler a entrada padrão com o gawk.
Hans-Peter Störr
O documento here consome o fluxo de entrada padrão para gawk, mas você ainda pode ser capaz de canalizar algo em stderr (isto é, redirecionar stdout para stderr antes de canalizar para este script). Na verdade, nunca tentei fazer isso, mas desde que o primeiro processo não emita nada no stderr, pode funcionar. Você também pode criar um pipe nomeado ( linuxjournal.com/content/using-named-pipes-fifos-bash ) se quiser ter certeza de que nada mais o está usando.
bta
3

Por que não usar bashe gawkele mesmo, para pular o shebang, ler o script e passá-lo como um arquivo para uma segunda instância 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
}

(-a mesma poderia naturalmente também ser realizado com por exemplo, sedou tail, mas eu acho que há algum tipo de beleza dependendo apenas bashe gawkem si;)

conny
fonte
0

Apenas por diversão: existe a seguinte solução bastante estranha que redireciona stdin e o programa através dos descritores de arquivo 3 e 4. Você também pode criar um arquivo temporário para o 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

Uma coisa é irritante sobre isso: o shell faz expansão de variável no script, então você precisa citar cada $ (como feito na segunda linha do script) e provavelmente mais do que isso.

Hans-Peter Störr
fonte
-1

Para uma solução portátil, use em awkvez de gawkinvocar o shell BOURNE padrão ( /bin/sh) com seu shebang e invoque awkdiretamente, passando o programa na linha de comando como um documento here em vez de via stdin:

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

Nota: nenhum -fargumento para awk. Isso deixa stdindisponível para awkler a entrada de. Supondo que você tenha gawkinstalado e no seu PATH, isso atinge tudo que eu acho que você estava tentando fazer com seu exemplo original (supondo que você queria que o conteúdo do arquivo fosse o script awk e não a entrada, o que eu acho que sua abordagem shebang teria tratado como )

Lharper71
fonte
3
Isso não funcionou para mim. O bash man diz <<< blabla puts blabla on stdin. Você quis dizer << - EOF? De qualquer forma, isso também coloca o programa em stdin.
Hans-Peter Störr