TL; DR: Ao tomar um comando nas opções "--shell" ou "--login", sudo
irá escapar da maioria dos caracteres (usando escapes de barra invertida), incluindo todos os metacaracteres ( exceto por $
), incluindo também espaços.
Isso quebra todos os casos de uso nos quais você deseja usar um shell, o que torna sudo -s
inadequada para executar comandos do shell que precisam para serem executados como comandos do shell.
Em vez de sudo -s
, use sudo sh -c '...'
ou sudo bash -c '...'
(ou seja qual for o% esperado$SHELL
.) Em vez de sudo -i
, use sudo bash -l -c '...'
(assumindo que o shell do root seja bash novamente.)
Analisando a parte relevante do código fonte do sudo , há este snippet:
/* quote potential meta characters */
if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\';
*dst++ = *src;
O fragmento é executado sobre cada caractere de cada argumento. Se o caractere não for alfanumérico, sublinhado, traço ou cifrão, ele será salvo com uma barra invertida.
Isso significa que os caracteres curinga *
, ?
, [...]
, etc. serão ignorados para \*
, \?
, \[...\]
.
Também metacaracteres, como ~
, ;
, |
, etc., serão escapados para \~
, \;
, \|
.
Uma string com espaços ls /root
será ignorada para ls\ /root
.
Por algum motivo, $
é uma exceção no escape, é por isso que sudo -s echo '$HOME'
funciona, pois o $
será mantido sem escape. (Observe que isso só funciona para variáveis, e nem mesmo para a forma ${HOME}
, caso em que as chaves serão escapadas, o que quebrará a expressão.)
As conseqüências dessa citação é que a maioria dos casos que exigem executar um shell como root não pode se beneficiar do formato sudo -s <command>
:
- Os wilcards não serão expandidos, pois terão escape e não funcionarão como curingas.
- Os pipelines não funcionarão, pois o canal
|
será ignorado e também não funcionará. - Comandos como
for
eif
normalmente precisam usar;
, que será escapado e não funcionará também. Uma nova linha seria uma alternativa, mas também não parece haver uma maneira de introduzir uma nova linha sem escape.
Em suma, o formulário sudo -s <command>
parece funcionar apenas para casos que não envolvem curingas, pipelines, scriptlets, etc. Mas, em seguida, você normalmente não precisa de um shell para executar esses comandos! Apenas executar sudo <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 tratamento de comandos ao adicionar ou remover o -s
. Talvez também porque -i
preceda -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 é executar o -c
explicitamente.
Isso envolve saber que $SHELL
é para o usuário de destino (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:
$ sudo bash -c 'ls -l /root/*.cfg'
E, em vez de sudo -i ls -l '~'
, use:
$ sudo bash -l -c 'ls -l ~'
(O argumento -l
do bash cria um shell de "login", que é equivalente ao criado por sudo -i
.)