Ao usar sudo -s
(abreviação da opção "--shell" ), é possível passar um comando "sudo", nesse caso ele executará o comando em um shell iniciado por "sudo" como usuário de destino.
(Da mesma forma, sudo -i
, também disponível como a opção "--login" , também inicia um shell e da mesma forma aceita um comando, que se comporta da mesma maneira.)
Executar comandos no sudo por meio de um shell pode ser importante em muitos casos:
- Ao usar curingas em um diretório ao qual o usuário atual não tem acesso, mas o root tem, o comando precisa ser executado em um shell de root para que os curingas sejam expandidos adequadamente.
- Executando um pipeline inteiro, muitos comandos encadeados em pipes (
|
). - Ao executar um shell embutido, como
for
,if
, etc. Executar um pequeno "script" inline inteiro em um únicosudo
pode ser útil.
A documentação da opção "-s" diz (ênfase minha):
Execute o shell especificado pela
SHELL
variável de ambiente se estiver definido ou o shell especificado pela entrada do banco de dados de senha do usuário que está chamando. Se um comando for especificado, ele será passado para o shell para execução por meio da opção -c do shell . Se nenhum comando for especificado, um shell interativo será executado. Observe que a maioria dos shells se comporta de maneira diferente quando um comando é especificado em comparação com uma sessão interativa; consulte o manual do shell para detalhes.
Em outras palavras, ao passar sudo -s
um comando, ele é passado para o shell usando a -c
opção, que pega uma string com um "script" e depois passa a executá-lo como um script de shell.
A documentação não vai muito além sobre como usar esta opção, ou apresentar exemplos, exceto dizer "consulte o manual do shell para detalhes". Isso implica que o comando recebido é passado diretamente para a opção do shell -c
. No entanto, como se vê, esse não é o caso.
Passar um script de shell com várias palavras falha:
$ sudo -s 'ls -ld /var/empty'
/bin/bash: ls -ld /var/empty: No such file or directory
A mensagem de erro indica que está tentando executar a string inteira como um comando simples... Hmmm, ok, então talvez adicionar espaços funcione? Sim, é o caso:
$ sudo -s ls -ld /var/empty
drwxr-xr-x. 3 root root 18 Jul 12 21:48 /var/empty
Mas não é bem assim que o shell -c
funciona... Bem, vamos tentar usar alguns metacaracteres, como ~
, que é um atalho para o diretório home, para ver como isso se comporta. Observe as ~
necessidades a serem citadas, para evitar que o shell não sudo o expanda (nesse caso, ele expandiria para a casa do usuário não root, em vez do /root
que é esperado):
$ sudo -s ls '~'
ls: cannot access '~': No such file or directory
Ok, então isso não funciona, e a saída de erro parece implicar que a expansão não está acontecendo, pois está preservando um literal ~
lá.
E os curingas? Também não funciona:
$ sudo -s ls '/root/*.cfg'
ls: cannot access '/root/*.cfg': No such file or directory
Em ambos os casos, executar o comando com $SHELL -c
funciona bem. Nesse caso, $SHELL
é bash, então:
$ sudo bash -c 'ls ~'
anaconda-ks.cfg
$ sudo bash -c 'ls /root/*.cfg'
/root/anaconda-ks.cfg
Uma exceção é que as variáveis parecem funcionar em sudo -s
, como:
$ sudo -s echo '$HOME'
/root
Então:
- O que está acontecendo aqui?
- Por que curingas e metacaracteres como
~
não funcionam no comando passado parasudo -s
ousudo -i
? - Dado que
$SHELL -c
recebe uma única string com um script, massudo -s
recebe vários argumentos, como o script é montado a partir dos argumentos? - Qual é uma maneira confiável de executar comandos em um shell via
sudo
?
TL;DR: Ao tomar um comando nas opções "--shell" ou "--login",
sudo
irá escapar a maioria dos caracteres (usando escapes de barra invertida), incluindo todos os metacaracteres (exceto$
), incluindo também espaços.Isso interrompe todos os casos de uso em que você deseja usar um shell, o que torna
sudo -s
a maioria inadequada para executar comandos de shell que precisam ser executados como comandos de shell.Em vez de
sudo -s
, usesudo sh -c '...'
orsudo bash -c '...'
(ou qualquer que seja o esperado$SHELL
.) Em vez desudo -i
, usesudo bash -l -c '...'
em vez disso (assumindo que o shell do root é bash novamente).Observando a parte relevante do código-fonte sudo , há este trecho:
O trecho é executado em cada caractere de cada argumento. Se o caractere não for alfanumérico, sublinhado, travessão ou cifrão, ele será escapado com uma barra invertida.
Isso significa que curingas
*
,?
,[...]
, etc. serão escapados para\*
,\?
,\[...\]
.Também metacaracteres como
~
,;
,|
, etc. serão escapados para\~
,\;
,\|
.Uma string com espaços
ls /root
será escapada parals\ /root
.Por alguma razão,
$
é uma exceção no escape, é por isso quesudo -s echo '$HOME'
funciona, já que o$
será mantido sem escape. (Observe que isso só funciona para variáveis, e nem mesmo para o${HOME}
formulário, caso em que as chaves serão escapadas, o que quebrará a expressão.)As consequências dessa citação é que a maioria dos casos que exigem a execução de um shell como root não podem realmente se beneficiar do
sudo -s <command>
formato:|
será escapado e também não funcionará.for
eif
normalmente precisam usar;
, que será escapado e também não funcionará. Uma nova linha seria uma alternativa, mas também não parece haver uma maneira de introduzir uma nova linha sem escape.Resumindo, o formulário
sudo -s <command>
parece funcionar apenas para casos que não envolvem curingas, pipelines, scriptlets, etc. Mas então, você normalmente não precisa de um shell para executar esses comandos! Apenas executarsudo <command>
sem o-s
normalmente é suficiente para esses casos.Não está claro qual a motivação para a implementação do sudo. Talvez para manter alguma consistência entre o manuseio de comandos ao adicionar ou remover o arquivo
-s
. Talvez também porque-i
precede-s
e, nesse caso, há uma pequena diferença em como as variáveis de ambiente são definidas ...SOLUÇÃO ALTERNATIVA: A solução alternativa é executar o shell
-c
explicitamente.Isso envolve saber o que
$SHELL
é para o usuário de destino (o que pode não corresponder ao usuário atual, portanto, usar$SHELL
diretamente nem sempre é correto).Por exemplo, em vez de
sudo -s ls -l '/root/*.cfg'
, use:E em vez de
sudo -i ls -l '~'
, use:(o argumento do bash
-l
cria um shell de "login", que é equivalente ao criado porsudo -i
.)