$ 0 her zaman komut dosyasının yolunu içerecek mi?


11

Üstteki yorumlar bölümünden yardım ve sürüm bilgilerini yazdırabilmek için geçerli komut dosyasını grep etmek istiyorum.

Ben böyle bir şey düşünüyordum:

grep '^#h ' -- "$0" | sed -e 's/#h //'

Ama sonra komut dosyası PATH olan bir dizinde bulunmuşsa ve dizini açıkça belirtmeden çağrılırsa ne olacağını merak ettim.

Özel değişkenlerin bir açıklamasını aradım ve aşağıdaki açıklamaları buldum $0:

  • geçerli kabuğun veya programın adı

  • geçerli komut dosyasının dosya adı

  • komut dosyasının kendisi

  • çalıştırıldığı gibi komut

Bunların hiçbiri $0, komut dosyası onsuz çağrıldıysa dizinin değerinin dizini içerip içermeyeceğini netleştirmez . Sonuncusu aslında bana bunun olmayacağını ima ediyor.

Sistemimde Test Etme (Bash 4.1)

/ Usr / local / bin içinde bir satır ile scriptname adlı çalıştırılabilir bir dosya oluşturdum echo $0ve farklı konumlardan çağırdım .

Bunlar benim sonuçlarım:

> 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

Bu testlerde, herhangi bir yol bileşeni olmadan çağrılmış olması durumu dışında$0 , komut dosyasının değeri her zaman tam olarak nasıl çağrılır . Bu durumda, değeri olan mutlak yol . Yani başka bir komuta geçmenin güvenli olacağı anlaşılıyor.$0

Ama sonra Stack Overflow hakkında kafamı karıştıran bir yorumla karşılaştım . Yanıt $(dirname $0), geçerli komut dosyasının dizinini almak için kullanılmasını önerir . Yorum (7 kez değerlendirildi) "komut dosyası yolunuzdaysa çalışmaz" der.

Sorular

  • Bu yorum doğru mu?
  • Davranış diğer sistemlerde farklı mıdır?
  • $0Dizini içermeyecek durumlar var mı?

İnsanlar $0, senaryodan başka bir şeyin olduğu ve soru başlığına cevap veren durumları cevapladılar. Ancak, $0betiğin kendisinin olduğu durumlarla da ilgileniyorum , ancak dizini içermiyor. Özellikle, SO cevabı üzerine yapılan yorumu anlamaya çalışıyorum.
toxalot

Yanıtlar:


17

En yaygın durumlarda, $0komut dosyasına mutlak veya göreli olarak bir yol içerir, bu nedenle

script_path=$(readlink -e -- "$0")

(bir readlinkkomut olduğu ve desteklediği varsayılarak -e) genellikle kodun kurallı mutlak yolunu elde etmek için yeterince iyi bir yoldur.

$0 yorumlayıcıya iletildiği şekliyle komut dosyasını belirten bağımsız değişkenten atanır.

Örneğin, içinde:

the-shell -shell-options the/script its args

$0alır the/script.

Koştuğunuzda:

the/script its args

Kabuğunuz şunları yapacak:

exec("the/script", ["the/script", "its", "args"])

Komut dosyası, #! /bin/sh -örneğin bir she-bang içeriyorsa , sistem bunu şuna dönüştürecektir:

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "the/script", "its", "args"])

(bir she-bang içermiyorsa veya daha genel olarak sistem bir ENOEXEC hatası döndürürse, aynı şeyi yapacak olan kabuğunuz)

Bazı sistemlerde setuid / setgid komut dosyaları için bir istisna vardır; burada sistem komut dosyasını bazılarında açar fd xve bunun yerine çalışır:

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "/dev/fd/x", "its", "args"])

yarış koşullarından kaçınmak için (bu durumda $0içerecektir /dev/fd/x).

Şimdi, bunu iddia edebilir /dev/fd/x olduğunu o komut dosyası yolu. Ancak, sizden okursanız $0, girdiyi tüketirken komut dosyasını kıracağınızı unutmayın.

Şimdi, çağrıldığı gibi komut dosyası komut adı eğik çizgi içermiyorsa, bir fark var. İçinde:

the-script its args

Sizin kabuk yukarı bakacak the-scriptiçinde $PATH. $PATHbazı dizinlere mutlak veya göreli (boş dize dahil) yollar içerebilir. Örneğin , geçerli dizinde $PATHiçerir /bin:/usr/bin:ve the-scriptbulunursa, kabuk aşağıdakileri yapar:

