Bir kabuk komut dosyası arka plan programı yapmanın en iyi yolu


82

Sadece sh kullanarak bir şey bekleyen bir arka plan programı yapmanın daha iyi bir yolu olup olmadığını merak ediyorum:

#! /bin/sh
trap processUserSig SIGUSR1
processUserSig() {
  echo "doing stuff"
}

while true; do
  sleep 1000
done

Özellikle, döngüden kurtulmanın ve hala sinyalleri dinleyen şeyin bir yolu olup olmadığını merak ediyorum.


3
Bir döngüye ihtiyacınız olacak, ancak örneğinizin muhtemelen beklediğiniz şekilde performans göstermeyeceğini unutmayın. Uyku, bir kabuk yerleşik değildir ve kabuk tarafından alınan bir SIGUSR1 alt süreçlere yayılmaz. Bu nedenle, sinyal işleyiciniz uyku bitene kadar işlem görmeyecektir. Bkz mywiki.wooledge.org/SignalTrap#preview , 3 bölüm.
Mike S

Yanıtlar:



119

Sadece betiğinizi ( ./myscript &) arka planda tutmak onu daemonlaştırmaz. Bir daemon olmak için nelerin gerekli olduğunu açıklayan http://www.faqs.org/faqs/unix-faq/programmer/faq/ , bölüm 1.7'ye bakın . Onu sonlandırmamak için terminalden ayırmalısınız SIGHUP. Bir komut dosyasının arka plan programı gibi davranmasını sağlamak için bir kısayol kullanabilirsiniz;

nohup ./myscript 0<&- &>/dev/null &

işi yapacak. Veya hem stderr hem de stdout'u bir dosyaya yakalamak için:

nohup ./myscript 0<&- &> my.admin.log.file &

Ancak, dikkate almanız gereken başka önemli hususlar da olabilir. Örneğin:

  • Yine de betiğe açık bir dosya tanımlayıcınız olacak, bu da bağlandığı dizinin çıkarılamayacağı anlamına gelir. Gerçek bir arka plan programı olabilmek için chdir("/")(veya cd /betiğinizin içinde) ve çatallayarak ebeveynin çıkması ve böylece orijinal tanımlayıcının kapatılması gerekir.
  • Belki koşun umask 0. Arka plan programı arayanın umask'ına bağlı olmak istemeyebilirsiniz.

Tüm bu yönleri hesaba katan bir komut dosyası örneği için, Mike S'nin cevabına bakınız .


1
Öncelikle, bu cevap için teşekkürler. Çoğunlukla benim için çok iyi çalışıyor. ANCAK günlük dosyasına eklemek istiyorum ve "& >> log.txt" yi denediğimde "Bu hatayı alıyorum ..." sözdizimi hatası beklenmeyen belirteç yakınında "> '" benim için herhangi bir fikir?
tom stratton

7
Ne yapar 0<&-? Bu karakter dizisinin neyi başardığı belli değil.
Craig McQueen

13
Ne 0<&-yapılması gerektiği açık değil . Açıklayan bu bağlantıyı buldum .
Craig McQueen

2
Bu doğru, 0<&-stdin (fd 0) kapatır. Bu şekilde, eğer işleminiz yanlışlıkla stdin'den okursa (yapması kolaydır), verilerin görünmesini sonsuza kadar beklemek yerine bir hata alır.
bronson

1
nohup otomatik olarak / dev / null'dan stdin'i yeniden yönlendirir (kılavuza bakın). Std dosya tanımlayıcılarını kapatmak en iyi uygulama değildir.
fitil

75

Buradaki en çok oylanan cevaplardan bazıları, sadece bir arka plan işleminin veya kabuktan ayrılmış bir arka plan işleminin aksine, bir arka plan programı yapan şeylerin bazı önemli kısımlarını kaçırıyor.

Bu http://www.faqs.org/faqs/unix-faq/programmer/faq/ , bir arka plan programı olmak için neyin gerekli olduğunu açıklamaktadır. Ve bu Run bash betiği daemon setsid'i uygularken chdir'i root'a kaçırır.

Orijinal posterin sorusu aslında "bash kullanarak bir arka plan programı sürecini nasıl oluştururum?" Sorusundan daha spesifikti, ancak konu ve cevaplar genel olarak daemonlaştırıcı kabuk komut dosyalarını tartıştığından, bunu belirtmenin önemli olduğunu düşünüyorum (benim gibi interlopers için bir arka plan programı oluşturmanın ince ayrıntıları).

İşte benim SSS'ye göre davranacak bir kabuk betiği yorumum. trueGüzel çıktı görmek için DEBUG'ı ayarlayın (ancak sonsuz döngüye girmek yerine hemen çıkar):

