Skip to content

Instantly share code, notes, and snippets.

@rodw
Created August 14, 2012 01:53
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save rodw/3345668 to your computer and use it in GitHub Desktop.
Save rodw/3345668 to your computer and use it in GitHub Desktop.
Bash script that uses a PID file to add daemon-like start/stop/status behavior to an arbitrary program.
#!/bin/bash
# Uses a PID file to add daemon-like behavior to an arbitrary program.
################################################################################
usage() {
echo "Usage: `basename $0` PROGRAM {start|stop|restart|force-stop|force-restart|status} [PIDFILE|PID]" >&2
echo "Where: PROGRAM is an executable file." >&2
echo " PIDFILE is the file that contains (or will contain) the PID." >&2
echo " PID is a process id to use in place of a PIDFILE." >&2
}
# At least two arguments are required.
if [[ -z "${1}" || -z "${2}" ]]; then
usage
exit 1
fi
# The first argument must be an actual file.
if [[ ! -e "${1}" ]]; then
echo "File \"${1}\" not found. Exiting." 1>&2;
exit 2
fi
PROGLONG=$(realpath $1)
PROGSHORT=$(basename ${PROGLONG})
PIDFILE=${PIDFILE:-"${PROGSHORT}.pid"}
# If there is a third argument, try to interpret it as a file or PID value.
if [[ ${3} ]]; then
if [[ `expr $3 + 1 2> /dev/null` ]]; then
PID=$3;
elif [[ -e ${3} || "${2}" == "start" ]]; then
PIDFILE="${3}"
else
echo "Third argument must be a number or a file. (Found $3). Exiting." 1>&2;
exit 3
fi
fi
# Get the PID from PIDFILE if we don't have one yet.
if [[ -z "${PID}" && -e ${PIDFILE} ]]; then
PID=$(cat ${PIDFILE});
fi
start() {
echo "Starting $PROGSHORT (PID written to $PIDFILE)."
${PROGLONG} & echo $! > ${PIDFILE}
}
status() {
if [[ -z "${PID}" ]]; then
echo "${PROGSHORT} is not running (missing PID)."
elif [[ -e /proc/${PID}/exe && "`realpath /proc/${PID}/exe`" == "`realpath ${PROGLONG}`" ]]; then
echo "${PROGSHORT} is running (PID: ${PID})."
else
echo "${PROGSHORT} is not running (tested PID: ${PID})."
fi
}
stop() {
if [[ -z "${PID}" ]]; then
echo "${PROGSHORT} is not running (missing PID)."
elif [[ -e /proc/${PID}/exe && "`realpath /proc/${PID}/exe`" == "`realpath ${PROGLONG}`" ]]; then
kill $1 ${PID}
else
echo "${PROGSHORT} is not running (tested PID: ${PID})."
fi
}
case "$2" in
start)
start;
;;
restart)
stop; sleep 1; start;
;;
stop)
stop
;;
force-stop)
stop -9
;;
force-restart)
stop -9; sleep 1; start;
;;
status)
status
;;
*)
usage
exit 4
;;
esac
exit 0
######################################################################
# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law.
#
# If your jurisdiction supports the concept of Public Domain works,
# this program is released into the Public Domain.
#
# Otherwise this program is available under the following terms:
#---------------------------------------------------------------------
# Copyright (c) 2012, Rodney Waldhoff
#
# Everyone is permitted to copy and distribute verbatim or modified
# copies of this program with or without this notice.
######################################################################
@kelbyers
Copy link

Just as an FYI, your status and stop functions fail to correctly match the running daemon with the path of the program when the program is a script, such as python or bash.

E.g.,
$ realpath /proc/3065/exe
/usr/bin/python2.7
$ ps -fp 3065
UID        PID  PPID  C STIME TTY          TIME CMD
vagrant   3065  3032  0 13:06 pts/0    00:00:00 /usr/bin/python /tmp/pyd

$ bash -x ./pid-file-daemon.sh /tmp/pyd
[ cut ]
+ [[ /usr/bin/python2.7 == \/\t\m\p\/\p\y\d ]]
+ echo 'pyd is not running (tested PID: 3065).'
pyd is not running (tested PID: 3065).

For my own use, I do the following:

I added a function to strip off the first command line path, if there are multiple paths. Since the command line used to start the process takes no arguments, the pid cmdline should either be just the command name, or it should be a path to the interpreter followed by the command name.

function proc2cmd {
    declare PID=$1
    declare CMDLINE=$(echo $(cat /proc/$PID/cmdline | while read -r -d $'\0' LINE; do echo $LINE; done | tail -1))
    realpath "${CMDLINE}"
}

Then, in the status() and stop() functions, I changed "realpath /proc/${PID}/exe" to call my proc2cmd function, instead:

elif [[ -e /proc/${PID}/exe && "`proc2cmd ${PID}`" == $( realpath "${PROGLONG}" ) ]]; then

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment