Yürütme sırasında bir komut dosyasını düzenlerseniz ne olur?


31

Linux'ta işlemlerin nasıl yapıldığının yanlış anlaşılmasının bir sonucu olabilecek genel bir sorum var.

Amaçlarım için, bir metin dosyasını, geçerli kullanıcı için etkinleştirilmiş yürütme izinleriyle bir metin dosyasına kaydedilen bash kod pasajı olarak tanımlayacağım.

Birbirimizi tandem olarak adlandıran bir dizi senaryom var. Sadelik uğruna, onlara A, B ve C komut dosyaları diyeceğim. Komut Dosyası A, bir dizi deyim yürütür ve duraklar, B komutunu çalıştırır, sonra duraklatır, sonra C komutunu çalıştırır. Diğer bir deyişle, seri Adımlar bunun gibi bir şey:

Script A'yı Çalıştır:

  1. İfadeler dizisi
  2. Duraklat
  3. Komut Dosyası B'yi Çalıştır
  4. Duraklat
  5. Script C'yi Çalıştır

Tecrübelerden biliyorum ki, A komut dosyasını ilk duraklatmaya kadar çalıştırırsam, sonra B komut dosyasında düzenlemeler yaparsam, bu düzenlemelerin devam etmesine izin verdiğimde kodun yürütülmesine yansıtılır. Aynı şekilde, A komut dosyası hala duraklatılmışken C komutunda düzenlemeler yaparsam, değişiklikleri kaydettikten sonra devam etmesine izin verirseniz, bu değişiklikler kodun yürütülmesine yansıtılır.

İşte asıl soru o zaman, hala çalışıyorken Script A'yı düzenlemenin bir yolu var mı? Veya yürütme başladığında düzenleme yapmak imkansız mıdır?


2
Kabuğa bağlı olduğunu düşünüyorum. bash kullandığınızı söylemenize rağmen. Kabuğun betiği içerden yüklediğine bağlı gibi gözüküyor.
strugee

davranış, dosyayı yürütmek yerine kaynak yaparsanız da değişebilir.
strugee

1
Sanırım bash çalıştırmadan önce bütün bir betiği belleğe okuyor.
w4etwetewtwet

2
@handuel, hayır değil. Girdiğiniz komutları yorumlamaya başlamak için istemde "exit" yazana kadar beklemiyormuş gibi.
Stéphane Chazelas

1
@StephaneChazelas Evet, terminalden okumak değil, bir senaryo çalıştırmaktan farklı.
w4etwetewtwet

Yanıtlar:


21

Unix'te çoğu düzenleyici, düzenlenmiş içerikleri içeren yeni bir geçici dosya oluşturarak çalışır. Düzenlenen dosya kaydedildiğinde, orijinal dosya silinir ve geçici dosyaya orijinal adı verilir. (Elbette, veri kaybını önlemek için çeşitli güvenlik önlemleri vardır.) Bu, örneğin, "hiç yerinde" olmayan , ("yerinde") bayrağıyla kullanıldığında sedveya kullanıldığında kullanılan stildir . Adı "eski adıyla yeni yer" olmalıydı.perl-i

Bu iyi çalışır çünkü unix (en azından yerel dosya sistemleri için) açılmış bir dosyanın, "silinmiş" olsa ve aynı ada sahip yeni bir dosya oluşturulmuş olsa bile kapanana kadar varlığını sürdürdüğünü garanti eder. (Unix sisteminin bir dosyayı "silmek" aslında "unlink" olarak adlandırılması anlamına gelmez.) Bu nedenle, genel olarak konuşursak, eğer bir kabuk yorumlayıcısının açık bir kaynak dosyası varsa ve dosyayı yukarıda açıklanan şekilde "düzenlersiniz". orijinal dosya hala açık olduğundan, kabuk değişiklikleri bile görmez.

[Not: Tüm standartlara dayalı yorumlarda olduğu gibi, yukarıdakiler birden fazla yoruma tabidir ve NFS gibi çeşitli köşe davaları vardır. Anlaşmalar, yorumları istisnalar dışında doldurabilir.]

