Como esse shebang que começa com um hífen duplo (-) funciona?

14

Encontrei o seguinte tipo de shebang na página RosettaCode:

--() { :; }; exec db2 -txf "$0"

Funciona para Db2, e uma coisa semelhante para o Postgres. No entanto, eu não entendo toda a linha.

Eu sei que o traço duplo é um comentário no SQL e, depois disso, chama o executável Db2 com alguns parâmetros passando o próprio arquivo como arquivo. Mas e os parênteses, as braçadeiras encaracoladas, o cólon e o ponto e vírgula e como substituir um verdadeiro zumbido #! ?

https://rosettacode.org/wiki/Multiline_shebang#PostgreSQL

AngocA
fonte

Respostas:

18

Relacionado: Qual interpretador de shell executa um script sem shebang?

O script não possui uma linha shebang / hashbang / #!, simplesmente porque não há um traço duplo #!.

No entanto, o script será executado por um shell (veja as perguntas e respostas vinculadas acima) e, nesse shell, se -for um caractere válido em um nome de função, a linha declara uma função do shell chamada --que não faz nada (bem, ele executa :, que não faz nada ) e que nunca é chamado.

A função, na notação multilinha mais comum (apenas para tornar mais óbvio o que parece, pois seu nome estranho meio que obscurece o fato de que é de fato uma função):

-- () {
  :
}

O único objetivo da definição da função é ter uma linha válida em um script de shell e, ao mesmo tempo, um comando SQL válido (um comentário). Esse tipo de código é chamado poliglota .

Após declarar a função de shell falsa, o script, quando executado por um interpretador de script de shell, usa execpara substituir o shell atual pelo processo resultante da execução db2 -txf "$0", o que seria o mesmo que usar db2 -txfno nome do caminho do script na linha de comando.

Esse truque provavelmente não funcionaria de maneira confiável em sistemas em que dashou em outros ashshells baseados em shell, yasho shell Bourne, ksh88ou ksh93é usado como /bin/sh, pois esse shell não aceita funções cujo nome contenha hífens.

Também relacionado:


Suponho que o seguinte também funcionaria (não realmente testado):

--() { exec db2 -txf "$0"; }; --
Kusalananda
fonte
@ilkkachu Melhor agora?
Kusalananda
1
Ai sim! E obrigado por me lembrar como esse tipo de coisa é chamado. :)
ilkkachu
6

Como o @Kusalananda já disse, esse truque está quebrado e não funcionará em todos os aspectos.

Aqui está minha opinião sobre como fazê-lo de maneira portável:

--/.. 2>/dev/null; exec db2 -txf "$0"

O primeiro comando deve falhar, mesmo que --exista um arquivo / diretório nomeado no diretório atual e quaisquer erros sejam encerrados pelo 2>/dev/null; o shell continuará com o segundo comando, o exec.

Tio Billy
fonte
Ainda não é realmente portátil. Não é um script válido e você ainda está contando com o shell de chamada para solucionar o fato de que o kernel se recusará a executar o script e retornará ENOEXECse você tentar. Tente executar o script abaixo stracepara ver o que quero dizer.
kasperd
@ Kasperd, ainda deve ser portátil, o shell deve executar o script como um script de shell, se exec()não funcionar. "Se a função execl () falhar devido a um erro equivalente ao erro [ENOEXEC], o shell executará um comando equivalente a ter um shell invocado com o nome do comando como seu primeiro operando, ..." (consulte pubs.opengroup .org / onlinepubs / 9699919799.2018edition / utilities /… )
ilkkachu
@ilkkachu Mas os scripts nem sempre são executados a partir de um shell. Se você tentar usar o script em qualquer outro contexto em que um executável funcione, ele falhará. Além disso, as conchas não concordam com qual intérprete usar. Portanto, seu script agora se comportará de maneira diferente ou falhará completamente, dependendo de qual contexto ele está sendo chamado.
kasperd
@ Kasperd, bem, claro, não vai funcionar se você exec()diretamente de algo diferente de um shell. Mas qual seria esse caso? Você pode querer executar o script a partir de algo cronassim, mas acho que ele executa tudo através de um shell de qualquer maneira, e mesmo se não for, é fácil explicar db2 -txf /path/to/scriptnesse caso, já que você só precisa fazê-lo uma vez. Ter o trabalho taquigráfico é muito útil em um shell interativo. Mas com certeza, um script de wrapper separado pode ser mais robusto.
Ilkkachu
1
@kasperd Não vou incomodá-lo com documentos e normas; apenas tente! echo 'int main(int c,char**a){execvp(a[1],a+1);}' | cc -include unistd.h -xc -; echo echo yeah > a.sh; chmod 755 a.sh; ./a.out ./a.sh; PATH=`pwd` ./a.out a.sh
Tio Billy