Qual interpretador de shell executa um script sem shebang?

14

Suponha que o shell padrão para minha conta seja zsh, mas eu abri o terminal, atuei e executei um script chamado prac002.sh , que interpretador de shell seria usado para executar o script, zsh ou bash? Considere o seguinte exemplo:

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % sudo cat /etc/passwd | grep papagolf
[sudo] password for papagolf: 
papagolf:x:1000:1001:Rex,,,:/home/papagolf:/usr/bin/zsh
# papagolf's default shell is zsh

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % bash
# I fired up bash. (See that '%' prompt in zsh changes to '$' prompt, indicating bash.)

papagolf@Sierra:~/My Files/My Programs/Learning/Shell$ ./prac002.sh 
Enter username : Rex
Rex
# Which interpreter did it just use?

** EDIT: ** Aqui está o conteúdo do script

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % cat ./prac002.sh 
read -p "Enter username : " uname
echo $uname
    
por 7_R3X 25.06.2017 / 08:48

3 respostas

14

Como o script não começa com uma linha #! shebang indicando qual interpretador usar, POSIX diz que :

If the execl() function fails due to an error equivalent to the [ENOEXEC] error defined in the System Interfaces volume of POSIX.1-2008, the shell shall execute a command equivalent to having a shell invoked with the pathname resulting from the search as its first operand, with any remaining arguments passed to the new shell, except that the value of "$0" in the new shell may be set to the command name. If the executable file is not a text file, the shell may bypass this command execution. In this case, it shall write an error message, and shall return an exit status of 126.

Esse fraseado é um pouco ambíguo e diferentes shells têm diferentes interpretações.

Nesse caso, o Bash executará o script usando ele mesmo . Por outro lado, se você executá-lo a partir do zsh, o zsh usaria sh (seja lá o que estiver no seu sistema).

Você pode verificar esse comportamento para este caso adicionando essas linhas ao script:

echo $BASH_VERSION
echo $ZSH_VERSION

Você notará que, do Bash, a primeira linha produz sua versão, enquanto a segunda nunca diz nada, não importa qual shell você usa.

  • Se o seu /bin/sh for, digamos, dash , nenhuma das linhas emitirá nada quando o script for executado a partir do zsh ou do traço.
  • Se o seu /bin/sh for um link para o Bash, você verá a saída da primeira linha em todos os casos.
  • Se /bin/sh for uma versão diferente do Bash do que você estava usando diretamente, você verá resultados diferentes quando executar o script diretamente do bash e a partir do zsh.

O comando ps -p $$ da resposta do rools também mostrará informações úteis sobre o comando que o shell usou para executar o script.

    
por 25.06.2017 / 09:41
7

Como o arquivo não é de nenhum dos tipos de executável reconhecido pelo sistema e supondo que você tenha permissão para executar esse arquivo, a chamada do sistema execve() normalmente falhará com ENOEXEC ( não um executável ).

O que acontece, então, é com a função de aplicativo e / ou biblioteca usada para executar o comando.

Isso pode ser, por exemplo, um shell, a função execlp() / execvp() libc.

A maioria dos outros aplicativos usará um desses quando executar um comando. Eles invocarão um shell, por exemplo, pela função system("command line") libc, que normalmente chamará sh para analisar essa linha de comando (o caminho pode ser determinado em tempo de compilação (como /bin/sh vs /usr/xpg4/bin/sh no Solaris). )), ou invoca o shell armazenado em $SHELL por si só como vi com seu comando ! , ou xterm -e 'command line' e muitos outros comandos ( su user -c invocará o shell de login do usuário em vez de $SHELL ).

Geralmente, um arquivo de texto sem texto que não comece com # é considerado como um script sh . Qual sh é variando.

execlp() / execvp() , sobre execve() retornando ENOEXEC normalmente invocará sh sobre ele. Para sistemas que têm mais de um sh , porque podem estar em conformidade com mais de um padrão, que sh normalmente será determinado no tempo de compilação (do aplicativo usando execvp() / execlp() vinculando um blob diferente de código que se refere a um caminho diferente para sh ). Por exemplo, no Solaris, isso será /usr/xpg4/bin/sh (um padrão, POSIX sh ) ou /bin/sh (o shell Bourne (um shell antiquado) no Solaris 10 e mais antigo, ksh93 no Solaris 11).