#!/bin/bash
DEBUG=false

# This part is for fun, if you consider shell scripts fun- and I do.
trap process_USR1 SIGUSR1

process_USR1() {
    echo 'Got signal USR1'
    echo 'Did you notice that the signal was acted upon only after the sleep was done'
    echo 'in the while loop? Interesting, yes? Yes.'
    exit 0
}
# End of fun. Now on to the business end of things.

print_debug() {
    whatiam="$1"; tty="$2"
    [[ "$tty" != "not a tty" ]] && {
        echo "" >$tty
        echo "$whatiam, PID $$" >$tty
        ps -o pid,sess,pgid -p $$ >$tty
        tty >$tty
    }
}

me_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
me_FILE=$(basename $0)
cd /

#### CHILD HERE --------------------------------------------------------------------->
if [ "$1" = "child" ] ; then   # 2. We are the child. We need to fork again.
    shift; tty="$1"; shift
    $DEBUG && print_debug "*** CHILD, NEW SESSION, NEW PGID" "$tty"
    umask 0
    $me_DIR/$me_FILE XXrefork_daemonXX "$tty" "$@" </dev/null >/dev/null 2>/dev/null &
    $DEBUG && [[ "$tty" != "not a tty" ]] && echo "CHILD OUT" >$tty
    exit 0
fi

##### ENTRY POINT HERE -------------------------------------------------------------->
if [ "$1" != "XXrefork_daemonXX" ] ; then # 1. This is where the original call starts.
    tty=$(tty)
    $DEBUG && print_debug "*** PARENT" "$tty"
    setsid $me_DIR/$me_FILE child "$tty" "$@" &
    $DEBUG && [[ "$tty" != "not a tty" ]] && echo "PARENT OUT" >$tty
    exit 0
fi

##### RUNS AFTER CHILD FORKS (actually, on Linux, clone()s. See strace -------------->
                               # 3. We have been reforked. Go to work.
exec >/tmp/outfile
exec 2>/tmp/errfile
exec 0</dev/null

shift; tty="$1"; shift

$DEBUG && print_debug "*** DAEMON" "$tty"
                               # The real stuff goes here. To exit, see fun (above)
$DEBUG && [[ "$tty" != "not a tty" ]]  && echo NOT A REAL DAEMON. NOT RUNNING WHILE LOOP. >$tty

$DEBUG || {
while true; do
    echo "Change this loop, so this silly no-op goes away." >/dev/null
    echo "Do something useful with your life, young padawan." >/dev/null
    sleep 10
done
}

$DEBUG && [[ "$tty" != "not a tty" ]] && sleep 3 && echo "DAEMON OUT" >$tty

exit # This may never run. Why is it here then? It's pretty.
     # Kind of like, "The End" at the end of a movie that you
     # already know is over. It's always nice.

Çıkış DEBUG, olarak ayarlandığında böyle görünür true. Oturum ve süreç grubu kimliği (SESS, PGID) numaralarının nasıl değiştiğine dikkat edin:

<shell_prompt>$ bash blahd

*** PARENT, PID 5180
  PID  SESS  PGID
 5180  1708  5180
/dev/pts/6
PARENT OUT
<shell_prompt>$ 
*** CHILD, NEW SESSION, NEW PGID, PID 5188
  PID  SESS  PGID
 5188  5188  5188
not a tty
CHILD OUT

*** DAEMON, PID 5198
  PID  SESS  PGID
 5198  5188  5188
not a tty
NOT A REAL DAEMON. NOT RUNNING WHILE LOOP.
DAEMON OUT

Bu çok güzel bir cevap! Siz (Mike S) SO (blog, sosyal ağ, her neyse) dışında herhangi bir çevrimiçi varlığınız var mı? Profil bilgilerinizde buna benzer bir şey görmedim.
Michaël Le Barbier

@ MichaelGrünewald Resmi olarak pek değil. Korkarım o kadar ilginç değilim; Ben pek blog yazmıyorum. Schwager.com etki alanına sahibim (şu anda çok az var) ve bazı Arduino projelerim var. Benim takma adım genellikle GreyGnome'dur. Google GreyGnome veya GreyGnome + Arduino ile beni bulabilirsin.
Mike S

1
@MikeS Teşekkürler! Soruyorum çünkü kabuk programlamaya gerçekten ilgi duyan insanlarla tanışmak pek yaygın değil ve bununla ilgili fikir ve görüş alışverişinde bulunmaktan her zaman mutluyum. Bu tamamlayıcı bilgiler için teşekkür ederiz! :)
Michaël Le Barbier

