Çalıştırmadan önce tüm kabuk betiği nasıl okunur?


35

Genellikle, bir scrpit düzenlerseniz, betiğin tüm kullanım kullanımları hatalara açıktır.

Anladığım kadarıyla, bash (diğer kabukları da?) Betiği adım adım okudum, yani betiği harici olarak değiştirdiyseniz yanlış şeyler okumaya başlar. Bunu önlemenin bir yolu var mı?

Örnek:

sleep 20

echo test

Bu betiği çalıştırırsanız, bash ilk satırı okur (10 bayt) ve uyur. Devam ettiğinde, 10'uncu bayttan başlayan komut dosyasında farklı içerikler olabilir. Yeni senaryoda bir çizginin ortasında olabilirim. Böylece çalışan komut dosyası bozulacak.


"Komut dosyasını harici olarak değiştirerek" derken ne demek istiyorsunuz?
maulinglawns

1
Belki de bir içeriğe tüm içeriği sarmanın bir yolu vardır, bu yüzden kabuk önce tüm betiği okuyacaktır? Fakat işlevi çağırdığınız son satır ne olacak, EOF'a kadar okunacak mı? Belki son ihmal \nhile yapardı? Belki bir denizaltı ()yapacak? Ben çok tecrübeli değilim, lütfen yardım edin!
VasyaNovikov

@ maulinglawns betiği gibi içerikler içeriyorsa sleep 20 ;\n echo test ;\n sleep 20ve onu düzenlemeye başlarsam, yaramazlık olabilir. Örneğin, bash betiğin ilk 10 baytını okuyabilir, sleepkomutu anlayabilir ve uyuyabilir. Devam ettikten sonra, 10 bayttan başlayan dosyada farklı içerikler olacaktır.
VasyaNovikov

1
Demek istediğin şu ki, çalışmakta olan bir betiği düzenliyorsun? Önce betiği durdurun, düzenlemelerinizi yapın ve sonra yeniden başlatın.
maulinglawns

@ Maulinglawns evet, temelde bu. Sorun şu ki, senaryoları durdurmak benim için uygun değil ve bunu yapmayı her zaman hatırlamak zor. Belki de önce bütün betiği okumayı bash yapmaya zorlamanın bir yolu vardır?
VasyaNovikov

Yanıtlar:


43

Evet kabukları ve bashözellikle de dosyayı tek seferde bir satır okumak için dikkatli olun, bu nedenle etkileşimli olarak kullandığınız zamanki gibi çalışır.

Dosya aranamadığında (bir boru gibi), karakterin bashokunmadığından emin olmak için bir seferde bir bayt okuduğunu fark edeceksiniz \n. Dosya aranabilir olduğunda, bir kerede tam blokları okuyarak en iyi duruma getirir, ancak sonrasına geri dönün \n.

Bunun gibi şeyler yapabilirsiniz anlamına gelir:

bash << \EOF
read var
var's content
echo "$var"
EOF

Veya kendilerini güncelleyen komut dosyaları yazabilirsiniz. Size bu garantiyi vermezse yapamayacağınız bir şey.

Şimdi, bunun gibi şeyler yapmak nadirdir ve sizin de fark ettiğiniz gibi, bu özellik faydalı olduğundan daha sık karşılaşır.

Bunu önlemek için, dosyayı yerinde değiştirmediğinizden emin olabilirsiniz (örneğin, bir kopyayı değiştirebilir ve kopyayı yerine taşıyabilirsiniz (örneğin sed -iya da perl -pive bazı editörler, örneğin)).

Veya senaryonuzu şöyle yazabilirsiniz:

{
  sleep 20
  echo test
}; exit

( Kapatma exithattından }hemen önce aynı parantez içinde olmasına rağmen;

veya:

main() {
  sleep 20
  echo test
}
main "$@"; exit

Bir exitşey yapmadan önce kabuğun betiği okuması gerekecektir . Bu, kabuğun komut dosyasından tekrar okunmamasını sağlar.

Bu, tüm komut dosyasının hafızada saklanacağı anlamına gelir.

Bu ayrıca betiğin ayrıştırılmasını da etkileyebilir.

Örneğin, içinde bash:

export LC_ALL=fr_FR.UTF-8
echo $'St\ue9phane'

U + 00E9’un UTF-8’de kodlanmış olduğunu gösterir. Ancak, bunu değiştirirseniz:

{
  export LC_ALL=fr_FR.UTF-8
  echo $'St\ue9phane'
}

