script bash: resultados diferentes quando chamados com ou sem sudo

10

No Ubuntu 16.04.3, tenho um script bash muito simples:

test.sh

[[ 0 == 0 ]] && result="true" || result="false"
echo $result
echo $USER $SHELL $0

Quando eu o chamo como usuário não raiz meou como root, ele funciona conforme o esperado. Se eu usar sudo ./test.sh, ele reclama de um erro de sintaxe:

$ ./test.sh
true
me /bin/bash ./test.sh

$ sudo su
# ./test.sh 
true
root /bin/bash ./test.sh

# exit
$ sudo ./test.sh
./test.sh: 1: ./test.sh: [[: not found
false
root /bin/bash ./test.sh

O que poderia estar causando isso? Como posso corrigi-lo para que mepossa usar esse script normalmente e com sudo?

James Newton
fonte
3
Dica profissional: não faz sentido corrersudo su . Basta executar sudo -iou em sudo -svez disso.
terdon
@terdon sudo -ialtera a localização para /root. sudo suou sudo -snão altere o local do diretório.
James Newton
Sim, leia a pergunta à qual vinculei anteriormente e por quê. E desculpe, eu editei meu comentário anterior, esqueci de mencionar -s.
terdon

Respostas:

20

Todo script começa com um Shebang , sem ele, o shell que inicia o script não sabe qual intérprete deve executar o seu script 1 e pode - como no caso sudo ./script.shaqui - executá-lo sh, ao qual o Ubuntu 16.04 está vinculado dash. A expressão condicional [[ é um bashcomando composto , portanto dash, não sabe como lidar com isso e lança o erro que você encontrou.

A solução aqui é adicionar

#!/bin/bash

como a primeira linha do seu script. Você pode obter o mesmo resultado quando o chama explicitamente sudo bash ./script.sh, mas um shebang é o caminho a percorrer.
Para verificar qual shell executa seu script, adicione echo $0-o. Não é o mesmo queecho $SHELL , citando wiki.archlinux.org :

SHELL contém o caminho para o shell preferido do usuário. Observe que este não é necessariamente o shell atualmente em execução, embora o Bash defina essa variável na inicialização.

1: Como você começou ./test.shcom bashele apenas assumiu bash, o mesmo vale para o sudo susubnível.

sobremesa
fonte
1
Observe também que o bash executa um script sem shebang usando o bash, não /bin/sh.
25717 muru
@dessert Isso corrige. Obrigado! Como posso verificar em um script qual shell está executando? ( echo $0Dá-me o nome do script: ./test.sh)
James Newton
@ JamesNewton de maneira portátil, AFAIK, mas você pode verificar para que serve /proc/$$/exe. Além disso, você pode testar várias variáveis como $BASH_VERSION, $ZSH_VERSION, etc. (mas traço não define qualquer variável)
Muru
5

Como o @dessert explicou , o problema aqui é que seu script não tem uma linha shebang . Sem um shebang, sudoo padrão é tentar executar o arquivo usando /bin/sh. Não consegui encontrá-lo documentado em nenhum lugar, mas confirmei verificando o sudocódigo-fonte onde encontrei o seguinte no arquivo pathnames.h:

#ifndef _PATH_BSHELL
#define _PATH_BSHELL "/bin/sh"
#endif /* _PATH_BSHELL */

Isso significa "definir se a variável _PATH_BSHELLnão estiver definida, defina-a como /bin/sh". Então, no configurescript incluído no tarball de origem, temos:

for p in "/bin/bash" "/usr/bin/sh" "/sbin/sh" "/usr/sbin/sh" "/bin/ksh" "/usr/bin/ksh" "/bin/bash" "/usr/bin/bash"; do
    if test -f "$p"; then
    found=yes
    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $p" >&5
$as_echo "$p" >&6; }
    cat >>confdefs.h <<EOF
#define _PATH_BSHELL "$p"
EOF

    break
    fi
done

Este circuito irá procurar /bin/bash, /usr/bin/sh, /sbin/sh, /usr/sbin/shou /bin/kshe, em seguida, define o _PATH_BSHELLque o que foi encontrado pela primeira vez . Como /bin/shfoi o primeiro na lista e existe, _PATH_BSHELLestá definido como /bin/sh. O resultado de tudo isso é que o shell padrão, a sudomenos que seja definido de outra forma, é /bin/sh.

Portanto, sudoo padrão será executar as coisas usando /bin/she, no Ubuntu, que é um link simbólico para dash, um mínimo de shell compatível com POSIX:

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Feb 27  2015 /bin/sh -> dash

A [[construção é um recurso bash, não é definido pelo padrão POSIX e não é entendido por dash:

$ bash -c '[[ true ]] && echo yes'
yes
$ dash -c '[[ true ]] && echo yes'
dash: 1: [[: not found

Em detalhes, nas três invocações que você tentou:

  1. ./test.sh

    Não sudo; na ausência de uma linha shebang, seu shell tentará executar o próprio arquivo. Como você está executando bash, isso funcionará bash ./test.she funcionará efetivamente .

  2. sudo suseguido por ./test.sh.

    Aqui, você está iniciando um novo shell para o usuário root. Este será o shell definido na $SHELLvariável de ambiente para esse usuário e, no Ubuntu, o shell padrão do root é bash:

    $ grep root /etc/passwd
    root:x:0:0:root:/root:/bin/bash
  3. sudo ./test.sh

    Aqui, você está deixando sudoexecutar o comando diretamente. Como seu shell padrão é /bin/shcomo explicado acima, isso faz com que ele execute o script /bin/sh, o que é dashe falha desde que dashele não entende [[.


Nota : os detalhes de como sudodefine o shell padrão parecem um pouco mais complexos. Tentei alterar os arquivos mencionados na minha resposta para apontar para, /bin/bashmas sudoainda era o padrão /bin/sh. Portanto, deve haver outros lugares no código-fonte em que o shell padrão está definido. No entanto, o ponto principal (que o sudopadrão é sh) ainda permanece.

Terdon
fonte
Não consegui encontrá-lo documentado em nenhum lugar - nem eu, apenas presumi que usaria a /bin/shpartir da mensagem de erro - o que mais poderia ser possível? A pergunta é respondida lindamente em Qual shell o sudo usa · SO , consulte também a man sudoseção COMMAND EXECUTION . Acontece sudoque não usa um shell intermediário !
Dessert
1
@dessert sim, ele usa sua própria implementação da execvechamada de sistema à qual o padrão é sh. E não, shells intermediários são irrelevantes, não se trata do shell que executa o comando, mas do interpretador de shell usado para ler o script de shell fornecido. Portanto, não, ele não inicia um shell intermediário, mas ainda precisa de um interpretador de shell para scripts de shell.
terdon