@MikeS, neden iki kez çatala ihtiyacın var? Kodunuzda ana betiğin torunu setsid ile yeni bir oturum lideri olur, değil mi? Bunu neden ilk çatal için yapmıyorsun?
2016

Gönderimde atıfta bulunduğum SSS'den: "İşte bir arka plan programı olma adımları: ... 1. fork()' so the parent can exit, this returns control to the command line or shell invoking your program. ... 2. setsid () 'bir süreç grubu ve oturum grubu lideri olmak için ... şu anda sürecimiz kontrol terminaline sahip değil, artalan süreçleri için İyi Bir Şey ... 3. tekrar “fork () ', böylece ebeveyn ... çıkabilir. Bu, oturum dışı bir grup lideri olarak, bir kontrol terminalini asla geri alamayacağımız anlamına gelir."
Mike S

63
# double background your script to have it detach from the tty
# cf. http://www.linux-mag.com/id/5981 
(./program.sh &) & 

Harika numara! Genelde nohup olmadan giderim, ama kesinlikle bunun için kullanım bulacağım.
joel

Harika! Doğru yol mu bilmiyorum ama bir cazibe gibi çalışıyor.
Marc MAURICE

1
Yani kesmek görünmüyor stdin, stdout, stderr. En azından onunla değil sh.
Craig McQueen

3
Bu ayırma yöntemi epeyce kullanılmaktadır. Buna "çift çatal" denir ve kutsal UNIX kutsal metinlerinden biri olan 2. Stevens'ta ( amazon.com/dp/0201433079 ) daha derinlemesine açıklanmıştır .
Dave

1
Çift çatal ve setsid () hakkında daha fazla teknik bilgi için, bkz. Thelinuxjedi.blogspot.com/2014/02/… . Özellikle, "İkili çatalın arkasındaki gerçek adımlar şu şekildedir:" yazan "Arıza" bölümünü okuyun:
Mike S

4

Bu gerçekten ikilinin kendisinin ne yapacağına bağlıdır.

Örneğin, bir dinleyici oluşturmak istiyorum.

Daemon'u başlatmak basit bir görevdir:

lis_deamon:

#!/bin/bash

# We will start the listener as Deamon process
#    
LISTENER_BIN=/tmp/deamon_test/listener
test -x $LISTENER_BIN || exit 5
PIDFILE=/tmp/deamon_test/listener.pid

case "$1" in
      start)
            echo -n "Starting Listener Deamon .... "
            startproc -f -p $PIDFILE $LISTENER_BIN
            echo "running"
            ;;
          *)
            echo "Usage: $0 start"
            exit 1
            ;;
esac

arka plan programını bu şekilde başlatıyoruz (tüm /etc/init.d/ personeli için ortak yol)

şimdi dinleyiciye gelince, bu bir tür döngü / uyarı olmalı veya betiği istediğiniz şeyi yapması için tetikleyecek başka bir şey olmalıdır. Örneğin, senaryonuzun 10 dakika uyumasını ve uyanmasını istiyorsanız ve size nasıl olduğunuzu soruyorsanız, bunu

while true ; do sleep 600 ; echo "How are u ? " ; done

İşte komutlarınızı uzak makineden dinleyecek ve yerelde çalıştıracak basit dinleyici:

dinleyici:

#!/bin/bash

# Starting listener on some port
# we will run it as deamon and we will send commands to it.
#
IP=$(hostname --ip-address)
PORT=1024
FILE=/tmp/backpipe
count=0
while [ -a $FILE ] ; do #If file exis I assume that it used by other program
  FILE=$FILE.$count
  count=$(($count + 1))
done

# Now we know that such file do not exist,
# U can write down in deamon it self the remove for those files
# or in different part of program

mknod $FILE p

while true ; do 
  netcat -l -s $IP -p $PORT < $FILE |/bin/bash > $FILE
done
rm $FILE

Yani onu başlatmak için: / tmp / deamon_test / listener start

ve kabuktan komut göndermek (veya komut dosyasına sarmak) için:

test_host#netcat 10.184.200.22 1024
uptime
 20:01pm  up 21 days  5:10,  44 users,  load average: 0.62, 0.61, 0.60
date
Tue Jan 28 20:02:00 IST 2014
 punt! (Cntrl+C)

Umarım bu yardımcı olur.




1

Eğer bir a'm olsaydı script.shve onu bash'dan yürütmek ve bash oturumumu kapatmak istediğimde bile çalışır halde bırakmak istersem, o zaman birleştiririm nohupve &sonunda.

misal: nohup ./script.sh < inputFile.txt > ./logFile 2>&1 &