exec("the-script", ["the-script", "its", "args"])

hangi olacak:

exec("/bin/sh", ["/bin/sh" or "the-script", "-", "the-script", "its", "args"]

Veya şu konumda bulunursa /usr/bin:

exec("/usr/bin/the-script", ["the-script", "its", "args"])
exec("/bin/sh", ["/bin/sh" or "the-script" or "/usr/bin/the-script",
     "-", "/usr/bin/the-script", "its", "args")

Setuid köşe durumu dışında yukarıdaki tüm durumlarda $0, komut dosyasına giden bir yol (mutlak veya göreli) içerecektir.

Şimdi, bir komut dosyası da şu şekilde çağrılabilir:

the-interpreter the-script its args

Tüm the-scriptyukarıda çizgi karakterleri içermeyen, davranış kabuğundan kabuğa biraz değişir.

Eski AT & T kshuygulamaları aslında koşulsuz in-senaryoyu aradılar $PATHbu yüzden, (aslında bir böcek ve setuid komut için bir güvenlik deliği olduğu) $0aslında yoktu değil sürece komut dosyası için bir yolunu içermelidir $PATHarama aslında bulmak oldu the-scriptgeçerli dizinde.

Daha yeni AT&T ksh, the-scriptokunabilirse geçerli dizinde yorumlamaya çalışır . Değilse bir okunabilir için arama ediyorum ve yürütülebilir the-script in $PATH.

İçin basheğer kontrol, the-scriptgeçerli dizinde (ve kırık sembolik bağ değildir) ve değilse, arama (ille çalıştırılabilir) bir okunabilir için the-scriptde $PATH.

zshiçinde shöykünmesi gibi yapacağını basheğer dışında the-scriptgeçerli dizinde bir kırık sembolik bağdır, bir aramak olmaz the-scriptiçinde $PATHve bunun yerine bir hata rapor verecek.

Diğer tüm Bourne benzeri kabuklar görünmüyor the-scriptkadar $PATH.

Zaten tüm bu mermiler için, $0bir içermeyen /ve okunabilir olmayan bulursanız, muhtemelen arandı $PATH. Sonra, içindeki dosyalar $PATHyürütülebilir olması muhtemeldir, muhtemelen command -v -- "$0"yolunu bulmak için kullanmak güvenli bir yaklaşımdır (ancak $0bir kabuk yerleşiminin veya anahtar kelimenin (çoğu kabukta) adı da olsa bu işe yaramaz ).

Yani bu davayı gerçekten kapsamak istiyorsanız, şunu yazabilirsiniz:

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}""; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

( ""ekte , ayırıcı yerine ayırıcı olarak işlev gören $PATHkabukları olan boş bir elemanı korumaktır ).$IFS

Şimdi, bir senaryoyu çağırmanın daha ezoterik yolları var. Biri yapabilirdi:

the-shell < the-script

Veya:

cat the-script | the-shell

Bu durumda, yorumlayıcının aldığı $0ilk argüman ( argv[0]) olacaktır , ancak bu genellikle taban adı veya söz konusu tercümana giden bir yol olsa da herhangi bir şey olabilir.the-shell

Bu durumda değerine dayalı olduğunuzu tespit etmek $0güvenilir değildir. ps -o args= -p "$$"Bir ipucu almak için çıktısına bakabilirsiniz . Boru hattında, senaryoya giden bir yola geri dönmenin gerçek bir yolu yoktur.

Bir de yapabilirdi:

the-shell -c '. the-script' blah blih

Daha sonra, içinde hariç zsh(ve Bourne kabuğu bazı eski uygulama), $0olacaktır blah. Yine, bu kabuklardaki senaryonun yoluna ulaşmak zor.

Veya:

the-shell -c "$(cat the-script)" blah blih

vb.

Hakka sahip olduğunuzdan emin olmak için $progname, içinde belirli bir dize arayabilirsiniz:

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}:; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

[ -f "$progname" ] && grep -q 7YQLVVD3UIUDTA32LSE8U9UOHH < "$progname" ||
  progname=unknown

Ama yine de çabaya değeceğini sanmıyorum.


Stéphane, "-"yukarıdaki örneklerde kullanımınızı anlamıyorum . Deneyimlerime göre, elbette bir tercüman seçeneği olasılığı exec("the-script", ["the-script", "its", "args"])olur exec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"]).
jrw32982 Monica

