Bir bash betiğinin sadece bir örneğinin çalıştığından nasıl emin olabilirim?


26

Ek araçlar gerektirmeyen bir çözüm tercih edilecektir.


Peki ya kilit dosyası?
Marco

@Marco Bunu kullanarak SO cevabını buldum , ancak bir yorumda belirtildiği gibi , bu bir yarış durumu yaratabilir
Tobias Kienzler 18:12

3
Bu BashFAQ 45 .
jw013

@ jw013 teşekkürler! Bu yüzden belki bir şey ln -s my.pid .lockkilidi izler (takip eder echo $$ > my.pid) ve başarısızlık durumunda .lock
PID'nin

Yanıtlar:


18

Neredeyse nsg'nin cevabı gibi: bir kilit dizini kullanın . Dizin oluşturma linux ve unix ve * BSD ve daha birçok işletim sistemi altında atomiktir.

if mkdir $LOCKDIR
then
    # Do important, exclusive stuff
    if rmdir $LOCKDIR
    then
        echo "Victory is mine"
    else
        echo "Could not remove lock dir" >&2
    fi
else
    # Handle error condition
    ...
fi

Kilitleme sh'in PID'sini hata ayıklama amacıyla kilit dizinindeki bir dosyaya koyabilirsiniz, ancak kilitleme işleminin devam edip etmediğini görmek için PID'yi kontrol edebilirsiniz. Birçok yarış koşulu bu yolda uzanıyor.


1
Kilitleme örneğinin hala hayatta olup olmadığını kontrol etmek için saklanan PID'yi kullanmayı düşünürdüm. Ancak, buradamkdir NFS üzerinde atomik olmayan bir iddia var (ki bu benim için geçerli değil, ama sanırım doğruysa birinden bahsetmeliyim)
Tobias Kienzler

Evet, kesinlikle, kilitleme işleminin hala devam edip etmediğini görmek için saklanan PID'yi kullanın, ancak mesaj günlüğü dışında herhangi bir şey yapmayı denemeyin. Depolanan pid'i kontrol etme, yeni bir PID dosyası oluşturma, vb. Çalışmaları yarışlar için büyük bir pencere bırakır.
Bruce Ediger

Tamam, Ihunath'ın belirttiği gibi, lockdir büyük olasılıkla /tmpgenellikle NFS'nin paylaşılmadığı bir durumda olacaktı, bu yüzden iyi olmalı.
Tobias Kienzler 19:12

rm -rfKilit dizinini kaldırmak için kullanırdım . rmdirBirisi (mutlaka sizin için) dizine bir dosya eklemeyi başarırsa başarısız olur.
chepner

18

Bruce Ediger'in cevabına eklemek ve bu cevaptan ilham almak için, senaryo sonlandırmasına karşı korunmak için temizlemeye daha fazla akıllı eklemelisiniz:

#Remove the lock directory
function cleanup {
    if rmdir $LOCKDIR; then
        echo "Finished"
    else
        echo "Failed to remove lock directory '$LOCKDIR'"
        exit 1
    fi
}

if mkdir $LOCKDIR; then
    #Ensure that if we "grabbed a lock", we release it
    #Works for SIGTERM and SIGINT(Ctrl-C)
    trap "cleanup" EXIT

    echo "Acquired lock, running"

    # Processing starts here
else
    echo "Could not create lock directory '$LOCKDIR'"
    exit 1
fi

Alternatif olarak if ! mkdir "$LOCKDIR"; then handle failure to lock and exit; fi trap and do processing after if-statement,.
Kusalananda

6

Bu çok basit olabilir, yanılıyorsam lütfen beni düzeltin. psYeterince basit değil mi?

#!/bin/bash 

me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;

# do stuff below this comment

1
Güzel ve / veya mükemmel. :)
Spooky

1
Bu durumu bir hafta boyunca kullandım ve 2 durumda yeni sürecin başlamasını engellemedi. Sorunun ne olduğunu çözdüm - yeni çare eskisinin temeli ve gizleniyor grep -v $$. gerçek örnekler: eski - 14532, yeni - 1453, eski - 28858, yeni - 858.
Naktibalda

Ben değiştirerek sabit grep -v $$içingrep -v "^${$} "
Naktibalda

@Naktibalda iyi yakalama, teşekkürler! Ayrıca düzeltebilirsiniz grep -wv "^$$"(bkz. Düzenleme).
terdon

