Argomenti multipli in shebang


33

Mi chiedo se esiste un modo generale di passare più opzioni a un eseguibile tramite la linea shebang ( #!).

Uso NixOS, e la prima parte dello shebang in qualsiasi script che scrivo è di solito /usr/bin/env. Il problema che incontro è che tutto ciò che viene dopo viene interpretato come un singolo file o directory dal sistema.

Supponiamo, ad esempio, che io voglia scrivere uno script da eseguire bashin modalità posix. Il modo ingenuo di scrivere lo shebang sarebbe:

#!/usr/bin/env bash --posix

ma il tentativo di eseguire lo script risultante produce il seguente errore:

/usr/bin/env: ‘bash --posix’: No such file or directory

Sono a conoscenza di questo post , ma mi chiedevo se esistesse una soluzione più generale e più pulita.


EDIT : So che per gli script Guile , c'è un modo per ottenere ciò che voglio, documentato nella Sezione 4.3.4 del manuale:

 #!/usr/bin/env sh
 exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
 !#

Il trucco, qui, è che la seconda riga (a partire da exec) viene interpretata come codice da shma, essendo nel blocco #!... !#come un commento, e quindi ignorata, dall'interprete Guile.

Non sarebbe possibile generalizzare questo metodo ad alcun interprete?


Secondo EDIT : dopo aver suonato un po 'in giro, sembra che, per gli interpreti in grado di leggere i loro input stdin, il seguente metodo funzionerebbe:

#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;

Probabilmente non è ottimale, poiché il shprocesso dura fino a quando l'interprete non ha terminato il suo lavoro. Qualsiasi feedback o suggerimento sarebbe apprezzato.



Risposte:


27

Non esiste una soluzione generale, almeno non se è necessario supportare Linux, poiché il kernel di Linux tratta tutto ciò che segue la prima "parola" nella riga shebang come un singolo argomento .

Non sono sicuro di quali siano i vincoli di NixOS, ma in genere scriverei semplicemente il tuo shebang

#!/bin/bash --posix

o, ove possibile, impostare le opzioni nello script :

set -o posix

In alternativa, è possibile riavviare lo script con il richiamo della shell appropriato:

#!/bin/sh -

if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi

shift

# Processing continues

Questo approccio può essere generalizzato ad altre lingue, purché si trovi un modo per la prima coppia di righe (che sono interpretate dalla shell) di essere ignorate dalla lingua di destinazione.

GNU coreutils" envfornisce una soluzione alternativa dalla versione 8.30, vedere la risposta di unode per i dettagli. (Questo è disponibile in Debian 10 e versioni successive, RHEL 8 e versioni successive, Ubuntu 19.04 e versioni successive, ecc.)


18

Sebbene non sia esattamente portatile, a partire da coreutils 8.30 e secondo la sua documentazione sarai in grado di utilizzare:

#!/usr/bin/env -S command arg1 arg2 ...

Quindi dato:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

otterrete:

% ./test.sh 
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'

e nel caso tu sia curioso showargsè:

#!/usr/bin/env sh
echo "\$0 is '$0'"

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done

Questo è molto buono da sapere per riferimento futuro.
John McGehee,

Quell'opzione è stata copiata da FreeBSD envdove è -Sstata aggiunta nel 2005. Vedi lists.gnu.org/r/coreutils/2018-04/msg00011.html
Stéphane Chazelas

Funziona a meraviglia su Fedora 29
Eric

@unode alcuni miglioramenti di showargs: pastebin.com/q9m6xr8H e pastebin.com/gS8AQ5WA (one-liner)
Eric

Cordiali saluti: a partire da coreutils 8.31, envinclude il proprio showargs: l'opzione -v es.#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy

9

Lo standard POSIX è molto conciso nel descrivere #!:

Dalla sezione logica della documentazione della exec()famiglia di interfacce di sistema :

Un altro modo in cui alcune implementazioni storiche gestiscono gli script di shell è riconoscendo i primi due byte del file come stringa di caratteri #!e usando il resto della prima riga del file come nome dell'interprete dei comandi da eseguire.

Dalla sezione Introduzione alla Shell :

La shell legge il suo input da un file (vedi sh), -cdall'opzione o dalle funzioni system()e popen()definite nel volume Interfacce di sistema di POSIX.1-2008. Se la prima riga di un file di comandi shell inizia con i caratteri #!, i risultati non sono specificati .

Questo in pratica significa che qualsiasi implementazione (Unix che stai usando) è libera di fare i dettagli dell'analisi della linea shebang come vuole.

Alcuni Unices, come macOS (impossibile testare ATM), suddivideranno gli argomenti forniti all'interprete sulla riga shebang in argomenti separati, mentre Linux e la maggior parte degli altri Unices forniranno gli argomenti come una singola opzione per l'interprete.

Non è quindi saggio fare affidamento sul fatto che la linea Shebang sia in grado di accettare più di un singolo argomento.

Vedi anche la sezione Portabilità dell'articolo Shebang su Wikipedia .


Una soluzione semplice, generalizzabile a qualsiasi utilità o linguaggio, è quella di creare uno script wrapper che esegua lo script reale con gli argomenti della riga di comando appropriati:

#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"

Non penso che proverei personalmente a farlo riapplicare se stesso in quanto sembra un po 'fragile.


7

Lo shebang è descritto nella execve(2) pagina man come segue:

#! interpreter [optional-arg]

In questa sintassi sono accettati due spazi:

  1. Uno spazio prima del percorso dell'interprete , ma questo spazio è facoltativo.
  2. Uno spazio che separa il percorso dell'interprete e il suo argomento facoltativo.

Si noti che non ho usato il plurale quando si parla di un argomento opzionale, né la sintassi sopra utilizzata [optional-arg ...], poiché è possibile fornire al massimo un singolo argomento .

Per quanto riguarda gli script di shell, è possibile utilizzare il setcomando integrato all'inizio dello script che consentirà di impostare i parametri degli interpreti, fornendo lo stesso risultato come se si utilizzassero argomenti da riga di comando.

Nel tuo caso:

set -o posix

Da un prompt di Bash, controlla l'output di help setper ottenere tutte le opzioni disponibili.


1
Puoi avere più di due spazi, sono considerati parte dell'argomento facoltativo.
Stephen Kitt,

@StephenKitt: In effetti, lo spazio bianco qui deve essere considerato più come una categoria rispetto al carattere spaziale reale. Suppongo che anche altri spazi bianchi come le schede dovrebbero essere ampiamente accettati.
WhiteWinterWolf il

3

Su Linux, lo shebang non è molto flessibile; in base a risposte multiple ( la risposta di Stephen Kitt e di Jörg W Mittag ), non esiste un modo designato per passare più argomenti in una riga shebang.

Non sono sicuro che sarà utile a nessuno, ma ho scritto un breve script per implementare la funzione mancante. Vedi https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa .

È anche possibile scrivere soluzioni alternative incorporate. Sotto, vi presento quattro soluzioni alternative agnostiche applicate allo stesso script di test e il risultato viene stampato. Suppongo che lo script sia eseguibile e sia presente /tmp/shebang.


Avvolgendo la tua sceneggiatura in una eredità bash all'interno della sostituzione del processo

Per quanto ne so, questo è il modo più affidabile di agnostico linguaggio per farlo. Permette il passaggio di argomenti e conserva lo stdin. Lo svantaggio è che l'interprete non conosce la posizione (reale) del file che legge.

#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv
try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"

Chiamata di echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'stampe:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /dev/fd/62
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: False
PYTHON_SCRIPT_END

Si noti che la sostituzione del processo produce un file speciale. Questo potrebbe non essere adatto a tutti gli eseguibili. Ad esempio, si #!/usr/bin/lesslamenta:/dev/fd/63 is not a regular file (use -f to see it)

Non so se è possibile avere ereditarietà nella sostituzione del processo in dash.


Avvolgendo la tua sceneggiatura in una semplice eredità

Più breve e più semplice, ma non sarai in grado di accedere stdindal tuo script e richiede che l'interprete sia in grado di leggere ed eseguire uno script da stdin.

#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER

Chiamata di echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'stampe:

PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0]   :: -
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END