@ jrw32982, #! /bin/sh -olduğu "her zaman kullanmak cmd -- somethingsize garanti edemez eğer somethingbaşlamaz -" Burada uygulanan iyi bir uygulama atasözü /bin/sh(burada -sonu seçeneği işaretleyici daha taşınabilir olduğu gibi --) ile somethingyolu / isim olma senaryo. Bunu setuid komut dosyaları için kullanmazsanız (bunları destekleyen ancak yanıtta belirtilen / dev / fd / x yöntemiyle kullanmayan sistemlerde), komut dosyanızda -iveya adlı bir simge bağlantısı oluşturarak bir kök kabuk alabilirsiniz -s. örneği.
Stéphane Chazelas

Teşekkürler Stéphane. Ben senin örnek shebang satırındaki tek tek tire kaçırmıştı. Tek bir kısa çizginin çift ​​kısa çizgi pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html ile eşdeğer olduğu belgelendiği yerde avlanmak zorunda kaldım .
jrw32982, Monica

Bir setuid komut dosyası için shebang satırındaki sondaki tek / çift tireyi unutmak çok kolay; bir şekilde sistem sizin için bununla ilgilenmelidir. Setuid komut dosyalarına tamamen izin verme veya /bin/shsetuid çalıştırdığını algılayabiliyorsa, bir şekilde kendi seçenek işlemesini devre dışı bırakmalıdır. / Dev / fd / x kullanmanın bunu nasıl düzelttiğini göremiyorum. Hala tek / çift tireye ihtiyacınız var, sanırım.
jrw32982, Monica