\ue9Komut bu durumda olan ayrıştırıldı o zaman yürürlükte olan charset genişletilecek önceexport komut yürütülür.

Ayrıca, eğer sourceaka .komutu kullanılırsa, bazı kabuklarda kaynak dosyalarında aynı problemi yaşarsınız.

Bu böyle değil ait bashkimin olsa sourcekomut yorumlayarak önce tamamen dosyasını okur. Özellikle yazıyorsanız bash, betiğin başına ekleyerek bunu gerçekten kullanabilirsiniz:

if [[ ! $already_sourced ]]; then
  already_sourced=1
  source "$0"; exit
fi

(Gelecekteki sürümlerinin bashşu anda bir sınırlama olarak görülen davranışı değiştirebileceğini hayal edebileceğinize inanmıyorum (bash ve AT&T ksh, söyleyebileceği kadarıyla davranan tek POSIX benzeri kabuklarıdır) ve already_sourcedpüf noktası, değişkenin çevrede olmadığını varsaydığı için biraz kırılgandır, BASH_SOURCE değişkeninin içeriğini etkilediğinden bahsetmez)


@ VasyaNovikov, şu anda SE'de (ya da en azından benim için) yanlış bir şey var gibi görünüyor. Madeni eklediğimde sadece birkaç cevap vardı ve yorumunuz 16 dakika önce yayınlandığını söylese de (ya da belki benim misketlerimi kaybettiğimi söylese de) henüz ortaya çıkmış görünüyor. Her neyse, dosyanın boyutu arttığında sorunlardan kaçınmak için burada gerekli olan ekstra "çıkış" ı not edin (cevabınıza eklediğim yorumda belirtildiği gibi).
Stéphane Chazelas

Stéphane, sanırım başka bir çözüm buldum. Kullanmaktır }; exec true. Bu şekilde, bazı editörlere (emac'ler gibi) uygun olan dosya sonunda yeni satırlara gerek yoktur. Doğru çalışmayı düşünebileceğim tüm testler}; exec true
VasyaNovikov

@ VasyaNovikov, ne demek istediğinizi tam olarak bilmiyorsunuz. Bundan daha iyi ne olabilir }; exit? Ayrıca çıkış durumunu da kaybediyorsunuz.
Stéphane Chazelas 23:17

Farklı bir soruda belirtildiği gibi: önce nokta komutunun tamamı ayrıştırılır, ardından dot komutunun ( . script) kullanılması durumunda bileşik deyimi çalıştırın .
schily

@schily, evet, bu cevapta AT&T ksh ve bash sınırlamaları olarak bahsetti. Diğer POSIX tipi mermilerin bu sınırlaması yoktur.
Stéphane Chazelas

12

Sadece dosyayı silmeniz gerekir (yani kopyalayın, silin, kopyayı tekrar orijinal ismine çevirin). Aslında birçok editör bunu sizin için yapacak şekilde yapılandırılabilir. Bir dosyayı düzenleyip değiştirilmiş bir arabellek kaydettiğinizde, dosyanın üzerine yazmak yerine eski dosyayı yeniden adlandırır, yeni bir tane oluşturur ve yeni içeriği yeni dosyaya koyar. Bu nedenle çalışan herhangi bir komut dosyası sorunsuz devam etmelidir.

Vim ve emacs için kolayca kullanılabilen RCS gibi basit bir sürüm kontrol sistemi kullanarak, değişikliklerin geçmişine sahip olmanın çifte avantajını elde edersiniz ve ödeme sistemi varsayılan olarak mevcut dosyayı kaldırmalı ve doğru modlarla yeniden oluşturmalıdır. (Elbette bu tür dosyaları zor bağlamaya dikkat edin).


"delete" aslında sürecin bir parçası değil. Düzgün bir şekilde atomize etmek istiyorsanız, hedef dosya üzerinde bir yeniden adlandırma yaparsınız - bir silme adımınız varsa, işleminizin silme işleminden sonra ancak adlandırmadan önce ölmesi ve hiçbir dosya bırakılmaması riski vardır ( veya bir okuyucu bu penceredeki dosyaya erişmeye çalışır ve eski ya da yeni sürümleri bulamaz).
Charles Duffy

11

En basit çözüm:

{
  ... your code ...

  exit
}

Bu şekilde, bash {}çalıştırmadan önce tüm bloğu okuyacak ve exityönerge kod bloğunun dışında hiçbir şey okunmayacağından emin olacaktır.

