Beragam argumen di shebang

32

Saya bertanya-tanya apakah ada cara umum untuk melewatkan beberapa opsi ke file yang dapat dieksekusi melalui baris shebang ( #!).

Saya menggunakan NixOS, dan bagian pertama dari shebang dalam skrip apa pun yang saya tulis biasanya /usr/bin/env. Masalah yang saya temui kemudian adalah bahwa semua yang muncul setelah ditafsirkan sebagai satu file atau direktori oleh sistem.

Misalkan, misalnya, saya ingin menulis skrip yang akan dieksekusi bashdalam mode posix. Cara naif menulis shebang adalah:

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

tetapi mencoba menjalankan skrip yang dihasilkan menghasilkan kesalahan berikut:

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

Saya mengetahui posting ini , tetapi saya bertanya-tanya apakah ada solusi yang lebih umum dan lebih bersih.


EDIT : Saya tahu bahwa untuk skrip Guile , ada cara untuk mencapai apa yang saya inginkan, didokumentasikan dalam Bagian 4.3.4 manual:

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

Kuncinya, di sini, adalah bahwa baris kedua (dimulai dengan exec) ditafsirkan sebagai kode oleh shtetapi, berada di #!... !#blok, sebagai komentar, dan dengan demikian diabaikan, oleh juru bahasa Guile.

Apakah tidak mungkin untuk menggeneralisasi metode ini kepada penerjemah mana pun?


EDIT Kedua : Setelah bermain-main sedikit, tampaknya, untuk penerjemah yang dapat membaca masukan dari mereka stdin, metode berikut akan bekerja:

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

Ini mungkin tidak optimal, meskipun, karena shprosesnya hidup sampai penerjemah menyelesaikan tugasnya. Umpan balik atau saran apa pun akan dihargai.

Rastapopoulos
sumber

Jawaban:

27

Tidak ada solusi umum, setidaknya tidak jika Anda perlu mendukung Linux, karena kernel Linux memperlakukan semuanya mengikuti "kata" pertama di baris shebang sebagai argumen tunggal .

Saya tidak yakin apa kendala NixOS, tetapi biasanya saya hanya akan menuliskan shebang Anda

#!/bin/bash --posix

atau, jika memungkinkan, mengatur opsi dalam skrip :

set -o posix

Atau, Anda dapat meminta script memulai ulang sendiri dengan permintaan shell yang sesuai:

#!/bin/sh -

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

shift

# Processing continues

Pendekatan ini dapat digeneralisasi ke bahasa lain, selama Anda menemukan cara untuk beberapa baris pertama (yang ditafsirkan oleh shell) untuk diabaikan oleh bahasa target.

GNU coreutils' envmenyediakan solusi sejak versi 8.30, lihat unode ‘s jawaban untuk rincian. (Ini tersedia dalam Debian 10 dan yang lebih baru, RHEL 8 dan yang lebih baru, Ubuntu 19.04 dan yang lebih baru, dll.)

Stephen Kitt
sumber
18

Meskipun tidak sepenuhnya portabel, dimulai dengan coreutils 8.30 dan menurut dokumentasinya Anda dapat menggunakan:

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

Jadi diberikan:

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

kamu akan mendapatkan:

% ./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'

dan jika Anda penasaran showargsadalah:

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

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done
unode
sumber
Ini sangat baik untuk diketahui sebagai referensi di masa depan.
John McGehee
Opsi yang disalin dari FreeBSD envmana -Sditambahkan pada tahun 2005. See lists.gnu.org/r/coreutils/2018-04/msg00011.html
Stéphane Chazelas
Bekerja memperlakukan pada Fedora 29
Eric
@unode beberapa peningkatan showargs: pastebin.com/q9m6xr8H dan pastebin.com/gS8AQ5WA (satu-liner)
Eric
FYI: mulai dari coreutils 8.31, envtermasuk miliknya sendiri showargs: opsi -v misalnya#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy
9

Standar POSIX sangat singkat dalam menggambarkan #!:

Dari bagian pemikiran dokumentasi exec()keluarga antarmuka sistem :

Cara lain yang beberapa implementasi historis menangani skrip shell adalah dengan mengenali dua byte pertama file sebagai string karakter #!dan menggunakan sisa baris pertama file sebagai nama penerjemah perintah untuk dieksekusi.

Dari bagian Pengantar Shell :

Shell membaca inputnya dari file (lihat sh), dari -copsi atau dari system()dan popen()fungsi yang didefinisikan dalam volume Antarmuka Sistem POSIX.1-2008. Jika baris pertama file perintah shell dimulai dengan karakter #!, hasilnya tidak ditentukan .

Ini pada dasarnya berarti bahwa implementasi apa pun (Unix yang Anda gunakan) bebas untuk melakukan spesifikasi parsing dari garis shebang seperti yang diinginkan.

Beberapa Unices, seperti macOS (tidak dapat menguji ATM), akan membagi argumen yang diberikan kepada penerjemah pada baris shebang menjadi argumen yang terpisah, sementara Linux dan sebagian besar Unices lainnya akan memberikan argumen sebagai opsi tunggal untuk penerjemah.

Karena itu tidak bijaksana untuk mengandalkan garis shebang untuk dapat mengambil lebih dari satu argumen.

Lihat juga bagian Portabilitas artikel Shebang di Wikipedia .


Salah satu solusi mudah, yang dapat digeneralisasikan untuk utilitas atau bahasa apa pun, adalah membuat skrip wrapper yang mengeksekusi skrip asli dengan argumen baris perintah yang sesuai:

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

Saya tidak berpikir saya akan secara pribadi mencoba membuatnya kembali mengeksekusi sendiri karena rasanya agak rapuh.

Kusalananda
sumber
7

Shebang dijelaskan dalam execve(2) halaman manual sebagai berikut:

#! interpreter [optional-arg]

Dua spasi diterima dalam sintaks ini:

  1. Satu ruang sebelum jalur juru bahasa , tetapi ruang ini opsional.
  2. Satu ruang yang memisahkan jalur juru bahasa dan argumen opsionalnya.

Perhatikan bahwa saya tidak menggunakan jamak ketika berbicara tentang argumen opsional, sintaksis di atas juga tidak digunakan [optional-arg ...], karena Anda dapat memberikan paling banyak satu argumen tunggal .

Sejauh menyangkut skrip shell, Anda dapat menggunakan setperintah bawaan di dekat bagian awal skrip Anda yang akan memungkinkan untuk mengatur parameter penerjemah, memberikan hasil yang sama seperti jika Anda menggunakan argumen baris perintah.

Dalam kasus Anda:

set -o posix

Dari prompt Bash, periksa output help setuntuk mendapatkan semua opsi yang tersedia.

WhiteWinterWolf
sumber
1
Anda diperbolehkan memiliki lebih dari dua ruang, mereka hanya dianggap sebagai bagian dari argumen opsional.
Stephen Kitt
@StephenKitt: Memang, ruang putih di sini harus dianggap lebih sebagai kategori daripada ruang char sebenarnya. Saya kira ruang putih lain seperti tab juga harus diterima secara luas.
WhiteWinterWolf
3

Di Linux, shebang tidak terlalu fleksibel; menurut beberapa jawaban (jawaban Stephen Kitt dan Jörg W Mittag ), tidak ada cara yang ditentukan untuk melewati beberapa argumen dalam garis shebang.

Saya tidak yakin apakah ini akan berguna bagi siapa pun, tetapi saya telah menulis skrip pendek untuk mengimplementasikan fitur yang kurang. Lihat https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa .

Dimungkinkan juga untuk menulis solusi yang disematkan. Di bawah, saya sajikan empat solusi bahasa-agnostik yang diterapkan pada skrip uji yang sama dan hasil masing-masing dicetak. Saya kira bahwa script adalah dieksekusi dan di /tmp/shebang.


Membungkus skrip Anda di bash heredoc di dalam subtitusi proses

Sejauh yang saya tahu, ini adalah cara agnostik bahasa yang paling dapat diandalkan untuk melakukannya. Ini memungkinkan lewat argumen dan mempertahankan stdin. Kekurangannya adalah bahwa penerjemah tidak mengetahui lokasi (sebenarnya) dari file yang dibacanya.

#!/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
) "$@"

