Come funziona il #! shebang funziona?

In uno script devi includere un #! sulla prima riga seguita dal percorso del programma che eseguirà lo script (es: sh, perl).

Per quanto ne so, il carattere # indica l’inizio di un commento e quella linea dovrebbe essere ignorata dal programma che esegue lo script. Sembrerebbe che questa prima riga sia a un certo punto letta da qualcosa in modo che lo script possa essere eseguito dal programma corretto.

Qualcuno potrebbe per favore gettare più luce sul funzionamento del #! ?

Sono davvero curioso di questo, quindi più approfondisci la risposta, meglio è.

Lettura consigliata:

  • FAQ UNIX: Perché alcuni script iniziano con #! …?
  • Il #! magia, dettagli sul meccanismo shebang / hash-bang su vari gusti Unix
  • Wikipedia: Shebang

Il programma di caricamento del kernel di unix è responsabile di ciò. Quando viene chiamato exec() , chiede al kernel di caricare il programma dal file al suo argomento. Quindi controllerà i primi 16 bit del file per vedere quale formato eseguibile ha. Se trova che questi bit sono #! utilizzerà il resto della prima riga del file per trovare il programma da avviare e fornisce il nome del file che stava tentando di avviare (lo script) come ultimo argomento del programma interprete.

L’interprete funziona quindi normalmente e tratta il #! come una linea di commento.

Breve storia: la riga shebang ( #! ) Viene letta dalla shell (ad es. sh , bash , ecc.) Dal programma di caricamento del sistema operativo. Mentre sembra formalmente un commento, il fatto che siano i primi due byte di un file contrassegna l’intero file come un file di testo e come uno script. Lo script verrà passato all’eseguibile menzionato sulla prima riga dopo lo shebang. Ecco!


Storia leggermente più lunga: immagina di avere il tuo script, foo.sh , con il bit eseguibile ( x ) impostato. Questo file contiene ad esempio quanto segue:

 #!/bin/sh # some script commands follow...: # *snip* 

Ora, sulla tua shell, digiti:

 > ./foo.sh 

Modifica: Si prega di leggere anche i commenti qui sotto o prima di leggere quanto segue! A quanto pare, mi sono sbagliato. Apparentemente non è la shell che passa lo script all’interprete di destinazione, ma il sistema operativo (kernel) stesso.

Ricorda che lo scrivi all’interno del processo shell (supponiamo che questo sia il programma /bin/sh ). Pertanto, tale input dovrà essere elaborato da quel programma. Interpreta questa linea come un comando, poiché scopre che la prima cosa immessa sulla linea è il nome di un file che esiste realmente e che ha il bit oi bit eseguibili impostati.

/bin/sh quindi inizia a leggere il contenuto del file e scopre lo shebang ( #! ) proprio all’inizio del file. Alla shell, questo è un token (“numero magico”) con il quale sa che il file contiene uno script.

Ora, come fa a sapere quale linguaggio di programmazione è stato scritto dallo script? Dopo tutto, è ansible eseguire script Bash, script Perl, script Python, … Tutta la shell sa fino ad ora che sta guardando un file di script (che non è un file binario, ma un file di testo). In questo modo legge l’input successivo fino alla prima interruzione di riga (che risulterà in /bin/sh , confronta con quanto sopra). Questo è l’interprete al quale verrà passato lo script per l’esecuzione. (In questo caso particolare, l’interprete di destinazione è la shell stessa, quindi non deve invocare una nuova shell per lo script, ma elabora semplicemente il resto del file di script stesso.)

Se lo script era destinato ad es. /bin/perl , tutto ciò che l’interprete Perl avrebbe (facoltativamente) da fare è vedere se la riga shebang menziona realmente l’interprete Perl. In caso contrario, l’interprete Perl saprebbe che non può eseguire questo script. Se infatti l’interprete Perl è menzionato nella riga shebang, legge il resto del file di script e lo esegue.

La exec sistema exec kernel Linux utilizza i byte iniziali #! per identificare il tipo di file

Quando fai su bash:

 ./something 

su Linux, chiama la exec sistema exec con il percorso ./something .

Questa riga viene chiamata nel kernel sul file passato a exec : https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm-> buf [0]! = ‘#’) || (bprm-> buf [1]! = ‘!’))

Legge i primissimi byte del file e li confronta con #! .

Se il confronto è vero, il resto della riga viene analizzato dal kernel Linux, il che fa richiamare un altro exec con path /usr/bin/env python e il file corrente come primo argomento:

 /usr/bin/env python /path/to/script.py 

e questo funziona per qualsiasi linguaggio di scripting che usa # come carattere di commento.

E sì, puoi creare un ciclo infinito con:

 printf '#!/a\n' | sudo tee /a sudo chmod +x /a /a 

Bash riconosce l’errore:

 -bash: /a: /a: bad interpreter: Too many levels of symbolic links 

#! è leggibile dall’uomo, ma non è necessario.

Se il file fosse stato avviato con diversi byte, la exec sistema exec avrebbe utilizzato un gestore diverso. L’altro gestore incorporato più importante è per i file eseguibili ELF: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305 che controlla i byte 7f 45 4c 46 (che anche sembra essere umano leggibile per .ELF ), che legge il file elf, lo mette in memoria correttamente e inizia un nuovo processo con esso. Vedi anche: In che modo il kernel ottiene un file eseguibile eseguibile sotto Linux?

Infine, puoi aggiungere i tuoi handbang handler con il meccanismo binfmt_misc . Ad esempio, è ansible aggiungere un gestore personalizzato per i file .jar . Questo meccanismo supporta anche i gestori per estensione di file. Un’altra applicazione è eseguire in modo trasparente i file eseguibili di un’architettura diversa con QEMU .

Non penso però che POSIX specifichi gli shebang: https://unix.stackexchange.com/a/346214/32558