Nella sequenza di cinque comandi di seguito, tutti dipendono da virgolette singole per trasferire la possibile sostituzione di variabili al denominato bash
shell anziché la shell chiamante. L'utente chiamante è xx , ma la shell chiamata verrà eseguita come utente yy . Il primo comando sostituisce $HOME con il valore della shell chiamante perché la shell chiamata non è una shell di login. Il secondo comando sostituisce il valore di $HOME caricato da una shell di login, quindi è il valore che appartiene all'utente yy . Il terzo comando non si basa su un valore $HOME e crea un file nella directory home ipotizzata dell'utente yy .
Perché il quarto comando non riesce? L'intenzione è che scriva lo stesso file, ma basandosi sulla variabile $HOME appartenente all'utente yy per assicurarsi che finisca effettivamente nella sua home directory. Non capisco perché una shell di accesso interrompa il comportamento di un comando here-doc passato come stringa statica con virgolette singole. Il fallimento del quinto comando verifica che questo problema non riguarda la sostituzione delle variabili.
[email protected] ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
[email protected] ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
[email protected] ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
[email protected] ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
[email protected] ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
Questi comandi sono stati immessi su un sistema Linux Mint 18.3 Cinnamon a 64 bit, basato su Ubuntu 16.04 (Xenial Xerus).
Aggiornamento: L'aspetto qui-doc sta solo offuscando il problema. Ecco una semplificazione del problema:
$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2
Perché il primo di questi due comandi preserva l'interruzione di riga e il secondo no? sudo
è comune a entrambi i comandi, ma sembra sfuggire/filtrare/interpolare in modo diverso a seconda di nient'altro che l'opzione "-i".
Risposta accettata:
La documentazione per -i
afferma:
Il -i
(simula login iniziale) esegue la shell specificata dalla voce del database delle password dell'utente di destinazione come shell di login. Ciò significa che i file di risorse specifici per l'accesso come .profile o .login verranno letti dalla shell. Se viene specificato un comando, questo viene passato alla shell per l'esecuzione tramite l'opzione -c della shell.
Cioè, esegue davvero la shell di accesso dell'utente, quindi passa qualsiasi comando tu abbia dato sudo
ad esso usando -c
– diversamente da cosa sudo cmd arg arg
di solito fa senza il -i
opzione. Normalmente, sudo
usa solo uno dei exec*
funziona direttamente per avviare il processo stesso, senza shell intermedia e tutti gli argomenti passati esattamente come sono.
Con -i
, configura l'ambiente, esegue la shell dell'utente come shell di login e ricostruisce il comando che hai chiesto di eseguire come argomento per bash -c
. Nel tuo caso, esegue (diciamo, approssimativamente) /bin/bash -c "bash -c ' ... '"
(immagina di citare che funzioni).
Il problema sta nel modo in cui sudo
trasforma il comando che hai scritto in qualcosa che -c
può occuparsi di , spiegato nella sezione successiva. L'ultima sezione presenta alcune possibili soluzioni, e in mezzo c'è qualche tecnica di debug e verifica,
Perché succede?
Quando si passa il comando a -c
, ha bisogno di un po' di pre-elaborazione per farlo fare la cosa giusta, che sudo
fa prima di eseguire la shell. Ad esempio, se il tuo comando è:
sudo -iu yy echo 'three spaces'
quindi è necessario eseguire l'escape di quegli spazi affinché il comando significhi la stessa cosa (cioè, affinché il singolo argomento non venga diviso in due parole). Ciò che finisce per essere eseguito è:
/bin/bash -c 'echo three spaces'
Iniziamo con il tuo comando semplificato:
sudo bash -c 'echo 1
echo 2'
In questo caso, sudo
cambia utente, quindi esegue execvp("bash", ["bash", "-c", "echo 1necho 2"])
(per una sintassi letterale di matrice inventata).
Con -i
:
sudo -i bash -c 'echo 1
echo 2'
invece cambia utente, quindi esegue execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"])
, dove \
equivale a un letterale e
n
è un'interruzione di riga. È sfuggito agli spazi e alla nuova riga nel comando principale facendoli precedere da barre inverse.
Cioè, c'è una shell di login esterna, che ha due argomenti:-c
e l'intero comando, ricostruito in una forma che la shell dovrebbe comprendere correttamente. Sfortunatamente, non è così. Il bash
interno il comando alla fine tenta di eseguire:
echo 1
echo 2
dove la prima riga fisica termina con una continuazione di riga (barra rovesciata seguita da nuova riga), che viene eliminata completamente. La linea logica è quindi solo echo 1echo 2
, che non fa quello che volevi.
C'è un argomento secondo cui questo è un difetto in sudo
sta scappando, dato il comportamento standard delle coppie barra rovesciata-nuova riga. Penso che dovrebbe essere sicuro lasciarli senza scampo qui.
Lo stesso accade per il tuo comando con un here-document. Funziona come, all'incirca:
/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'
dove