Elbette, dosyaları doğrudan değiştirmek mümkündür; bu sadece düzenleme amaçları için uygun değildir, çünkü bir dosyadaki verilerin üzerine yazarken, aşağıdaki tüm verileri değiştirmeden silemez veya ekleyemezsiniz, bu da çok fazla yeniden yazma anlamına gelir. Dahası, bu değişimi yaparken, dosyanın içeriği tahmin edilemez ve dosyayı açmış olan işlemler zarar görür. Bundan kurtulmak için (örneğin, veritabanı sistemlerinde olduğu gibi), gelişmiş bir değişiklik protokolleri kümesine ve dağıtılmış kilitlere ihtiyacınız vardır; Tipik bir dosya düzenleme programının kapsamı dışında kalan şeyler.

Bu nedenle, bir dosyayı kabuk tarafından işlenirken düzenlemek istiyorsanız, iki seçeneğiniz vardır:

  1. Dosyaya ekleyebilirsiniz. Bu her zaman çalışması gerekir.

  2. Aynı uzunluktaki yeni içeriklerle dosyanın üzerine yazabilirsiniz . Bu, kabuğun dosyanın o bölümünü okumuş olup olmamasına bağlı olarak çalışmayabilir veya çalışmayabilir. Çoğu dosya giriş / çıkış dosyası okuma arabellekleri içerdiğinden ve bildiğim tüm kabuklar çalıştırılmadan önce bir bileşik komutun tamamını okuduğundan, bununla başa çıkmanız pek mümkün değildir. Kesinlikle güvenilir olmazdı.

Posix standardında, dosya yürütülürken aslında bir komut dosyasına ekleme olanağı gerektiren herhangi bir ifade bilmiyorum, bu yüzden neredeyse tüm mevcut tekliflerle çok daha az, her Posix uyumlu kabukla çalışmayabilir. ve bazen posix uyumlu kabukları. Yani YMMV. Fakat bildiğim kadarıyla bash ile güvenilir bir şekilde çalışıyor.

Kanıt olarak, burada bash ddolarak üzerine basıp eklemeye yarayan bash'deki 99 şişe bira programının "loop'suz" bir uygulaması var. Dosya, tam olarak aynı uzunluktaki bir yorumla; Son sonucun kendi kendini değiştiren davranış olmadan yürütülebilmesi için bunu yaptım.)

#!/bin/bash
if [[ $1 == reset ]]; then
  printf "%s\n%-16s#\n" '####' 'next ${1:-99}' |
  dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^#### $0 | cut -f1 -d:) bs=1 2>/dev/null
  exit
fi

step() {
  s=s
  one=one
  case $beer in
    2) beer=1; unset s;;
    1) beer="No more"; one=it;;
    "No more") beer=99; return 1;;
    *) ((--beer));;
  esac
}
next() {
  step ${beer:=$(($1+1))}
  refrain |
  dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^next\  $0 | cut -f1 -d:) bs=1 conv=notrunc 2>/dev/null
}
refrain() {
  printf "%-17s\n" "# $beer bottles"
  echo echo ${beer:-No more} bottle$s of beer on the wall, ${beer:-No more} bottle$s of beer.
  if step; then
    echo echo Take $one down, pass it around, $beer bottle$s of beer on the wall.
    echo echo
    echo next abcdefghijkl
  else
    echo echo Go to the store, buy some more, $beer bottle$s of beer on the wall.
  fi
}
####
next ${1:-99}   #

Bunu çalıştırdığımda "Artık yok" ile başlıyor, sonra -1 ile negatif sayılara süresiz olarak devam ediyor.
Daniel Hershcovich

export beer=100Komut dosyasını çalıştırmadan önce yaparsam , beklendiği gibi çalışır.
Daniel Hershcovich

@DanielHershcovich: oldukça doğru; benim tarafımdan özensiz test. Sanırım tamir ettim; şimdi isteğe bağlı bir sayım parametresi alır. Parametre önbelleğe alınmış kopyaya uymuyorsa, daha iyi ve daha ilginç bir düzeltme otomatik olarak sıfırlamak olacaktır.
rici

18

bash çalıştırmadan hemen önce komutları okuduğundan emin olmak için uzun bir yol gidiyor.

Örneğin:

cmd1
cmd2

Kabuk, betiği bloklarla okuyacak, muhtemelen her iki komutu da okuyacak, ilkini yorumlayacak ve sonra cmd1betiğin sonuna geri dönecek cmd2ve onu okumak ve yürütmek için betiği tekrar okuyacaktır .

Kolayca doğrulayabilirsiniz:

$ cat a
echo foo | dd 2> /dev/null bs=1 seek=50 of=a
echo bar
$ bash a
foo

