Cách tốt nhất để tạo daemon script shell?


81

Tôi tự hỏi liệu có cách nào tốt hơn để tạo một daemon chờ đợi thứ gì đó chỉ sử dụng sh hơn là:

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

while true; do
  sleep 1000
done

Đặc biệt, tôi đang tự hỏi liệu có cách nào để thoát khỏi vòng lặp và vẫn có thể lắng nghe các tín hiệu hay không.


3
Bạn sẽ cần một vòng lặp, nhưng lưu ý rằng ví dụ của bạn có thể sẽ không hoạt động theo cách bạn mong đợi. Sleep không phải là một nội trang của shell và SIGUSR1 mà shell nhận được sẽ không được truyền tới các tiến trình con. Do đó, trình xử lý tín hiệu của bạn sẽ không được xử lý cho đến khi kết thúc chế độ ngủ. Xem mywiki.wooledge.org/SignalTrap#preview , phần thứ 3.
Mike S

Câu trả lời:



118

Chỉ làm nền cho tập lệnh của bạn ( ./myscript &) sẽ không tạo ra nó. Xem http://www.faqs.org/faqs/unix-faq/programmer/faq/ , phần 1.7, mô tả những gì cần thiết để trở thành một daemon. Bạn phải ngắt kết nối nó khỏi thiết bị đầu cuối để SIGHUPkhông làm chết nó. Bạn có thể sử dụng một phím tắt để làm cho một tập lệnh có vẻ hoạt động giống như một daemon;

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

sẽ thực hiện công việc. Hoặc, để chụp cả stderr và stdout vào một tệp:

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

Tuy nhiên, có thể có những khía cạnh quan trọng khác mà bạn cần xem xét. Ví dụ:

  • Bạn vẫn sẽ mở bộ mô tả tệp cho tập lệnh, điều đó có nghĩa là thư mục mà nó được gắn vào sẽ không thể tháo gỡ được. Để trở thành một daemon thực sự, bạn nên chdir("/")(hoặc cd /bên trong tập lệnh của bạn) và fork để phần tử gốc thoát ra, và do đó bộ mô tả ban đầu bị đóng.
  • Có lẽ chạy umask 0. Bạn có thể không muốn phụ thuộc vào umask của trình gọi của daemon.

Đối với một ví dụ về một kịch bản mà sẽ đưa tất cả các khía cạnh vào tài khoản, xem Mike S' câu trả lời .


1
Đầu tiên, cảm ơn vì câu trả lời này. Nó hầu như đang hoạt động rất tốt đối với tôi. NHƯNG tôi muốn thêm vào tệp nhật ký và khi tôi thử "& >> log.txt" Tôi gặp lỗi này ... "lỗi cú pháp gần mã thông báo không mong muốn`> '"có ý kiến ​​nào cho tôi không?
tom stratton

7
Làm gì 0<&-? Không rõ chuỗi ký tự đó đạt được mục đích gì.
Craig McQueen

13
Nó không rõ ràng 0<&-là những gì có nghĩa là để làm. Tôi tìm thấy liên kết này giải thích nó.
Craig McQueen

2
Đúng vậy, 0<&-đóng stdin (fd 0). Bằng cách đó, nếu process của bạn vô tình đọc từ stdin (dễ thực hiện), nó sẽ gặp lỗi thay vì treo mãi chờ dữ liệu hiển thị.
bronson

1
nohup tự động chuyển hướng stdin từ / dev / null (xem hướng dẫn sử dụng). Đóng bộ mô tả tệp std không phải là phương pháp hay nhất.
wick

74

Một số câu trả lời được bình chọn nhiều nhất ở đây thiếu một số phần quan trọng của điều khiến daemon trở thành daemon, trái ngược với chỉ là một quy trình nền hoặc một quy trình nền tách rời khỏi trình bao.

Http://www.faqs.org/faqs/unix-faq/programmer/faq/ này mô tả những gì cần thiết để trở thành một daemon. Và tập lệnh Run bash này dưới dạng daemon triển khai các setid, mặc dù nó bỏ lỡ chdir để root.

Câu hỏi của người đăng ban đầu thực sự cụ thể hơn là "Làm cách nào để tạo một quy trình daemon bằng cách sử dụng bash?", Nhưng vì chủ đề và câu trả lời thảo luận về các tập lệnh shell nói chung, tôi nghĩ điều quan trọng là phải chỉ ra nó (đối với những người liên quan như tôi đang xem xét chi tiết tốt của việc tạo daemon).

Đây là bản trình diễn của tôi về một tập lệnh shell sẽ hoạt động theo Câu hỏi thường gặp. Đặt DEBUG để truexem đầu ra khá (nhưng nó cũng thoát ngay lập tức thay vì lặp lại liên tục):

#!/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 man." >/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.

Đầu ra trông như thế này khi DEBUGđược đặt thành true. Lưu ý cách số ID nhóm phiên và quá trình (SESS, PGID) thay đổi:

<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

Đó là một câu trả lời rất hay! Bạn (Mike S) có bất kỳ hình thức hiện diện trực tuyến nào ngoài SO (blog, mạng xã hội, bất kỳ điều gì không?) Tôi không thấy bất kỳ điều gì như thế này trên thông tin hồ sơ của bạn.
Michaël Le Barbier

@ MichaelGrünewald Không có nhiều hình thức. Tôi e rằng tôi không thú vị như vậy; Tôi không viết blog nhiều. Tôi sở hữu miền schwager.com (hiện có rất ít) và tôi có một số dự án Arduino. Danh mục de plume của tôi nói chung là GreyGnome. Bạn có thể Google GreyGnome hoặc GreyGnome + Arduino và tìm tôi xung quanh.
Mike S