Memanggil echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'cetakan:

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

Perhatikan bahwa substitusi proses menghasilkan file khusus. Ini mungkin tidak sesuai dengan semua yang dapat dieksekusi. Misalnya, #!/usr/bin/lessmengeluh:/dev/fd/63 is not a regular file (use -f to see it)

Saya tidak tahu apakah mungkin untuk memiliki heredoc di dalam proses substitusi di dash.


Membungkus naskah Anda dalam heredoc sederhana

Lebih pendek dan sederhana, tetapi Anda tidak akan dapat mengakses stdindari skrip Anda dan itu membutuhkan penerjemah untuk dapat membaca dan menjalankan skrip dari 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

Memanggil echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'cetakan:

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

Gunakan system()panggilan awk tetapi tanpa argumen

Benar melewati nama file yang dieksekusi, tetapi skrip Anda tidak akan menerima argumen yang Anda berikan. Perhatikan bahwa awk adalah satu-satunya bahasa yang saya tahu yang interpreter keduanya diinstal di linux secara default dan membaca instruksinya dari baris perintah secara default.

#!/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")

Memanggil echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'cetakan:

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

Gunakan system()panggilan 4.1+ awk , asalkan argumen Anda tidak mengandung spasi

Bagus, tetapi hanya jika Anda yakin skrip Anda tidak akan dipanggil dengan argumen yang berisi spasi. Seperti yang dapat Anda lihat, argumen Anda yang berisi spasi akan dipisah, kecuali spasi tersebut lolos.

