NOT: @ jw013, aşağıdaki yorumlarda aşağıdaki desteklenmeyen itirazları yapmaktadır:
Aşağı oy, kendi kendini değiştiren kodun genellikle kötü uygulama olarak kabul edilmesidir. Küçük montaj programlarının eski günlerinde, koşullu dalları azaltmanın ve performansı artırmanın akıllı bir yoluydu, ancak günümüzde güvenlik riskleri avantajlardan daha ağır basmaktadır. Komut dosyasını çalıştıran kullanıcının komut dosyasında yazma ayrıcalıkları yoksa yaklaşımınız işe yaramaz.
Bunu işaret ederek güvenlik itirazlar cevap herhangi özel izinler olan sadece bir kez gerekli başına yükleme / güncelleme amacıyla eylem / install güncelleme kendinden yüklenen şahsen güvenli oldukça çağrı olur - Senaryoyu. Ben de ona man sh
benzer yollarla benzer hedeflere ulaşma başvurusuna işaret ettim . O zamanlar, cevabımda temsil edilebilecek ya da gösterilemeyecek güvenlik kusurları ya da genel olarak tavsiye edilmeyen uygulamalar ne olursa olsun , sorunun cevabında olduğundan daha büyük bir olasılıkla sorunun kendisinden kaynaklandığını belirtmek için uğraşmadım :
Komut dosyasını /path/to/script.sh olarak çalıştırmanın her zaman PATH'de bulunan Zsh'ı kullanması için shebang'ı nasıl ayarlayabilirim?
Memnun değilim, @ jw013 henüz desteklenmeyen argümanını en az birkaç hatalı ifadeyle ileri sürerek itiraz etmeye devam etti :
İki dosya değil, tek bir dosya kullanırsınız. [ man sh
Başvurulan]
paketi tek bir dosya başka bir dosya değiştirme sahiptir. Kendini değiştiren bir dosyanız var. Bu iki durum arasında belirgin bir fark vardır. Girdi alan ve çıktı üreten bir dosya iyidir. Çalışırken kendini değiştiren yürütülebilir bir dosya genellikle kötü bir fikirdir. İşaret ettiğiniz örnek bunu yapmaz.
İlk başta:
SADECE EXECUTABLE HERHANGİ KOD EXECUTABLE kabuk yazısıdır #!
KENDİNİ
(gerçi bile #!
olduğunu resmen belirtilmemiş )
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
{ ${l=lsof -p} $$
echo "$l \$$" | sh
} | grep \
"COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE
##OUTPUT
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
file 8900 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
file 8900 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
file 8900 mikeserv 0r REG 0,35 108 15496912 /tmp/zshUTTARQ (deleted)
file 8900 mikeserv 1u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 255r REG 0,33 108 2134129 /home/mikeserv/file
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sh 8906 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
sh 8906 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
sh 8906 mikeserv 0r FIFO 0,8 0t0 15500515 pipe
sh 8906 mikeserv 1w FIFO 0,8 0t0 15500514 pipe
sh 8906 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
{ sed -i \
'1c#!/home/mikeserv/file' ./file
./file
sh -c './file ; echo'
grep '#!' ./file
}
##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links
#!/home/mikeserv/file
Buna gereken hiç bir etkisi olması için sırayla - Bir kabuk komut dosyası sadece bir metin dosyasıdır okumak onun talimatları sonra başka yürütülebilir dosya tarafından yorumlanır önce nihayet diğer yürütülebilir dosya daha sonra, bu diğer yürütülebilir dosya tarafından kendi yorumunu yürütür arasında kabuk betiği. Öyle mümkün değildir bir kabuk komut dosyası yürütme için ikiden az dosya içerir. zsh
Kendi derleyicisinde olası bir istisna vardır , ancak bununla çok az deneyimim var ve burada hiçbir şekilde temsil edilmiyor.
Bir kabuk betiğinin hashbang amaçlanan yorumlayıcısına işaret etmeli veya ilgisiz olarak atılmalıdır.
Kabuk, girdisini ayrıştırmak ve yorumlamak için iki temel moda sahiptir: ya geçerli girdisi a'yı tanımlamaktadır <<here_document
ya da a'yı tanımlamaktadır { ( command |&&|| list ) ; } &
- başka bir deyişle, kabuk ya bir anahtarı okuduktan sonra yürütmesi gereken bir komut için sınırlayıcı olarak yorumlar . bir dosya oluşturmak ve başka bir komut için bir dosya tanımlayıcıya eşlemek için talimatlar olarak. Bu kadar.
Kabuğu yürütmek için komutları yorumlarken, ayrılmış sözcük kümesindeki belirteçleri sınırlar . Gibi belirteci ya da kapatma - uygulanabilir olduğunda - kabuk liste, belirteç bir kapatma ile sınırlanmış kadar böyle bir yeni satır olarak bir komut listesinde okumaya devam gerekir belirteci bir açıklık karşılaştığında })
için ({
yürütme önce.
Kabuk, basit bir komut ile bileşik komut arasında ayrım yapar . Bileşik komut yürütme önce okunmalıdır komutlar kümesi, ancak kabuk gerçekleştirmez $expansion
oluşturucu herhangi basit komutlar bu tek başına her biri yürütür kadar.
Bu nedenle, aşağıdaki örnekte, ;semicolon
ayrılmış kelimeler bireysel sınırlayan basit komutlar olmayan kaçan ise \newline
iki ile karakter sınırlayan birleşik komut:
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
echo "simple command ${sc=1}" ;\
: > $0 ;\
echo "simple command $((sc+2))" ;\
sh -c "./file && echo hooray"
sh -c "./file && echo hooray"
#END
FILE
##OUTPUT
simple command 1
simple command 3
hooray
Bu kılavuz ilkelerin basitleştirilmesidir. Kabuk yapılıları, alt kabukları, mevcut ortamı vb. Düşündüğünüzde çok daha karmaşık hale gelir , ancak buradaki amacım için yeterlidir.
Ve konuşma yerleşik ins ve komut listeleri, bir function() { declaration ; }
salt bir atama bir araçtır bileşik komutu a basit komutla. Kabuk $expansions
, bildirim deyiminin üzerinde herhangi bir işlem yapmamalı - içermek için <<redirections>
- bunun yerine tanımı tek, değişmez bir dize olarak saklamalı ve çağrıldığında yerleşik özel bir kabuk olarak yürütmelidir.
Dolayısıyla, yürütülebilir bir kabuk komut dosyasında bildirilen bir kabuk işlevi, yorumlama kabuğunun belleğinde, burada ekli belgeleri giriş olarak içerecek şekilde genişletilmeyen ve kabuk oluşturulmuş olarak her çağrıldığında kaynak dosyasından bağımsız olarak yürütülen yorumlanmış dize biçiminde depolanır. Kabuğun mevcut ortamı devam ettiği sürece.
Yeniden yönlendirme işleçleri <<
ve <<-
her ikisi de, burada belge olarak bilinen kabuk girdi dosyasında bulunan satırların komut girişine yeniden yönlendirilmesine izin verir .
Bu belgede , bir sonrakinden sonra başlayan \newline
ve aralarında s olmayan sadece sınırlayıcı ve a'yı içeren bir çizgi olana kadar devam eden tek bir kelime olarak ele alınacaktır . Sonra bir sonraki belge başlar, eğer varsa. Biçim aşağıdaki gibidir:\newline
[:blank:]
[n]<<word
here-document
delimiter
... burada isteğe bağlı n
dosya tanımlayıcı numarasını temsil eder. Numara atlanırsa, buradaki belge standart girdiyi ifade eder (dosya tanımlayıcı 0).
for shell in dash zsh bash sh ; do sudo $shell -c '
{ readlink /proc/self/fd/3
cat <&3
} 3<<-FILE
$0
FILE
' ; done
#OUTPUT
pipe:[16582351]
dash
/tmp/zshqs0lKX (deleted)
zsh
/tmp/sh-thd-955082504 (deleted)
bash
/tmp/sh-thd-955082612 (deleted)
sh
Anlıyorsun? Kabuğun üzerindeki her kabuk için bir dosya oluşturur ve bir dosya tanımlayıcıyla eşler. Gelen zsh, (ba)sh
kabuğunda düzenli dosya oluşturur /tmp
, çıkış döker, bir tanımlayıcı bunu eşler, daha sonra siler /tmp
tanımlayıcısı çekirdeğin kopyası tüm kalıntılar o yüzden dosyayı. dash
tüm bu saçmalıklardan kaçınır ve çıktı işlemini |pipe
yönlendirme <<
hedefine yönelik anonim bir dosyaya bırakır .
Bu dash
şunları yapar :
cmd <<HEREDOC
$(cmd)
HEREDOC
işlevsel olarak bash
's eşdeğer :
cmd <(cmd)
while dash
uygulaması en azından POSIXly taşınabilir.
BİRİNCİ DOSYALARI HANGİ YAPAN
Bu yüzden aşağıdaki cevapta:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat
} <<SCRIPT >$0
[SCRIPT BODY]
SCRIPT
_fn ; exec $0
FILE
Aşağıdakiler olur:
Önce cat
kabuğun FILE
içine hangi dosyayı oluşturduysa ./file
onu çalıştırılabilir hale getirip yürütürüm.
Çekirdek, atanmış olan bir dosya tanımlayıcıyla yorumlanır #!
ve çağrılır ./usr/bin/sh
<read
./file
sh
dizeyi başında başlayan ve biten bileşik komutundan oluşan belleğe eşler ._fn()
SCRIPT
Ne zaman _fn
denir, sh
ilk önce bir tanımlayıcı tanımlanan dosyayı map yorumlanacağıyla gerekir <<SCRIPT...SCRIPT
önce çağırma _fn
çünkü yerleşik yarar desteğiyle, özel olarak SCRIPT
ise _fn
'ın<input.
Tarafından dizeleri çıktı printf
ve command
dışarı yazılır _fn
'ın çıkış standardı >&1
- Geçerli kabuk en yönlendirilir hangi ARGV0
- ya $0
.
cat
<&0
standart girdi dosya tanımlayıcısını - SCRIPT
- >
kesilmiş geçerli kabuğun ARGV0
argümanı üzerinde birleştirir veya $0
.
Bunu daha önce akım okuma işleminin tamamlanması bileşik komutu , sh exec
ve yeni yeniden yazılmış - - yürütülebilir s $0
değişken.
Zaman itibaren ./file
onun içerdiği talimatlar o gerektiğini belirtmek dek denir exec
yeniden d, sh
tek onu okur bileşik komutu onları çalıştırır olarak ise, bir anda ./file
kendisini hiç bir şey yapmaz mutlu yeni içeriğini kabul hariç. Aslında işte olan dosyalar/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
TÜM SONRASI TEŞEKKÜRLER
Yani @ jw013 şunu belirtirse:
Girdi alan ve çıktı üreten bir dosya gayet iyi ...
... bu cevaba yönelik hatalı eleştirisinin ortasında, aslında burada kullanılan tek yöntemi istemeden kınıyor ve temelde şu şekilde çalışıyor:
cat <new_file >old_file
CEVAP
Buradaki tüm cevaplar iyi, ama hiçbiri tam olarak doğru değil. Herkes dinamik ve kalıcı olarak yol açamadığınızı iddia ediyor gibi görünüyor #!bang
. İşte yoldan bağımsız bir sapma kurmanın bir gösterimi:
DEMO
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE
ÇIKTI
$0 : ./file
lines : 13
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
12 > SCRIPT
13 > _rewrite_me ; out=$0 _rewrite_me ; exec $0
$0 : /home/mikeserv/file
lines : 8
!bang : #!/usr/bin/zsh
shell : /usr/bin/zsh
1 > #!/usr/bin/zsh
2 > printf "
...
7 > sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 > sed -e 'N;s/\n/ >\t/' -e 4a\\...
Anlıyorsun? Sadece betiğin üzerine yazılmasını sağlıyoruz. Ve bir git
senkronizasyondan sonra sadece bir kez olur . Bu noktadan sonra #! Bang çizgisinde doğru yol var.
Şimdi hemen hemen hepsi kabartmak var. Bunu güvenli bir şekilde yapmak için ihtiyacınız olan:
Üst kısımda tanımlanan ve alt kısımda yazmayı yapan bir işlev. Bu şekilde, ihtiyacımız olan her şeyi belleğe kaydeder ve üzerine yazmaya başlamadan önce tüm dosyanın okunduğundan emin oluruz.
Yolun ne olması gerektiğini belirlemenin bir yolu. command -v
bunun için oldukça iyi.
Yorumlu metinler gerçekten yardımcı olur çünkü gerçek dosyalardır. Bu arada senaryonuzu saklayacaklar. Dizeleri de kullanabilirsiniz ama ...
Kabuğun, komut dosyanızı çalıştıran komutla aynı komut listesinde yazan komutu okuduğundan emin olmalısınız.
Bak:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE
exec
Komutu yalnızca bir satır aşağı taşıdığımı fark ettim . Şimdi:
#OUTPUT
$0 : ./file
lines : 14
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
13 > _rewrite_me ; out=$0 _rewrite_me
14 > exec $0
Komut dosyasının bir sonraki komutta okuyamaması nedeniyle çıktının ikinci yarısını alamıyorum. Yine de, eksik olan tek komut sonuncusu olduğu için:
cat ./file
#!/usr/bin/zsh
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
Senaryo olması gerektiği gibi geldi - çoğunlukla heredoc içinde olduğu için - ama doğru planlamıyorsanız, filistinizi kısaltabilirsiniz, bu da bana olan şeydi.
env
Hem / bin hem de / usr / bin'de olmadığından emin misiniz ?which -a env
Onaylamaya çalışın .