@ jrw32982, ile /dev/fd/xbaşlar /, değil -. Birincil amacı iki arasındaki yarış durumu kaldırmaktır execve()aradaki (gerçi s execve("the-script")ayrıcalıkları yükseltir hangi ve müteakip execve("interpreter", "thescript")nerede interpreter(daha sonra The Script açan bu arada başka bir şey) sembolik bağ olmuştur yerine geçer çok iyi olabilir. Uygulamak Sistemleri suid betikleri düzgün bir şekilde execve("interpreter", "/dev/fd/n")ilk execve () bir parçası olarak n açıldı ().
Stéphane Chazelas

6

İşte dizin olacak iki durumlardır değil eklenecektir:

> bash scriptname
scriptname

> bash <scriptname
bash

Her iki durumda da, geçerli dizinin komut dosyası adının bulunduğu dizin olması gerekir .

İlk durumda, FILE argümanının geçerli dizine göreli olduğu varsayılarak $0yine de değeri geçirilebilir grep.

İkinci durumda, yardım ve sürüm bilgileri yalnızca belirli bir komut satırı seçeneğine yanıt olarak yazdırılırsa, sorun olmamalıdır. Neden kimse yardım veya sürüm bilgilerini yazdırmak için bir komut dosyası bu şekilde çağırmak emin değilim.

Uyarılar

  • Komut dosyası geçerli dizini değiştirirse, göreli yollar kullanmak istemezsiniz.

  • Komut dosyası kaynaklanıyorsa, değeri $0kaynak kodundan ziyade genellikle arayan komut dosyası olur.


3

-cÇoğu (tümü?) Mermi seçeneği kullanılırken rastgele sıfır argümanı belirtilebilir . Örneğin:

sh -c 'echo $0' argv0

Kimden man bash(tamamen seçildiğim için bu benim seçimimden daha iyi bir açıklamaya sahip man sholduğu için seçildi )

-c

-C seçeneği varsa, komutlar, seçenek olmayan ilk command_string bağımsız değişkeninden okunur. Command_string öğesinden sonra argümanlar varsa, bunlar $ 0 ile başlayan konumsal parametrelere atanır.


Bence bu -c 'command' işlenenler ve argv0ilk ve nonoperand komut satırı argümanı olduğu için çalışır.
mikeserv

@mike right, bir erkek snippet ile güncelledim.
Graeme

3

NOT: Diğerleri zaten mekaniğini açıkladı bu $0yüzden hepsini atlayacağım.

Ben genellikle tüm bu sorunu yan adım ve sadece komutu kullanın readlink -f $0. Bu size her zaman bir argüman olarak verdiğiniz şeyin tam yolunu verecektir.

Örnekler

Başlamak için burada olduğumu söyle:

$ pwd
/home/saml/tst/119929/adir

Bir dizin + dosya oluşturun:

$ mkdir adir
$ touch afile
$ cd adir/

Şimdi gösteriş yapmaya başlayın readlink:

$ readlink -f ../adir
/home/saml/tst/119929/adir

$ readlink -f ../
/home/saml/tst/119929

$ readlink -f ../afile 
/home/saml/tst/119929/afile

$ readlink -f .
/home/saml/tst/119929/adir

Ek hile

Biz sorgularken Şimdi tutarlı sonuçla iade edilen $0yoluyla readlinkbiz sadece kullanabilirsiniz dirname $(readlink -f $0)komut -veya- mutlak yolunu almak için basename $(readlink -f $0)komut gerçek adını alır.


0

Sayfam mandiyor ki:

$0: Genişler namearasında shellya da shell script.

Bu argv[0], geçerli kabuğun veya şu anda yorumlanan kabuğun çağrıldığında beslenen ilk işlem dışı ve komut satırı argümanına çevrildiği anlaşılıyor . Daha önce ifade sh ./somescriptyol veriniz onun değişkeni için fakat bu yanlış kendine ait yeni bir süreçtir ve yeni çağrılır .$0 $ENV shshell$ENV

Bu şekilde mevcut ortamda çalışan ve önceden ayarlanmış sh ./somescript.sholandan farklıdır .. ./somescript.sh$0

Sen karşılaştırarak kontrol edebilirsiniz $0etmek /proc/$$/status.

echo 'script="/proc/$$/status"
    echo $0
    cat "$script"' \
    > ./script.sh
sh ./script.sh ; . ./script.sh

Düzeltme için teşekkürler, @toxalot. Bir şey öğrendim.


Sistemimde, kaynaklanıyorsa ( . ./myscript.shveya source ./myscript.sh), $0kabuktur. Ancak, shell ( sh ./myscript.sh) öğesine argüman olarak iletilirse $0, komut dosyasının yolu olur. Tabii ki, shbenim sistemimde Bash. Yani bunun bir fark yaratıp yaratmadığını bilmiyorum.
toxalot

Bir hashbang var mı? Farkın önemli olduğunu düşünüyorum - ve bunu ekleyebilirim - o olmadan sh olmamalı execve bunun yerine kaynak yapmalı , ama onunla birlikte yapmalı exec.
mikeserv

Hashbang ile veya hashbang olmadan, aynı sonuçları alıyorum. Yürütülebilir olsun veya olmasın, aynı sonuçları alıyorum.
toxalot

Ben de! Bence bu bir kabuk yerleşik. Kontrol ediyorum ...
mikeserv

Patlama çizgileri çekirdek tarafından yorumlanır. Eğer ./somescriptbangline #!/bin/shile çalıştırırsanız, koşmaya eşdeğer olacaktır /bin/sh ./somescript. Aksi takdirde kabukta fark etmezler.
Graeme

0

Üstteki yorumlar bölümünden yardım ve sürüm bilgilerini yazdırabilmek için geçerli komut dosyasını grep etmek istiyorum.

İken $0bu komut denir biçimine göre bir öneki yol içerebilir betik adını içerir, hep kullanmış ${0##*/}herhangi giden yolu kaldırır yardım çıkışında komut adını yazdırmak için $0.

Alındığı İleri Bash Scripting kılavuz - Bölüm 10.2 parametre ikamesi

${var#Pattern}

Bunun ön ucuyla eşleşen $varen kısa bölümünden kaldırın .$Pattern$var

${var##Pattern}

Bunun ön ucuyla eşleşen $varen uzun kısmından çıkarın .$Pattern$var

Bu $0eşleşmelerin en uzun kısmı */yolun önekinin tamamı olacak ve yalnızca komut dosyası adını döndürecektir.


Evet, kullanılan komut dosyası adı için de bunu yapmak içinde yardım mesajının. Ama senaryoyu selamlamaktan bahsediyorum , bu yüzden en azından göreceli bir yola ihtiyacım var. Yardım mesajını yorumların en üstüne koymak istiyorum ve yardım mesajını yazdırırken daha sonra tekrarlamak zorunda değilim.
toxalot

0

Benzer bir şey için kullanıyorum:

rPath="$(dirname $(realpath $0))"
echo $rPath 

rPath=$(dirname $(readlink -e -- "$0"))
echo $rPath 

rPath her zaman aynı değere sahiptir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.