Bu güncelleme için teşekkürler. Paternim zaman zaman başarısız oldu çünkü kısa pids boşluklarla doluydu.
Naktibalda

4

Marco tarafından belirtildiği gibi bir kilit dosyası kullanırdım

#!/bin/bash

# Exit if /tmp/lock.file exists
[ -f /tmp/lock.file ] && exit

# Create lock file, sleep 1 sec and verify lock
echo $$ > /tmp/lock.file
sleep 1
[ "x$(cat /tmp/lock.file)" == "x"$$ ] || exit

# Do stuff
sleep 60

# Remove lock file
rm /tmp/lock.file

1
(Kilit dosyasını oluşturmayı unuttuğunuzu düşünüyorum) Peki ya yarış koşulları ?
Tobias Kienzler 18:12

ops :) Evet, yarış koşulları benim örneğimde bir sorun, genellikle saatlik ya da günlük cron işleri yazıyorum ve yarış koşulları çok nadir.
nsg

Benim durumumla da alakalı olmamalılar ama bu akılda tutulması gereken bir şey. Belki kullanmak lsof $0da fena değil?
Tobias Kienzler 18:12

Kendinize $$kilit dosyasını yazarak yarış koşulunu azaltabilirsiniz . Sonra sleepkısa bir süre için ve tekrar okuyun. PID hala size aitse, kilidi başarıyla aldınız. Kesinlikle ek bir alete ihtiyaç yok.
Manatwork

1
Bunu daha önce hiç kullanmadım, çalışması gerekiyor. Lsof'un sistemimde gerçekten yavaş (1-2 sn) olduğunu ve muhtemelen yarış koşulları için çok zaman olduğunu unutmayın.
nsg

3

Komut dosyanızın yalnızca bir örneğinin çalıştığından emin olmak istiyorsanız, şunlara bakın:

Komut dosyanızı kilitleyin (paralel çalışmaya karşı)

Aksi taktirde psdenetleyebilir veya çağırabilirsiniz lsof <full-path-of-your-script>, çünkü onlara ek araçlar demem.


Ek :

Aslında böyle yapmayı düşündüm:

for LINE in `lsof -c <your_script> -F p`; do 
    if [ $$ -gt ${LINE#?} ] ; then
        echo "'$0' is already running" 1>&2
        exit 1;
    fi
done

bu pid, <your_script>aynı anda birkaç örneği çatalla ve uygulasanız bile , yalnızca en düşük olan işlemin çalışmaya devam etmesini sağlar .


1
Bağlantı için teşekkürler, ancak cevabınıza gerekli kısımları ekleyebilir misiniz? Bağlantı çürüklüğünü önlemek SE'de sıkça görülen bir politikadır ... Ama bunun gibi bir şey [[(lsof $0 | wc -l) > 2]] && exitaslında yeterli olabilir mi, yoksa bu da yarış koşullarına eğilimli mi?
Tobias Kienzler

Haklısın cevabımın önemli kısmı eksikti ve sadece link gönderme oldukça topal. Cevaba kendi önerimi ekledim.
user1146332 18:12

3

Tek bir bash betiğinin çalıştığından emin olmanın başka bir yolu:

#!/bin/bash

# Check if another instance of script is running
pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1

...

pidof -o %PPID -x $0 Eğer çalışıyorsa mevcut betiğin PID'ini veya başka bir betiğin çalışmaması durumunda hata kodu 1 ile çıkarsa PID'yi alır.


3

Ek araçlar olmadan bir çözüm istediyseniz de, bu benim en sevdiğim yöntem flock:

#!/bin/sh

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

echo "servus!"
sleep 10

Bu örnekler bölümünden geliyor. man flock , daha fazla açıklayan :

Bu kabuk betikleri için yararlı bir boyler kodudur. Kilitlemek istediğiniz kabuk betiğinin en üstüne koyun ve otomatik olarak ilk çalıştırmada kilitlenir. Env var $ FLOCKER çalışmakta olan kabuk betiğine ayarlanmamışsa, sürüyü yürütün ve engelleyici olmayan özel bir kilit alın (betiğin kendisini kilit dosyası olarak kullanarak), kendini doğru argümanlarla yeniden çalıştırmadan önce. Ayrıca FLOCKER env değerini doğru değere ayarlar, böylece tekrar çalışmaz.

Dikkate alınması gereken noktalar:

Ayrıca bkz . Https://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at .


1

Bu Anselmo'nun Cevabının değiştirilmiş bir versiyonudur . Fikir, bash betiğini kullanarak salt okunur bir dosya flocktanıtıcı oluşturmak ve kilidi kullanmak için kullanmaktır .

SCRIPT=`realpath $0`     # get absolute path to the script itself
exec 6< "$SCRIPT"        # open bash script using file descriptor 6
flock -n 6 || { echo "ERROR: script is already running" && exit 1; }   # lock file descriptor 6 OR show error message if script is already running

echo "Run your single instance code here"

Diğer tüm cevaplayıcılar arasındaki temel fark, bu kodun dosya sistemini değiştirmemesi, çok düşük bir ayak izi kullanması ve komut dosyası çıkış durumundan bağımsız olarak bittiğinde dosya tanımlayıcısının kapatılması nedeniyle herhangi bir temizlemeye ihtiyaç duymamasıdır. Bu nedenle, komut dosyasının başarısız olup olmaması önemli değildir.


Olmamak için iyi bir nedeniniz yoksa ve ne yaptığınızı bildiğinizden emin olmadığınız sürece, tüm kabuk değişkeni referanslarını mutlaka belirtmelisiniz . Öyleyse yapmalısın exec 6< "$SCRIPT".
Scott

@Scott Önerilerinize göre kodu değiştirdim. Çok teşekkürler.
John Doe

1

Komut dosyamın gerçekten tek örnekle çalıştığını kontrol etmek için cksum kullanıyorum , dosya adı ve dosya yolunu bile değiştirdim .

Bindirme ve kilitleme dosyası kullanmıyorum, çünkü sunucum aniden kapanıyorsa, sunucu açıldıktan sonra el ile kilitlenen dosyayı kaldırmam gerekiyor.

Not: grep ps için ilk satırdaki #! / Bin / bash gereklidir

#!/bin/bash

checkinstance(){
   nprog=0
   mysum=$(cksum $0|awk '{print $1}')
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(cksum /proc/$i/cwd/$cmd|awk '{print $1}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance 

#--- run your script bellow 

echo pass
while true;do sleep 1000;done

Veya kodunuzun içinde kodlanmış cksum kullanabilirsiniz, bu nedenle komut dosyanızın adını , yolunu veya içeriğini değiştirmek istiyorsanız bir daha endişelenmeyin .

#!/bin/bash

mysum=1174212411

checkinstance(){
   nprog=0
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(grep mysum /proc/$i/cwd/$cmd|head -1|awk -F= '{print $2}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance

#--- run your script bellow

echo pass
while true;do sleep 1000;done

1
Lütfen tam sağlama toplamı kodlamanın iyi bir fikir olduğunu açıklayınız.
Scott

kodlama sağlama toplamı değil, kodunuzun tek kimlik anahtarıdır, başka bir örnek çalışırken, diğer kabuk komut sürecini kontrol eder ve kimlik anahtarınız bu dosyadaysa ilk önce dosyayı yakalar, bu nedenle örneğinizin zaten çalıştığı anlamına gelir.
arputra

TAMAM; lütfen bunu açıklamak için cevabınızı düzenleyin . Ve gelecekte, kod değil sonrası çoklu 30 satırlı uzun bloklar lütfen bu onlar gibi bakmak olmadan (neredeyse) aynı söyleyerek ve açıklayan nasıl they farklı. Ve “betiğinizin içinde kodlanmış [sic] cksum” gibi şeyler söyleme ve değişken isimlerini kullanmaya devam etmeyin mysumve fsumartık bir sağlama toplamından bahsetmediğinizde.
Scott

İlginç görünüyor, teşekkürler! Ve unix.stackexchange'e hoş geldiniz :)
Tobias Kienzler


0

Sana kodum

#!/bin/bash

script_file="$(/bin/readlink -f $0)"
lock_file=${script_file////_}

function executing {
  echo "'${script_file}' already executing"
  exit 1
}

(
  flock -n 9 || executing

  sleep 10

) 9> /var/lock/${lock_file}

Göre man flocksadece iyileştirilmesi:

  • Komut dosyasının tam adına göre kilit dosyasının ismi.
  • mesaj executing

Buraya sleep 10koyduğum yere, bütün ana senaryoyu koyabilirsiniz.

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.