1
@MikeS Cảm ơn! Tôi đang hỏi vì không quá phổ biến khi gặp những người thực sự quan tâm đến lập trình shell và tôi luôn sẵn lòng trao đổi ý kiến ​​và quan điểm về điều này. Cảm ơn bạn vì thông tin bổ sung! :)
Michaël Le Barbier

@MikeS, tại sao bạn cần fork hai lần? Trong mã của bạn, cháu của tập lệnh mẹ sẽ trở thành người dẫn đầu phiên mới với setid, đúng không? Tại sao bạn không thể làm điều này cho lần fork đầu tiên?
quảng cáo

Từ Câu hỏi thường gặp mà tôi đã tham khảo trong bài đăng của mình: "Dưới đây là các bước để trở thành daemon: ... 1. fork()' so the parent can exit, this returns control to the command line or shell invoking your program. ... 2. setsid () 'để trở thành nhóm quy trình và nhóm trưởng phiên ... quy trình của chúng tôi hiện không có thiết bị đầu cuối kiểm soát, đó là a Good Thing cho daemon ... 3. lại `fork () 'để cha mẹ ... có thể thoát. Điều này có nghĩa là chúng tôi, với tư cách là trưởng nhóm không phải phiên, không bao giờ có thể lấy lại thiết bị đầu cuối kiểm soát."
Mike S

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

Mẹo hay! Tôi thường đi mà không có nohup, nhưng tôi chắc chắn sẽ sử dụng cho cái này.
joel

Tuyệt quá! Tôi không biết đó có phải là cách thích hợp không, nhưng nó hoạt động như một cái bùa.
Marc MAURICE

1
Điều đó không có vẻ để ngắt kết nối stdin, stdout, stderr. Ít nhất là không với sh.
Craig McQueen

3
Phương pháp này để tách ra được sử dụng khá nhiều. Nó được gọi là "ngã ba kép" và được giải thích sâu hơn trong một trong những thánh thư UNIX, Stevens thứ 2 ( amazon.com/dp/0201433079 ).
Dave

1
Để biết thêm thông tin kỹ thuật về double fork và setid (), hãy xem thelinuxjedi.blogspot.com/2014/02/… . Đặc biệt hãy đọc phần "Các sự cố", nơi mà nó nói, "Các bước thực sự đằng sau những đôi ngã ba như sau:"
Mike S

4

Nó thực sự phụ thuộc vào bản thân hệ nhị phân sẽ làm gì.

Ví dụ, tôi muốn tạo một số người nghe.

Daemon bắt đầu là nhiệm vụ đơn giản:

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

đây là cách chúng tôi khởi động daemon (cách phổ biến cho tất cả nhân viên /etc/init.d/)

bây giờ đối với bản thân người nghe, Nó phải là một loại vòng lặp / cảnh báo nào đó nếu không sẽ kích hoạt kịch bản để làm những gì bạn muốn. Ví dụ: nếu bạn muốn kịch bản của bạn ngủ 10 phút và thức dậy và hỏi bạn rằng bạn đang làm như thế nào, bạn sẽ làm điều này với

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

Đây là trình lắng nghe đơn giản mà bạn có thể thực hiện sẽ lắng nghe các lệnh của bạn từ máy từ xa và thực thi chúng trên cục bộ:

thính giả :

#!/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

Vì vậy, để bắt đầu UP nó: / tmp / deamon_test / nghe bắt đầu

và để gửi các lệnh từ shell (hoặc bọc nó thành script):

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)

Hy vọng điều này sẽ giúp ích.




1

Nếu tôi có một script.shvà tôi muốn thực thi nó từ bash và để nó chạy ngay cả khi tôi muốn đóng phiên bash của mình thì tôi sẽ kết hợp nohup&ở cuối.

thí dụ: nohup ./script.sh < inputFile.txt > ./logFile 2>&1 &

inputFile.txtcó thể là bất kỳ tệp nào. Nếu tệp của bạn không có đầu vào thì chúng tôi thường sử dụng /dev/null. Vì vậy, lệnh sẽ là:

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

Sau khi đóng phiên bash của bạn, hãy mở một thiết bị đầu cuối khác và thực thi: ps -aux | egrep "script.sh"và bạn sẽ thấy rằng tập lệnh của mình vẫn đang chạy ở chế độ nền. Đối với tòa án, nếu bạn muốn dừng nó, hãy thực hiện cùng một lệnh (ps) vàkill -9 <PID-OF-YOUR-SCRIPT>


0

Xem dự án Trình quản lý dịch vụ Bash : https://github.com/reduardo7/bash-service-manager

Ví dụ triển khai

#!/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

Ví dụ sử dụng

$ ./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

Giống như nhiều câu trả lời, câu trả lời này không phải là một sự đa dạng hóa "thực sự" mà là một sự thay thế cho nohupphương pháp tiếp cận.

echo "script.sh" | at now

Rõ ràng là có sự khác biệt so với việc sử dụng nohup. Đối với một người không có tách rời khỏi cha mẹ ngay từ đầu. Ngoài ra "script.sh" không kế thừa môi trường của cha mẹ.

Không có nghĩa đây là một thay thế tốt hơn. Nó chỉ đơn giản là một cách khác (và hơi lười biếng) để khởi chạy các quy trình trong nền.

Tái bút Cá nhân tôi đã ủng hộ câu trả lời của carlo vì nó có vẻ là câu trả lời thanh lịch nhất và hoạt động cả từ các tập lệnh đầu cuối và bên trong


-2

thử thực thi bằng cách sử dụng & nếu bạn lưu tệp này dưới dạng program.sh

bạn có thể dùng

$. program.sh &
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.