Saya ingin membaca skrip saat ini sehingga saya dapat mencetak informasi bantuan dan versi dari bagian komentar di bagian atas.
Saya sedang memikirkan sesuatu seperti ini:
grep '^#h ' -- "$0" | sed -e 's/#h //'
Tapi kemudian saya bertanya-tanya apa yang akan terjadi jika skrip tersebut berada di direktori yang ada di PATH dan dipanggil tanpa secara eksplisit menentukan direktori.
Saya mencari penjelasan tentang variabel-variabel khusus dan menemukan deskripsi berikut $0
:
nama shell atau program saat ini
nama file dari skrip saat ini
nama skrip itu sendiri
perintah saat dijalankan
Tak satu pun dari ini memperjelas apakah nilai $0
akan menyertakan direktori jika skrip dipanggil tanpa itu. Yang terakhir sebenarnya menyiratkan kepada saya bahwa itu tidak akan terjadi.
Menguji Sistem Saya (Bash 4.1)
Saya membuat file yang dapat dieksekusi di / usr / local / bin bernama scriptname dengan satu baris echo $0
dan memintanya dari lokasi yang berbeda.
Ini hasil saya:
> cd /usr/local/bin/test
> ../scriptname
../scriptname
> cd /usr/local/bin
> ./scriptname
./scriptname
> cd /usr/local
> bin/scriptname
bin/scriptname
> cd /tmp
> /usr/local/bin/scriptname
/usr/local/bin/scriptname
> scriptname
/usr/local/bin/scriptname
Dalam pengujian ini, nilai $0
selalu persis bagaimana skrip dipanggil, kecuali jika dipanggil tanpa komponen path. Dalam hal ini, nilai $0
adalah jalur absolut . Sehingga sepertinya aman untuk meneruskan ke perintah lain.
Tapi kemudian saya menemukan komentar tentang Stack Overflow yang membingungkan saya. Jawabannya menyarankan menggunakan $(dirname $0)
untuk mendapatkan direktori skrip saat ini. Komentar (7 kali dipilih) mengatakan "itu tidak akan berfungsi jika skrip berada di jalur Anda".
Pertanyaan
- Apakah komentar itu benar?
- Apakah perilaku berbeda pada sistem lain?
- Apakah ada situasi di mana
$0
tidak termasuk direktori?
sumber
$0
ada sesuatu selain naskah, yang memang menjawab judul pertanyaan. Namun, saya juga tertarik pada situasi di mana$0
skrip itu sendiri, tetapi tidak termasuk direktori. Secara khusus, saya mencoba memahami komentar yang dibuat pada jawaban SO.Jawaban:
Dalam kasus yang paling umum,
$0
akan berisi path, absolut atau relatif terhadap skrip, jadi(dengan asumsi ada
readlink
perintah dan itu mendukung-e
) umumnya adalah cara yang cukup baik untuk mendapatkan path absolut kanonik ke skrip.$0
ditugaskan dari argumen yang menentukan skrip yang diteruskan ke penerjemah.Misalnya, di:
$0
dapatkanthe/script
.Ketika Anda menjalankan:
Shell Anda akan melakukan:
Jika skrip berisi
#! /bin/sh -
she-bang misalnya, sistem akan mengubahnya menjadi:(jika tidak mengandung she-bang, atau lebih umum lagi jika sistem mengembalikan kesalahan ENOEXEC, maka shell Anda yang akan melakukan hal yang sama)
Ada pengecualian untuk skrip setuid / setgid pada beberapa sistem, di mana sistem akan membuka skrip pada beberapa
fd
x
dan menjalankannya sebagai gantinya:untuk menghindari kondisi balapan (dalam hal ini
$0
akan berisi/dev/fd/x
).Sekarang, Anda mungkin berpendapat bahwa itu
/dev/fd/x
adalah jalan menuju skrip itu. Namun perhatikan bahwa jika Anda membaca dari$0
, Anda akan merusak skrip saat Anda mengkonsumsi input.Sekarang, ada perbedaan jika nama perintah skrip yang dipanggil tidak mengandung garis miring. Di:
Shell Anda akan mencari
the-script
di$PATH
.$PATH
mungkin berisi jalur absolut atau relatif (termasuk string kosong) ke beberapa direktori. Misalnya, jika$PATH
berisi/bin:/usr/bin:
danthe-script
ditemukan di direktori saat ini, shell akan melakukan:yang akan menjadi:
Atau jika ditemukan di
/usr/bin
:Dalam semua kasus di atas kecuali kasus sudut setuid,
$0
akan berisi path (absolut atau relatif) ke skrip.Sekarang, skrip juga bisa disebut sebagai:
Ketika
the-script
seperti di atas tidak mengandung karakter garis miring, perilaku sedikit berbeda dari shell ke shell.ksh
Implementasi AT&T lama benar-benar mencari skrip tanpa syarat di$PATH
(yang sebenarnya adalah bug dan lubang keamanan untuk skrip setuid), jadi$0
sebenarnya tidak mengandung jalur ke skrip kecuali$PATH
pencarian benar-benar ditemukanthe-script
di direktori saat ini.AT&T yang lebih baru
ksh
akan mencoba dan menafsirkanthe-script
dalam direktori saat ini jika itu dapat dibaca. Jika tidak akan mencari yang dapat dibaca dan dieksekusithe-script
di$PATH
.Karena
bash
, ia memeriksa apakahthe-script
ada di direktori saat ini (dan bukan merupakan symlink yang rusak) dan jika tidak, cari yang dapat dibaca (belum tentu dapat dieksekusi)the-script
di$PATH
.zsh
dalamsh
emulasi akan melakukan sepertibash
kecuali bahwa jikathe-script
symlink rusak di direktori saat ini, itu tidak akan mencarithe-script
in$PATH
dan malah akan melaporkan kesalahan.Semua kerang mirip Bourne lainnya tidak terlihat
the-script
masuk$PATH
.Untuk semua kerang itu, jika Anda menemukan yang
$0
tidak mengandung a/
dan tidak dapat dibaca, maka itu mungkin telah dicari$PATH
. Kemudian, karena file-file di$PATH
dalamnya kemungkinan dapat dieksekusi, mungkin ini perkiraan yang aman untuk digunakancommand -v -- "$0"
untuk menemukan jalurnya (meskipun itu tidak akan berhasil jika$0
kebetulan juga merupakan nama dari shell builtin atau kata kunci (dalam kebanyakan shell)).Jadi, jika Anda benar-benar ingin menutupi kasus itu, Anda bisa menulisnya:
(yang
""
ditambahkan$PATH
adalah untuk melestarikan elemen kosong yang tertinggal dengan cangkang yang$IFS
bertindak sebagai pembatas bukannya pemisah ).Sekarang, ada lebih banyak cara esoteris untuk menjalankan skrip. Orang bisa melakukan:
Atau:
Dalam hal ini,
$0
akan menjadi argumen pertama (argv[0]
) yang diterima penerjemah (di atasthe-shell
, tetapi bisa berupa apa saja meskipun umumnya baik nama dasar atau satu jalur ke penerjemah itu).Mendeteksi bahwa Anda berada dalam situasi itu berdasarkan nilai
$0
tidak dapat diandalkan. Anda bisa melihat outputps -o args= -p "$$"
untuk mendapatkan petunjuk. Dalam kasus pipa, tidak ada cara nyata Anda bisa kembali ke jalur ke skrip.Orang juga bisa melakukan:
Kemudian, kecuali dalam
zsh
(dan beberapa implementasi lama dari Bourne shell),$0
akanblah
. Sekali lagi, sulit untuk mendapatkan jalan naskah di shell tersebut.Atau:
dll.
Untuk memastikan Anda memiliki hak
$progname
, Anda dapat mencari string tertentu di dalamnya seperti:Tetapi sekali lagi saya tidak berpikir itu sepadan dengan usaha.
sumber
"-"
dalam contoh di atas. Dalam pengalaman saya,exec("the-script", ["the-script", "its", "args"])
menjadiexec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"])
, tentu saja dengan opsi interpreter.#! /bin/sh -
adalah "selalu digunakancmd -- something
jika Anda tidak dapat menjamin bahwasomething
tidak akan mulai dengan-
" pepatah praktik yang baik di sini diterapkan untuk/bin/sh
(di mana-
penanda akhir opsi lebih portabel daripada--
) dengansomething
menjadi path / nama dari naskah. Jika Anda tidak menggunakannya untuk skrip setuid (pada sistem yang mendukungnya tetapi tidak dengan metode / dev / fd / x yang disebutkan dalam jawaban), maka seseorang dapat memperoleh shell root dengan membuat symlink ke skrip yang dipanggil-i
atau-s
untuk contoh./bin/sh
harus menonaktifkan pemrosesan opsinya sendiri jika dapat mendeteksi bahwa skrip itu menjalankan setuid. Saya tidak melihat bagaimana menggunakan / dev / fd / x dengan sendirinya memperbaiki itu. Anda masih membutuhkan tanda hubung tunggal / ganda, saya pikir./dev/fd/x
dimulai dengan/
, bukan-
. Tujuan utamanya adalah untuk menghapus kondisi balapan antara keduanyaexecve()
, meskipun (di antaranyaexecve("the-script")
yang meningkatkan hak istimewa dan berikutnya diexecve("interpreter", "thescript")
manainterpreter
membuka skrip nanti (yang mungkin telah diganti dengan symlink ke sesuatu yang lain sementara itu). Sistem yang menerapkan skrip suid dengan benar melakukanexecve("interpreter", "/dev/fd/n")
gantinya di mana n telah dibuka sebagai bagian dari execve pertama ().Berikut adalah dua situasi di mana direktori tidak akan dimasukkan:
Dalam kedua kasus, direktori saat ini harus menjadi direktori tempat scriptname berada.
Dalam kasus pertama, nilai
$0
masih bisa diteruskan kegrep
karena mengasumsikan argumen FILE relatif terhadap direktori saat ini.Dalam kasus kedua, jika informasi bantuan dan versi hanya dicetak sebagai respons terhadap opsi baris perintah tertentu, itu seharusnya tidak menjadi masalah. Saya tidak yakin mengapa ada orang yang meminta skrip seperti itu untuk mencetak bantuan atau informasi versi.
Peringatan
Jika skrip mengubah direktori saat ini, Anda tidak ingin menggunakan jalur relatif.
Jika skrip bersumber, nilai
$0
biasanya akan menjadi skrip penelepon daripada skrip bersumber.sumber
Argumen nol arbitrer dapat ditentukan saat menggunakan
-c
opsi untuk sebagian besar (semua?) Shell. Misalnya:Dari
man bash
(dipilih semata-mata karena ini memiliki deskripsi yang lebih baik daripada sayaman sh
- penggunaannya tetap sama):sumber
argv0
argumen commandline nonoperand pertamanya.CATATAN: Lainnya sudah menjelaskan mekanisme
$0
jadi saya akan melewatkan semua itu.Saya biasanya memihak seluruh masalah ini dan cukup menggunakan perintah
readlink -f $0
. Ini akan selalu memberi Anda kembali jalan penuh dari apa pun yang Anda berikan sebagai argumen.Contohnya
Katakan saya di sini untuk memulai dengan:
Buat file direktori +:
Sekarang mulai pamer
readlink
:Tipu daya tambahan
Sekarang dengan hasil yang konsisten dikembalikan ketika kita menginterogasi
$0
melaluireadlink
kita dapat menggunakan hanyadirname $(readlink -f $0)
untuk mendapatkan path absolut ke skrip -atau-basename $(readlink -f $0)
untuk mendapatkan nama sebenarnya dari skrip.sumber
man
Halaman saya mengatakan:Tampaknya ini diterjemahkan ke
argv[0]
dari shell saat ini - atau argumen commandline nonoperand pertama yang diumpankan shell diumpankan saat dipanggil. Saya sebelumnya menyatakan bahwash ./somescript
akan path variabelnya ke tetapi ini tidak benar karena ini adalah proses baru sendiri dan dipanggil dengan yang baru .$0 $ENV
sh
shell
$ENV
Dengan cara ini
sh ./somescript.sh
berbeda dari. ./somescript.sh
yang berjalan di lingkungan saat ini dan$0
sudah diatur.Anda dapat memeriksa ini dengan membandingkan
$0
ke/proc/$$/status
.Terima kasih atas koreksinya, @toxalot. Saya belajar sesuatu.
sumber
. ./myscript.sh
atausource ./myscript.sh
), maka$0
shell. Tetapi jika itu diteruskan sebagai argumen ke shell (sh ./myscript.sh
), maka$0
adalah jalan menuju skrip. Tentu saja,sh
pada sistem saya adalah Bash. Jadi saya tidak tahu apakah itu membuat perbedaan atau tidak.exec
dan sebaliknya sumber, tetapi dengan itu, seharusnyaexec
../somescript
mengeksekusi dengan bangline#!/bin/sh
, itu akan setara dengan berjalan/bin/sh ./somescript
. Kalau tidak, mereka tidak membuat perbedaan pada shell.Meskipun
$0
berisi nama skrip yang mungkin berisi lintasan awalan berdasarkan pada cara skrip dipanggil, saya selalu digunakan${0##*/}
untuk mencetak nama skrip dalam output bantuan yang menghilangkan lintasan utama apa pun dari$0
.Diambil dari panduan Advanced Bash Scripting - Bagian 10.2 penggantian parameter
Jadi bagian terpanjang dari
$0
kecocokan itu*/
akan menjadi awalan seluruh jalur, hanya mengembalikan nama skrip.sumber
Untuk hal serupa saya gunakan:
rPath
selalu memiliki nilai yang sama.sumber