Usa la system()chiamata awk ma senza argomenti

Passa correttamente il nome del file eseguito, ma lo script non riceverà gli argomenti forniti. Nota che awk è l'unica lingua che conosco il cui interprete è installato su Linux per impostazione predefinita e legge le istruzioni dalla riga di comando per impostazione predefinita.

#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

Chiamata di echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'stampe:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: []
__debug__ :: False
PYTHON_SCRIPT_END

Usa la system()chiamata awk 4.1+ , a condizione che i tuoi argomenti non contengano spazi

Bello, ma solo se sei sicuro che il tuo script non verrà chiamato con argomenti contenenti spazi. Come puoi vedere, i tuoi argomenti contenenti spazi verrebbero divisi, a meno che gli spazi non siano salvati.

#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

Chiamata di echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'stampe:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END

Per le versioni awk precedenti alla 4.1, dovrai usare la concatenazione di stringhe all'interno di un ciclo for, vedi esempio funzione https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .


1
Cita il terminatore del documento qui per inibire $variableo `command`sostituire:exec python3 -O <(cat <<'EOWRAPPER'
John McGehee

2

Un trucco da usare LD_LIBRARY_PATHcon Python sulla linea #!(shebang) che non dipende da nient'altro che dalla shell e funziona a meraviglia:

#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''

__doc__ = 'A great module docstring'

Come spiegato altrove in questa pagina alcune shell come shpossono prendere uno script sul loro input standard.

Lo script che forniamo shtenta di eseguire il comando ''''che è semplificato ''(la stringa vuota) da she, naturalmente, non riesce a eseguirlo in quanto non ci sono ''comandi, quindi normalmente viene emesso line 2: command not foundsul descrittore di errore standard ma reindirizziamo questo messaggio usando 2>/dev/nullal buco nero più vicino perché sarebbe disordinato e confuso per l'utente per lasciarlo shvisualizzare.

Passiamo quindi al comando che ci interessa: execche sostituisce l'attuale processo di shell con quanto segue, nel nostro caso: /usr/bin/env pythoncon i parametri adeguati:

  • "$0" per far sapere a Python quale script dovrebbe aprire e interpretare, e anche impostare sys.argv[0]
  • "$@"per impostare python's sys.argv[1:]sugli argomenti passati sulla riga di comando dello script.

E chiediamo anche envdi impostare la LD_LIBRARY_PATHvariabile d'ambiente, che è l'unico punto dell'hacking.

Il comando shell termina al commento a partire da in #modo che la shell ignori le virgolette triple finali '''.

shviene quindi sostituito da una nuova istanza shinny dell'interprete python che apre e legge lo script sorgente python dato come primo argomento (il "$0").

Python apre il file e salta sulla prima riga del sorgente grazie -xall'argomento. Nota: funziona anche senza -xperché per Python uno shebang è solo un commento .

Python quindi interpreta la seconda riga come docstring per il file del modulo corrente, quindi se hai bisogno di una docstring del modulo valida, imposta la __doc__prima cosa nel tuo programma python come nell'esempio sopra.



Dato che una stringa vuota è ... um ... vuota, dovresti essere in grado di rilasciare il comando non trovato business scimmia: ''''exec ...dovrebbe fare il lavoro. Nota che non c'è spazio prima di exec o lo farà cercare il comando vuoto. Volete unire il vuoto sul primo argomento, così $0è exec.
Caleb,

1

Ho trovato una soluzione piuttosto stupida quando cercavo un eseguibile che escludesse uno script come un singolo argomento:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
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.