In che modo il reindirizzamento dei file bash allo standard differisce dalla shell (`sh`) su Linux?


9

Ho scritto uno script che cambia gli utenti durante l'esecuzione e l'ho eseguito usando il reindirizzamento dei file allo standard in. Così user-switch.shè ...

#!/bin/bash

whoami
sudo su -l root
whoami

E eseguirlo con bashmi dà il comportamento che mi aspetto

$ bash < user-switch.sh
vagrant
root

Tuttavia, se eseguo lo script con sh, ottengo un output diverso

$ sh < user-switch.sh 
vagrant
vagrant

Perché bash < user-switch.shdare un output diverso da sh < user-switch.sh?

Appunti:

  • succede su due scatole diverse che eseguono Debian Jessie

1
Non una risposta, ma per quanto riguarda sudo su: unix.stackexchange.com/questions/218169/…
Kusalananda

1
è / bin / sh = trattino su Debian? Non riesco a riprodurre su RHEL dove / bin / sh = bash
Jeff Schaller

1
/bin/shsu (la mia copia di) Debian è Dash, sì. Non sapevo nemmeno cosa fosse fino ad ora. Fino ad ora mi sono bloccato con bash.
popedotninja,

1
Il comportamento bash non è garantito per funzionare da nessuna semantica documentata.
Charles Duffy,

Qualcuno mi ha fatto notare oggi che la sceneggiatura che stavo eseguendo ha violato la semantica di come si intende utilizzare bash. Ci sono state un paio di ottime risposte a questa domanda, ma vale la pena sottolineare che probabilmente non si dovrebbe cambiare utente a metà script. Inizialmente ho provato user-switch.shin un lavoro di Jenkins, e lo script è stato eseguito. L'esecuzione dello stesso script al di fuori di Jenkins ha prodotto risultati diversi e ho fatto ricorso al reindirizzamento dei file per ottenere il comportamento che ho visto in Jenkins.
popedotninja,

Risposte:


12

Uno script simile, senza sudo, ma risultati simili:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

Con bash, il resto dello script va come input per sed, con dash, la shell lo interpreta.

In esecuzione stracesu quelli: dashlegge un blocco dello script (otto KB qui, più che sufficiente per contenere l'intero script), e quindi genera sed:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Ciò significa che il filehandle è alla fine del file e sednon vedrà alcun input. La parte rimanente viene bufferizzata all'interno dash. (Se lo script fosse più lungo della dimensione del blocco di 8 kB, la parte rimanente verrebbe letta da sed.)

Bash, d'altra parte, cerca di nuovo alla fine dell'ultimo comando:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Se l'ingresso proviene da una pipe, come qui:

$ cat script.sh | bash

il riavvolgimento non può essere eseguito, poiché i tubi e le prese non sono ricercabili. In questo caso, Bash torna a leggere l'input di un carattere alla volta per evitare di leggere troppo. ( fd_to_buffered_stream()ininput.c ) Effettuare una chiamata di sistema completa per ogni byte non è molto efficace in linea di principio. In pratica, non credo che le letture saranno un grande sovraccarico rispetto, ad esempio, al fatto che la maggior parte delle cose che la shell comporta generano processi completamente nuovi.

Una situazione simile è questa:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

La subshell deve assicurarsi di readleggere solo la prima riga nuova, in modo che headveda la riga successiva. (Funziona dashanche con questo.)


In altre parole, Bash fa di tutto per supportare la lettura della stessa fonte per lo script stesso e per i comandi eseguiti da essa. dashnon lo fa. Il zsh, e ksh93confezionato in Debian, va con Bash su questo.


1
Francamente, sono un po 'sorpreso che funzioni.
ilkkachu,

Grazie per la magnifica risposta! Eseguirò entrambi i comandi usando in straceseguito e confronterò gli output. Non avevo pensato di usare questo approccio.
popedotninja,

2
Sì, la mia reazione alla lettura della domanda era sorpresa che fa lavoro con bash - avevo archiviato come qualcosa che sicuramente non avrebbe funzionato. Immagino di aver ragione solo a metà :)
hobbs il

12

La shell sta leggendo lo script dall'input standard. All'interno dello script, si esegue un comando che vuole anche leggere l'input standard. Quale input andrà dove? Non puoi dirlo in modo affidabile .

Il modo in cui funzionano le shell è che leggono un pezzo di codice sorgente, lo analizzano e, se trovano un comando completo, eseguono il comando, quindi procedono con il resto del blocco e il resto del file. Se il blocco non contiene un comando completo (con un carattere finale alla fine - penso che tutte le shell leggano fino alla fine di una riga), la shell legge un altro blocco e così via.

Se un comando nello script prova a leggere dallo stesso descrittore di file da cui la shell sta leggendo lo script, allora il comando troverà tutto ciò che viene dopo l'ultimo blocco che ha letto. Questa posizione è imprevedibile: dipende dalla dimensione del blocco selezionata dalla shell e che può dipendere non solo dalla shell e dalla sua versione, ma anche dalla configurazione della macchina, dalla memoria disponibile, ecc.

Bash cerca la fine del codice sorgente di un comando nello script prima di eseguire il comando. Questo non è qualcosa su cui puoi contare, non solo perché altre shell non lo fanno, ma anche perché funziona solo se la shell sta leggendo da un file normale. Se la shell legge da una pipe (ad es. ssh remote-host.example.com <local-script-file.sh), I dati letti vengono letti e non possono essere letti.

Se si desidera passare l'input a un comando nello script, è necessario farlo in modo esplicito, in genere con un documento qui . (Un documento qui è di solito il più conveniente per l'input multilinea, ma qualsiasi metodo lo farà.) Il codice che hai scritto funziona solo in poche shell, solo se lo script viene passato come input alla shell da un normale file; se ti aspettavi che il secondo whoamifosse passato come input a sudo …, ripensaci, tenendo presente che la maggior parte delle volte lo script non è passato all'input standard della shell.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

Si noti che in questo decennio è possibile utilizzare sudo -i root. La corsa sudo suè un trucco del passato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.