Quando se trata de projéteis, há muita variação. bash , AT & T ksh , o shell Bourne normalmente interpretará o script em si (em um processo filho, a menos que exec seja usado) depois de ter simulado um execve() , ou seja, todas as variáveis não exportadas foram fechadas. os fds close-on-exec, removeram todos os traps customizados, aliases, funções ... ( bash interpretará o script no modo sh ). yash será executado em si mesmo (com sh como argv[0] so no modo sh ) para interpretá-lo.

Os shells com base em zsh , pdksh , ash normalmente chamarão sh (o caminho do qual determinado no tempo de compilação).

Para csh e tcsh (e o sh de alguns BSDs iniciais), se o primeiro caractere do arquivo for # , eles serão executados para interpretá-lo e sh caso contrário. Isso remonta a um tempo pré-shebang em que csh reconheceu # como comentários, mas não o shell Bourne, então o # foi uma dica de que era um script csh.

fish (pelo menos versão 2.4.0), apenas retorna um erro se execve() falhar (ele não tenta tratá-lo como um script).

Alguns shells (como bash ou AT & T ksh ) primeiro tentam determinar heuristicamente se o arquivo provavelmente é um script ou não. Então você pode achar que alguns shells se recusarão a executar um script se ele tiver um caractere NUL nos primeiros bytes.

Observe também que se execve() falhar com ENOEXEC, mas o arquivo tiver uma linha shebang, alguns shells tentam interpretar eles mesmos a linha shebang.

Então, alguns exemplos:

  • Quando $SHELL for /bin/bash , xterm -e 'myscript with args' terá myscript interpretado por bash em sh mode. Enquanto com xterm -e myscript with args , xterm usará execvp() , então o script será interpretado por sh .
  • su -c myscript no Solaris 10, onde o shell de login de root é /bin/sh e /bin/sh é que o shell Bourne terá myscript interpretado pelo shell Bourne.
  • /usr/xpg4/bin/awk 'BEGIN{system("myscript")' no Solaris 10 fará com que seja interpretado por /usr/xpg4/bin/sh (o mesmo para /usr/xpg4/bin/env myscript ).
  • find . -prune -exec myscript {} \; no Solaris 10 (usando execvp() ) fará com que seja interpretado por /bin/sh mesmo com /usr/xpg4/bin/find , mesmo em um ambiente POSIX (um bug de conformidade).
  • csh -c myscript terá interpretado por csh se começar com # , com sh caso contrário.

No geral, você não pode ter certeza de qual shell será usado para interpretar esse script se você não souber como e por que ele será invocado.

Em todo caso, a sintaxe read -p é bash -only, portanto, convém garantir que o script seja interpretado por bash (e evitar essa extensão .sh enganosa). Você conhece o caminho do executável bash e usa:

#! /path/to/bash -
read -p ...

Ou você pode confiar em uma consulta $PATH do executável bash (assumindo que bash está instalado) usando:

#! /usr/bin/env bash
read -p ...

( env é quase encontrado em /usr/bin ). Alternativamente, você pode torná-lo compatível com POSIX + Bourne, caso em que você pode usar /bin/sh . Todos os sistemas terão um /bin/sh . Na maioria deles será (na maior parte) compatível com POSIX, mas você ainda pode encontrar de vez em quando um Bourne shell.

#! /bin/sh -
printf >&2 'Enter a user name: '
read user
printf '%s\n' "$user"
    
por 25.06.2017 / 19:31
0

Quando você não tem nenhuma linha #! (chamada shebang ), sh é usado. Para verificar isso, você pode executar o seguinte script.

ps -p $$
echo -n "The real shell is: "
realpath /proc/$$/exe

No meu computador, recebo

  PID TTY          TIME CMD
13718 pts/16   00:00:00 sh
The real program is: /usr/bin/bash

mesmo se meu shell padrão for zsh . Ele usa o bash pois na minha máquina, o comando sh é implementado pelo bash .

    
por 25.06.2017 / 09:39