( straceçıktıya baksam da, birkaç yıl önce aynı şeyi denediğimden çok daha fazla süslü şeyler (sanki verileri birkaç kez okumak, geri aramak ...) yapıyor gibi gözüküyor, artık yeni sürümler için geçerli değildir).

Bununla birlikte, komut dosyanızı şu şekilde yazın:

{
  cmd1
  cmd2
  exit
}

Kabuğun kapanışa kadar okuması }, hafızaya alması ve çalıştırması gerekir. Bu nedenle exit, kabuk komut dosyasından tekrar okunmayacak, böylece kabuk yorumlanırken güvenle düzenlenebilir.

Alternatif olarak, komut dosyasını düzenlerken, komut dosyasının yeni bir kopyasını yazdığınızdan emin olun. Kabuk orjinalini okumaya devam edecek (silinmiş veya yeniden adlandırılmış olsa bile).

Bunu yapmak için, yeniden adlandırmak the-scriptiçin the-script.oldve kopyalama the-script.oldiçin the-scriptve düzenlemek buna.


4

Çalışırken komut dosyasını değiştirmenin gerçekten güvenli bir yolu yoktur, çünkü kabuk dosyayı okumak için arabelleğe alma özelliğini kullanabilir. Ayrıca, komut dosyası yeni bir dosyayla değiştirilerek değiştirilirse, kabuklar genellikle yalnızca belirli işlemleri yaptıktan sonra yeni dosyayı okuyacaktır.

Genellikle, bir komut dosyası yürütülürken değiştirildiğinde, kabuk, sözdizimi hatalarını bildirmekle sonuçlanır. Bunun nedeni, kabuk komut dosyasını kapatıp yeniden açtığında, geri dönüşte kendisini yeniden konumlandırmak için dosyanın içine bayt uzaklığını kullanır.


4

Senaryonuzu bir tuzak kurarak ve sonra execyeni komut dosyası içeriğini almak için kullanarak bu sorunu çözebilirsiniz. Bununla birlikte, execçağrı, betiği sıfırdan başlayarak çalıştırma işleminde ulaştığı yerden başlatmaz ve böylece B betiği çağrılır (böyle devam eder).

#! /bin/bash

CMD="$0"
ARGS=("$@")

trap reexec 1

reexec() {
    exec "$CMD" "${ARGS[@]}"
}

while : ; do sleep 1 ; clear ; date ; done

Bu ekranda tarih göstermeye devam edecektir. O zaman benim komut ve değişim düzenleme olabilir dateiçin echo "Date: $(date)". Yazarken çalışan komut dosyası hala sadece tarihi gösterir. trapYakalamak için belirlediğim sinyali gönderirsem exec, komut $CMDve argümanlar olan komut dosyası (geçerli çalışan işlemi belirtilen komutla değiştirir) $@. Bunu kill -1 PID, PID'in çalışan betiğin PID'si olduğu ve çıkışının komut çıkışından Date:önce gösterileceği şekilde vermesini sağlayarak yapabilirsiniz date.

Komut dosyanızın "durumunu" harici bir dosyada (say / tmp olarak) saklayabilir ve programın yeniden yürütüldüğü sırada nerede "devam ettirileceğini" bilmek için içeriği okuyabilirsiniz. Daha sonra, "Script A" 'yı kestikten sonra yeniden başlattığınızda baştan başlayacaktır. Durum bilgisi olan bir sürüm şöyle bir şey olurdu:

#! /bin/bash

trap reexec 1
trap cleanup 2 3 9 15

CMD="$0"
ARGS=("$@")
statefile='/tmp/scriptA.state'
EXIT=1

reexec() { echo "Restarting..." ; exec "$CMD" "${ARGS[@]}"; }
cleanup() { rm -f $statefile; exit $EXIT; }
run_scriptB() { /path/to/scriptB; echo "scriptC" > $statefile; }
run_scriptC() { /path/to/scriptC; echo "stop" > $statefile;  }

while [ "$state" != "stop" ] ; do

    if [ -f "$statefile" ] ; then
        state="$(cat "$statefile")"
    else
        state='starting'
    fi

    case "$state" in
        starting)         
            run_scriptB
        ;;
        scriptC)
            run_scriptC
        ;;
    esac
done

EXIT=0
cleanup

Bu sorunu betiğin başında $0ve $@başında yakalayarak ve execbunun yerine bu değişkenleri kullanarak düzelttim .
Drav Sloan,
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.