#!/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")

Memanggil echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'cetakan:

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

Untuk versi awk di bawah 4.1, Anda harus menggunakan penggabungan string di dalam for for, lihat contoh fungsi https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .

loxaxs
sumber
1
Kutip terminator dokumen di sini untuk menghambat $variableatau `command`mengganti:exec python3 -O <(cat <<'EOWRAPPER'
John McGehee
2

Trik untuk digunakan LD_LIBRARY_PATHdengan python pada baris #!(shebang) yang tidak bergantung pada apa pun selain shell dan menjalankan suguhan:

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

__doc__ = 'A great module docstring'

Seperti yang dijelaskan di bagian lain halaman ini, beberapa shell seperti shdapat menggunakan script pada input standar mereka.

Skrip yang kami berikan shmencoba menjalankan perintah ''''yang disederhanakan ''(string kosong) oleh shdan tentu saja gagal menjalankannya karena tidak ada ''perintah, sehingga biasanya ditampilkan line 2: command not foundpada deskriptor kesalahan standar tetapi kami mengarahkan pesan ini menggunakan 2>/dev/nullke lubang hitam terdekat karena itu akan berantakan dan membingungkan bagi pengguna untuk membiarkannya shditampilkan.

Kami kemudian melanjutkan ke perintah yang menarik bagi kami: execyang menggantikan proses shell saat ini dengan yang berikut, dalam kasus kami: /usr/bin/env pythondengan parameter yang memadai:

  • "$0" untuk memberi tahu python skrip mana yang harus dibuka dan ditafsirkan, dan juga disetel sys.argv[0]
  • "$@"untuk mengatur python sys.argv[1:]ke argumen yang diteruskan pada baris perintah skrip.

Dan kami juga meminta envuntuk mengatur LD_LIBRARY_PATHvariabel lingkungan, yang merupakan satu-satunya titik peretasan.

Perintah shell berakhir pada komentar yang dimulai dengan #sehingga shell mengabaikan tanda kutip tiga trailing '''.

shkemudian digantikan oleh contoh baru dari interpreter python yang membuka dan membaca skrip sumber python yang diberikan sebagai argumen pertama (the "$0").

Python membuka file dan melompati baris pertama sumber berkat -xargumennya. Catatan: ini juga berfungsi tanpa -xkarena untuk Python shebang hanyalah sebuah komentar .

Python kemudian menginterpretasikan baris ke-2 sebagai docstring untuk file modul saat ini, jadi jika Anda memerlukan docstring modul yang valid, cukup atur __doc__hal pertama dalam program python Anda seperti pada contoh di atas.

Eric
sumber
Mengingat bahwa string kosong adalah ... um ... kosong, Anda harus dapat menjatuhkan perintah Anda tidak menemukan bisnis monyet: ''''exec ...harus menyelesaikan pekerjaan. Catat tidak ada spasi sebelum exec atau itu akan membuatnya mencari perintah kosong. Anda ingin menyambungkan yang kosong ke arg pertama begitu $0juga exec.
Caleb
1

Saya menemukan solusi yang agak bodoh ketika mencari executable yang mengecualikan script sebagai argumen tunggal:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
sampai
sumber