inputFile.txtherhangi bir dosya olabilir. Dosyanızda girdi yoksa, genellikle kullanırız /dev/null. Yani komut şöyle olacaktır:

nohup ./script.sh < /dev/null > ./logFile 2>&1 &

Bundan sonra bash oturumunuzu kapatın, başka bir terminal açın ve çalıştırın: ps -aux | egrep "script.sh"ve betiğinizin hala arka planda çalıştığını göreceksiniz. Tabii ki, eğer durdurmak istiyorsanız, aynı komutu (ps) çalıştırın vekill -9 <PID-OF-YOUR-SCRIPT>


0

Bash Service Manager projesine bakın : https://github.com/reduardo7/bash-service-manager

Uygulama örneği

#!/usr/bin/env bash

export PID_FILE_PATH="/tmp/my-service.pid"
export LOG_FILE_PATH="/tmp/my-service.log"
export LOG_ERROR_FILE_PATH="/tmp/my-service.error.log"

. ./services.sh

run-script() {
  local action="$1" # Action

  while true; do
    echo "@@@ Running action '${action}'"
    echo foo
    echo bar >&2

    [ "$action" = "run" ] && return 0
    sleep 5
    [ "$action" = "debug" ] && exit 25
  done
}

before-start() {
  local action="$1" # Action

  echo "* Starting with $action"
}

after-finish() {
  local action="$1" # Action
  local serviceExitCode=$2 # Service exit code

  echo "* Finish with $action. Exit code: $serviceExitCode"
}

action="$1"
serviceName="Example Service"

serviceMenu "$action" "$serviceName" run-script "$workDir" before-start after-finish

Kullanım örneği

$ ./example-service
# Actions: [start|stop|restart|status|run|debug|tail(-[log|error])]

$ ./example-service start
# Starting Example Service service...

$ ./example-service status
# Serive Example Service is runnig with PID 5599

$ ./example-service stop
# Stopping Example Service...

$ ./example-service status
# Service Example Service is not running

0

Pek çok cevap gibi bu da "gerçek" bir daemonizasyon değil, daha çok nohupyaklaşıma bir alternatiftir .

echo "script.sh" | at now

Açıkça kullanımdan farklılıklar var nohup. Birincisi, ilk etapta ebeveynden kopma yok. Ayrıca "script.sh" ebeveynin ortamını devralmaz.

Hiçbir şekilde bu daha iyi bir alternatif değildir. Bu, süreçleri arka planda başlatmanın farklı (ve biraz tembel) bir yoludur.

Not: Carlo'nun cevabını kişisel olarak, en zarif gibi göründüğü ve hem terminalden hem de komut dosyalarından çalıştığı için destekledim.


0

İşte Bourne kabuğunda (veya Bash) geçerli bir arka plan programı oluşturmak için orijinal teklifte yapılan minimum değişiklik:

#!/bin/sh
if [ "$1" != "__forked__" ]; then
    setsid "$0" __forked__ "$@" &
    exit
else
    shift
fi

trap 'siguser1=true' SIGUSR1
trap 'echo "Clean up and exit"; kill $sleep_pid; exit' SIGTERM
exec > outfile
exec 2> errfile
exec 0< /dev/null

while true; do
    (sleep 30000000 &>/dev/null) &
    sleep_pid=$!
    wait
    kill $sleep_pid &>/dev/null
    if [ -n "$siguser1" ]; then
        siguser1=''
        echo "Wait was interrupted by SIGUSR1, do things here."
    fi
done

Açıklama:

  • Satır 2-7: Bir arka plan programı, ebeveyni olmaması için çatallanmalıdır. Sonsuz çatallaşmayı önlemek için yapay bir argüman kullanmak. "setsid", işlemin ve uçbirimin başlamasından ayrılır.
  • Satır 9: İstediğimiz sinyalin diğer sinyallerden ayrılması gerekiyor.
  • Satır 10: Sarkan "uyku" süreçlerinden kurtulmak için temizleme gereklidir.
  • Satır 11-13: Betiğin stdout, stderr ve stdin'ini yeniden yönlendirin.
  • Satır 16: Arka planda uyu
  • Satır 18: bekleme, uykunun bitmesini bekler, ancak (bazı) sinyallerle kesintiye uğrar.
  • Satır 19: Uyku sürecini sonlandırın, çünkü sinyal yakalandığında hala çalışıyor.
  • Satır 22: SIGUSR1 yakalanmışsa işi yapın.

Sanırım bundan daha kolay olamaz.


-3

Bu dosyayı program.sh olarak kaydederseniz & kullanarak yürütmeyi deneyin

kullanabilirsiniz

$. program.sh &
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.