Senaryoyu "çalıştırmak" istemiyorsanız, "kaynaklamak" yerine, farklı bir çözüme ihtiyacınız var. Bu daha sonra çalışması gerekir:

{
  ... your code ...

  return 2>/dev/null || exit
}

Veya çıkış kodu üzerinde doğrudan kontrol istiyorsanız:

{
  ... your code ...

  ret="$?";return "$ret" 2>/dev/null || exit "$ret"
}

Voila Bu betiğin düzenlenmesi, kaynaklanması ve yürütülmesi güvenlidir. Başlangıçta okunurken, bu milisaniyede onu değiştirmediğinizden emin olmalısınız.


1
Bulduğum şey, EOF'yi göremediği ve dosyayı okumayı bırakmadığı, ancak "tamponlanmış akış" işleminde karışıklığa neden olduğu ve dosyanın sonundan önce aranan bittiği, bu yüzden boyutu iyi görünüyorsa dosya çok fazla artar, ancak dosyayı eskisinden iki kat daha büyük hale getirdiğinizde kötü görünür. Kısa süre içinde bash koruyucularına bir hata bildireceğim.
Stéphane Chazelas


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
terdon

5

Kavramın ispatı. İşte kendini değiştiren bir script:

cat <<EOF >/tmp/scr
#!/bin/bash
sed  s/[k]ept/changed/  /tmp/scr > /tmp/scr2

# this next line overwites the on disk copy of the script
cat /tmp/scr2 > /tmp/scr
# this line ends up changed.
echo script content kept
EOF
chmod u+x /tmp/scr
/tmp/scr

değiştirilen versiyonu görüyoruz

Bunun nedeni, bash load'lerin betiğe açmak için bir dosya tanıtıcısını tutmasıdır, bu nedenle dosyadaki değişiklikler hemen görülür.

Bellek içi kopyayı güncellemek istemiyorsanız, orijinal dosyanın bağlantısını kaldırın ve değiştirin.

Bunu yapmanın bir yolu sed -i kullanmaktır.

sed -i '' filename

kavramın ispatı

cat <<EOF >/tmp/scr
#!/bin/bash
sed  s/[k]ept/changed/  /tmp/scr > /tmp/scr2

# this next line unlinks the original and creates a new copy.
sed -i ''  /tmp/scr

# now overwriting it has no immediate effect
cat /tmp/scr2 > /tmp/scr
echo script content kept
EOF

chmod u+x /tmp/scr
/tmp/scr

Komut dosyasını değiştirmek için bir düzenleyici kullanıyorsanız, "yedek kopyayı sakla" özelliğini etkinleştirmek, düzenleyicinin değiştirilen sürümü varolanın üzerine yazmak yerine yeni bir dosyaya yazmasına neden olmak için gerekli olabilir.


2
Hayır, bashdosyayı açmıyor mmap(). Etkileşimli bir terminal cihazından komutları alırken olduğu gibi, bir defada bir satırın gerektiği gibi okunması çok dikkatlidir.
Stéphane Chazelas

2

Komut dosyanızı bir bloğa sarmak {}, muhtemelen en iyi seçenektir, ancak komut dosyalarınızı değiştirmenizi gerektirir.

F=$(mktemp) && cp test.sh $F && bash $F; rm $F;

En iyi ikinci seçenek ( tmpfs varsayarsak ), dezavantajı, eğer komut dosyalarınız kullanıyorsa, $ 0 kırmasıdır .

gibi bir şey kullanmak F=test.sh; tail -n $(cat "$F" | wc -l) "$F" | bashdaha az idealdir çünkü tüm dosyayı bellekte tutması ve 0 $ kırması gerekir.

orijinal dosyaya dokunmaktan kaçınılmalıdır, böylece en son değiştirilen süre, okuma kilitleri ve sert bağlantılar bozulmaz. bu şekilde dosyayı çalıştırırken bir düzenleyiciyi açık bırakabilirsiniz ve rsync, dosyayı yedeklemeler ve sabit bağlantılar için beklendiği gibi gereksiz yere sağlama toplamı sağlamaz.

Düzenleme sırasında dosyayı değiştirmek işe yarar, ancak diğer komut dosyalarına / kullanıcılara / veya birisine unutamayacağınız için daha az dayanıklıdır. Ve yine zor bağlantıları koparır.


bir kopyasını yapan herhangi bir şey işe yarayabilirdi. tac test.sh | tac | bash
Jasen,
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.