Functionality of a start-stop-restart shell script - linux

I am a shell scripting newbie trying to understand some code, but there are some lines that are too complexe for me. The piece of code I'm talking about can be found here: https://gist.github.com/447191
It's purpose is to start, stop and restart a server. That's pretty standard stuff, so it's worth taking some time to understand it. I commented those lines where I am unsure about the meaning or that I completely don't understand, hoping that somone could give me some explanation.
#!/bin/bash
#
BASE=/tmp
PID=$BASE/app.pid
LOG=$BASE/app.log
ERROR=$BASE/app-error.log
PORT=11211
LISTEN_IP='0.0.0.0'
MEM_SIZE=4
CMD='memcached'
# Does this mean, that the COMMAND variable can adopt different values, depending on
# what is entered as parameter? "memcached" is chosen by default, port, ip address and
# memory size are options, but what is -v?
COMMAND="$CMD -p $PORT -l $LISTEN_IP -m $MEM_SIZE -v"
USR=user
status() {
echo
echo "==== Status"
if [ -f $PID ]
then
echo
echo "Pid file: $( cat $PID ) [$PID]"
echo
# ps -ef: Display uid, pid, parent pid, recent CPU usage, process start time,
# controling tty, elapsed CPU usage, and the associated command of all other processes
# that are owned by other users.
# The rest of this line I don't understand, especially grep -v grep
ps -ef | grep -v grep | grep $( cat $PID )
else
echo
echo "No Pid file"
fi
}
start() {
if [ -f $PID ]
then
echo
echo "Already started. PID: [$( cat $PID )]"
else
echo "==== Start"
# Lock file that indicates that no 2nd instance should be started
touch $PID
# COMMAND is called as background process and ignores SIGHUP signal, writes it's
# output to the LOG file.
if nohup $COMMAND >>$LOG 2>&1 &
# The pid of the last background is saved in the PID file
then echo $! >$PID
echo "Done."
echo "$(date '+%Y-%m-%d %X'): START" >>$LOG
else echo "Error... "
/bin/rm $PID
fi
fi
}
# I don't understand this function :-(
kill_cmd() {
SIGNAL=""; MSG="Killing "
while true
do
LIST=`ps -ef | grep -v grep | grep $CMD | grep -w $USR | awk '{print $2}'`
if [ "$LIST" ]
then
echo; echo "$MSG $LIST" ; echo
echo $LIST | xargs kill $SIGNAL
# Why this sleep command?
sleep 2
SIGNAL="-9" ; MSG="Killing $SIGNAL"
if [ -f $PID ]
then
/bin/rm $PID
fi
else
echo; echo "All killed..." ; echo
break
fi
done
}
stop() {
echo "==== Stop"
if [ -f $PID ]
then
if kill $( cat $PID )
then echo "Done."
echo "$(date '+%Y-%m-%d %X'): STOP" >>$LOG
fi
/bin/rm $PID
kill_cmd
else
echo "No pid file. Already stopped?"
fi
}
case "$1" in
'start')
start
;;
'stop')
stop
;;
'restart')
stop ; echo "Sleeping..."; sleep 1 ;
start
;;
'status')
status
;;
*)
echo
echo "Usage: $0 { start | stop | restart | status }"
echo
exit 1
;;
esac
exit 0

1)
COMMAND="$CMD -p $PORT -l $LISTEN_IP -m $MEM_SIZE -v" — -v in Unix tradition very often is a shortcut for --verbose. All those dollar signs are variable expansion (their text values are inserted into the string assigned to new variable COMMAND).
2)
ps -ef | grep -v grep | grep $( cat $PID ) - it's a pipe: ps redirects its output to grep which outputs to another grep and the end result is printed to the standard output.
grep -v grep means "take all lines that do not contain 'grep'" (grep itself is a process, so you need to exclude it from output of ps). $( $command ) is a way to run command and insert its standard output into this place of script (in this case: cat $PID will show contents of file with name $PID).
3) kill_cmd.
This function is an endless loop trying to kill the LIST of 'memcached' processes' PIDs. First, it tries to send TERM signal (politely asking each process in $LIST to quit, saving its work and shutting down correctly), gives them 2 seconds (sleep 2) to do their shutdown job and then tries to make sure that all processes are killed using signal KILL (-9), which slays the process immediately using OS facilities: if a process has not done its shutdown work in 2 seconds, it's considered hung). If slaying with kill -9 was successful, it removes the PID file and quits the loop.
ps -ef | grep -v grep | grep $CMD | grep -w $USR | awk '{print $2}' prints all PIDs of processes with name $CMD ('memcached') and user $USR ('user'). -w option of grep means 'the Whole word only' (this excludes situations where the sought name is a part of another process name, like 'fakememcached'). awk is a little interpreter most often used to take a word number N from every line of input (you can consider it a selector for a column of a text table). In this case, it prints every second word in ps output lines, that means every PID.
If you have any other questions, I'll add answers below.

Here is an explanation of the pieces of code you do not understand:
1.
# Does this mean, that the COMMAND variable can adopt different values, depending on
# what is entered as parameter? "memcached" is chosen by default, port, ip address and
# memory size are options, but what is -v?
COMMAND="$CMD -p $PORT -l $LISTEN_IP -m $MEM_SIZE -v"
In the man, near -v:
$ man memcached
...
-v Be verbose during the event loop; print out errors and warnings.
...
2.
# ps -ef: Display uid, pid, parent pid, recent CPU usage, process start time,
# controling tty, elapsed CPU usage, and the associated command of all other processes
# that are owned by other users.
# The rest of this line I don't understand, especially grep -v grep
ps -ef | grep -v grep | grep $( cat $PID )
Print all processes details (ps -ef), exclude the line with grep (grep -v grep) (since you are running grep it will display itself in the process list) and filter by the text found in the file named $PID (/tmp/app.pid) (grep $( cat $PID )).
3.
# I don't understand this function :-(
kill_cmd() {
SIGNAL=""; MSG="Killing "
while true
do
## create a list with all the pid numbers filtered by command (memcached) and user ($USR)
LIST=`ps -ef | grep -v grep | grep $CMD | grep -w $USR | awk '{print $2}'`
## if $LIST is not empty... proceed
if [ "$LIST" ]
then
echo; echo "$MSG $LIST" ; echo
## kill all the processes in the $LIST (xargs will get the list from the pipe and put it at the end of the kill command; something like this < kill $SIGNAL $LIST > )
echo $LIST | xargs kill $SIGNAL
# Why this sleep command?
## some processes might take one or two seconds to perish
sleep 2
SIGNAL="-9" ; MSG="Killing $SIGNAL"
## if the file $PID still exists, delete it
if [ -f $PID ]
then
/bin/rm $PID
fi
## if list is empty
else
echo; echo "All killed..." ; echo
## get out of the while loop
break
fi
done
}
This function will kill all the processes related to memcached slowly and painfully (actually quite the opposite).
Above are the explanations.

Related

Why is my script telling me there's a process running when it's actually not?

func () {
local arrayOfParams=$#
local CMD="ps aux | grep -v grep"
for item in ${arrayOfParams[#]};
do
local CMD="$CMD | grep -e $item"
done
echo "Current process PID : $$"
echo "`date`: CMD is -> $CMD"
local isProcRunning=`eval ${CMD}`
if [[ 0 -eq `echo "$isProcRunning" | wc -l` ]];
then
echo "`date`: Following process is running: "
echo "$isProcRunning"
echo ""
if [[ "$FORCE_RUN" == true ]];
then
echo "`date`: User forced the run of current process ..."
# we extract the PIDs of proces mathcing our search criteria
PIDS=(`echo $isProcRunning | awk '{print \$2}'`)
for PID in "${PIDS[#]}";
do
# we extract the command running on said PID so that we know what we kill
local PIDCMD=`ps -o cmd -p ${PID} --no-headers`
echo "`date`: Killing PID: $PID with CMD -> $PIDCMD"
kill -9 $PID
done
else
echo "`date`: Current process will exit!!!"
fi
exit 0
fi
}
The above code is meant to check if current process is already running .
I use ps -ef and grep based on some params which should be found under CMD
I remove the line coresponding to the current PID and then I expect the wc -l to return 0, but it's returning 1.
I run that line outside of the script and I get 0 indeed.
Any idea why this is happening ?

How to limit concurrent SSH or Dropbear Tunnel connections

I need to limit concurrent SSH/Dropbear Tunnel connections to 1 login per user.
I have a script that takes care of that.
But it doesn't work for me because when there are many users it becomes saturated and it takes a long time to kick the users.
Another problem with this script is that if the user logs out and logs back in it is detected as multilogin.
Maxlogins and MaxSessions does not work on Dropbear.
Below is the script I am using:
#!/bin/bash
# This script locates all users who have multiple active dropbear
# processes and kills processes in excess of one for each user.
if [ "$EUID" -ne 0 ]; then
printf "Please run as root.\n"
exit
fi
IFS=+
while true; do
PIDFILE=$(mktemp)
AUTHFILE=$(mktemp)
USERS=$(mktemp)
ps aux | grep dropbear | grep -v grep | awk 'BEGIN{} {print $2}' > $PIDFILE
journalctl -r | grep dropbear | grep auth > $AUTHFILE
while read LINE; do
USER=$(printf "%s" $LINE | sed "s/^.* '//" | sed "s/'.*$//" -)
PID=$(printf "%s" $LINE | sed "s/^.*\[//" | sed "s/].*$//" -)
if grep -Fxq $(printf "%s" $USER) $USERS; then
:
else
printf "%s\n" $USER >> $USERS
fi
USERFILE=$(printf "/tmp/%s" $USER)
if [ ! -f $USERFILE ]; then
touch $USERFILE
fi
if grep -Fxq $(printf "%s" $PID) $PIDFILE; then
printf "%s\n" $PID >> $USERFILE
else
:
fi
done < $AUTHFILE
while read USER; do
i=1
while read PID; do
if [ $i -gt 1 ]; then
printf "Kill PID %s of user %s\n" $PID $USER
kill -9 $(printf "%s" $PID)
curl -k "https://redesprivadasvirtuales.com/modules/servers/openvpn/vega.php?secret=DD8sPD&user=$USER"
else
:
fi
((i++))
done < $(printf "/tmp/%s" $USER)
rm $(printf "/tmp/%s" $USER)
done < $USERS
rm $PIDFILE
rm $AUTHFILE
rm $USERS
done
Suggestions:
journalctl -r is very expensive. Limit journalctl to time since last search.
Line with USER=$(...) and PID=$(...). Replace printf and sed commands, with single awk command.
Research pgrep and pkill commaonds.
Replace file $PIDFILE $AUTHFILE $USERS with array variables (research readarray command).
While loop over $AUTHFILE could be implemented as loop over bash array.
While loop over $USERS (including internal loop) could be implemented as loop over bash array.
curl command might be very expensive. You do not check the response from each curl request. Run curl in background and if possible in parallel for all users.
Kind SO members could assist more, if you put sample lines from $AUTHFILE in the questions as sample input line.

Shell script to kill a process in specific time with process name and time as input

I need a shell script to kill a particular running process after a specific time by getting the process name and time as input.
I'm using centos machine i've tried the script but couldn't complete on killing the process on particular timing.
#!/bin/bash
read -p 'Process: ' name
read -p 'Timecontrol: ' time
ps -ef | grep $name | awk '{print $5}'
pkill -9 "$name"
the expected output to be kill the process in specific time which will be given as input.
With this script you can kill the running process at a specific time by giving the process name and the time.
[Note: The input for time must be in seconds only, i.e 120 for 2 minutes]
#!/bin/bash
LOG=/tmp/kill.log
EXIT_ON_KILL=true
read -p 'Process: ' name
read -p 'killat: ' time
PID=$(ps -ef | grep $name | awk '{print $2}')
ps -ef | grep $name | awk '{print $2}' &>>$LOG
if [ $? -eq 0 ]; then
echo -e "\n The process details "
ps -p $PID
else
echo -e "\nInvalid Process Name"
fi
current=$(date +"%T")
killat=$(date -d "+"$time" seconds" "+%T")
echo -e "\nCurrent time $current \nThe time target is $killat"
while :
do
current=$(date +"%T")
echo $current
if [ "${killat}" == "${current}" ]
then
kill -9 $PID &>>$LOG
if [ $? -eq 0 ]; then
echo "Process $name have been successfully killed"
if [ $EXIT_ON_KILL == true ];then
exit 0
fi
else
echo -e "\nFailed to Kill process $name"
echo -e "\nMay be Invalid Process Name"
exit 1
fi
fi
sleep 2
done
Sample Input:
Process: xxxx
Killat: 120
You can use a cron job to terminate the process in specific date and time.
If you have to use a script:
#!/bin/bash
read -p 'Process: ' name
read -p 'Timecontrol: ' timecontrol
while :
do
now="$(date --date="$(date +%H:%M)" +%s)"
x="$(date --date="$timecontrol" +%s)"
if [ $now == $x ]
then
ps -ef | grep $name | awk '{print $5}'
fi
done
Note:
Remove # if you plan to run this script forever.
The script will not kill the process at that particular time which you want to give it as an input as the script will run and die it will not wait for a specific time.
You can tickle this in two ways.
Loop but again it will run in the foreground
Cronjob
#!/bin/bash
EXIT_ON_KILL=true
read -p 'Process: ' name
read -p 'Timecontrol: ' timecontrol
while :
do
current_time=$(date +"%T")
echo $current_time "control "
current_time=$(echo $current_time | cut -d':' -f1-2)
if [ "${timecontrol}" == "${current_time}" ]
then
echo "killing process"
kill -9 $(ps -ef | grep $name | awk '{print $2}')
if [ $? -eq 0 ]; then
echo "Process killed having name $name and having $pid"
if [ $EXIT_ON_KILL == true ];then
exit 0
fi
else
echo "Failed to Kill process having name $name"
exit 1
fi
fi
sleep 2
done
awk '{print $2}' this return PID in ubuntu, you have to check-in Centos if it returns PID if not then change it to awk '{print $5}'
So you can run with
./kill_process.sh
Process: node
Timecontrol: 00:21
With Cron job, you do not need to pass time, just pass the name of the process and the script will run on the specified time and will kill the process.
#!/bin/bash
EXIT_ON_KILL=true
p_name=$1
kill -9 $(ps -ef | grep $p_name | awk '{print $2}')
if [ $? -eq 0 ]; then
echo "Process killed having name $p_name and having $pid"
if [ $EXIT_ON_KILL == true ];then
exit 0
fi
else
echo "Failed to Kill process having name $p_name"
exit 1
fi
Create-cron-job-on-CentOS
0 0 * * * /path_to_script/kill_process.sh node
This will kill process every-day-at-midnight

Check if a program in a specific path is running

I am trying to check if a process is running with the code below:
SERVICE="./yowsup/yowsup-cli"
RESULT=`ps aux | grep $SERVICE`
if [ "${RESULT:-null}" = null ]; then
echo "not running"
else
echo "running"
fi
But it keeps echoing it is running although it is not. I realized that the grep itself comes as a result and that is the issue.
How can I skip the grep and just check for the process?
Use pgrep:
if pgrep "$SERVICE" >/dev/null 2>&1 ; then
echo "$SERVICE is running"
fi
or:
if pgrep -f "/path/to/$SERVICE" >/dev/null 2>&1 ; then
echo "$SERVICE is running"
fi
NOTE:
pgrep interprets its argument as a regular expression. As a result, paths containing regex characters will likely fail to match or produce false positives (e.g. pgrep -f /home/user/projects/c++/application/executable won't work as expected due to +). This issue can be worked around by escaping the characters in question (e.g. pgrep -f /home/user/projects/c\+\+/application/executable)
pgrep -f <pattern> matches the specified pattern against the whole command line of running processes. As a result, it will match paths appearing as arguments of other processes (e.g. run nano /usr/bin/sleep in one terminal and pgrep -f /usr/bin/sleep in another -> pgrep reports the pid of nano since it contains /usr/bin/sleep as an argument in its command line). To prevent these kind of false positives, prefix the pattern with a caret (^) to force pgrep to only match against the beginning of the command line (e.g. pgrep -f ^/usr/bin/sleep)
For systems where pgrep isn't available you can use:
service="[.]/yowsup/yowsup-cli"
if ps aux | grep -q "$service"; then
echo "not running"
else
echo "running"
fi
[.] in will force grep to not list itself as it won't match [.] regex.
grep -q can be utilized to avoid command substitution step.
Prefer using lowercase variables in shell.
The problem is that grep you call sometimes finds himself in a ps list, so it is good only when you check it interactively:
$ ps -ef | grep bash
...
myaut 19193 2332 0 17:28 pts/11 00:00:00 /bin/bash
myaut 19853 15963 0 19:10 pts/6 00:00:00 grep --color=auto bash
Easiest way to get it is to use pidof. It accepts both full path and executable name:
service="./yowsup/yowsup-cli" # or service="yowsup-cli"
if pidof "$service" >/dev/null; then
echo "not running"
else
echo "running"
fi
There is more powerful version of pidof -- pgrep.
However, if you start your program from a script, you may save it's PID to a file:
service="./yowsup/yowsup-cli"
pidfile="./yowsup/yowsup-cli.pid"
service &
pid=$!
echo $pid > $pidfile
And then check it with pgrep:
if pgrep -F "$pidfile" >/dev/null; then
echo "not running"
else
echo "running"
fi
This is common technique in /etc/init.d start scripts.
The following solution avoids issues with ps + grep, pgrep and pidof (see Advantages below):
# Check if process is running [$1: path to executable]
function is_process_running() {
local path="$1" line
while read -r line; do
[[ "${line}" == "${path}" || "${line}" == "${path} "* ]] && return 0
done < <(ps -e -o command=)
return 1
}
is_process_running "./yowsup/yowsup-cli" && echo "running" || echo "not running"
Explanation:
ps -e -o command= list all processes, only output command line of each process, omit header line
while read -r line; do ... done < <(ps ...) process output produced by ps line by line
[[ "${line}" == "${path}" || "${line}" == "${path} "* ]] check if line matches path exactly -or- path + space + argument(s)
Advantages:
Works for paths containing regex special characters that would trip grep without option -F or pgrep, e.g. /home/user/projects/c++/application/executable (see NOTE in this answer for details)
Avoids issues with ps + grep / pgrep reporting false positives if path appears as argument of some other process (e.g. nano /usr/bin/sleep + pgrep -f /usr/bin/sleep -> falsely reports pid of nano process)
Avoids issues with pidof reporting false positives for processes that are run from PATH (e.g. sleep 60s & + pidof /tmp/sleep -> falsely reports pid of sleep process running from /usr/bin/sleep, regardless of whether /tmp/sleep actually exists or not)
I thought pidof was made for this.
function isrunning()
{
pidof -s "$1" > /dev/null 2>&1
status=$?
if [[ "$status" -eq 0 ]]; then
echo 1
else
echo 0
fi
)
if [[ $(isrunning bash) -eq 1 ]]; then echo "bash is running"; fi
if [[ $(isrunning foo) -eq 1 ]]; then echo "foo is running"; fi
## bash
## function to check if a process is alive and running:
_isRunning() {
ps -o comm= -C "$1" 2>/dev/null | grep -x "$1" >/dev/null 2>&1
}
## example 1: checking if "gedit" is running
if _isRunning gedit; then
echo "gedit is running"
else
echo "gedit is not running"
fi
## example 2: start lxpanel if it is not there
if ! _isRunning lxpanel; then
lxpanel &
fi
## or
_isRunning lxpanel || (lxpanel &)
Note: pgrep -x lxpanel or pidof lxpanel still reports that lxpanel is running even when it is defunct (zombie); so to get alive-and-running process, we need to use ps and grep
current_pid="$$" # get current pid
# Looking for current pid. Don't save lines either grep or current_pid
isRunning=$(ps -fea | grep -i $current_pid | grep -v -e grep -e $current_pid)
# Check if this script is running
if [[ -n "$isRunning" ]]; then
echo "This script is already running."
fi
SERVICE="./yowsup/yowsup-cli"
RESULT=`ps aux | grep $SERVICE|grep -v grep`
if [ "${RESULT:-null}" = null ]; then
echo "not running"
else
echo "running"
fi

Regarding PID Shell Script

I am calling another shell script testarg.sh within my main script.
the logfiles of testarg.sh are stored in $CUSTLOGS in the below format
testarg.DDMONYY.PID.log
example: testarg.09Jun10.21165.log
In the main script after the testarg process gets completed i need to grep the log file for the text "ERROR" and "COMPLETED SUCCESSFULLY".
How do i get the PID of the process and combine with DDMONYY for grepping. Also i need to check whether file
exists before grepping
$CUSTBIN/testarg.sh
$CUSTBIN/testarg.sh
rc=$?
if [ $rc -ne 0 ]; then
return $CODE_WARN
fi
You may background testarg.sh, which puts its pid into $!, and then wait for it:
#! /bin/bash
...
$CUSTBIN/testarg.sh &
LOGFILE=testarg.$(date +%d%b%y).$!.log # testarg.09Jun10.12345.log
wait $!
# ... $? is set as you expect ...
[ -f $LOGFILE ] && grep {pattern} $LOGFILE
...
If you can modify testarg.sh and it doesn't otherwise output anything, just change it to output its log file with a line like:
echo testarg.$(date +%blah).$$.log
then use:
fspec=$($CUSTBIN/testarg.sh)
in your parent.
Alternatively, you can provide a wrapper function to do the work:
#!/bin/bash
function fgpid() {
"$#" &
pid=$!
ps -ef | grep ${pid} | sed 's/^/DEBUG:/' >&2 # debugging
wait ${pid}
echo ${pid}
}
fspec=testarg.$(date +%d%b%y).$(fgpid sleep 5).log
echo ${fspec}
This produces:
pax> ./qq.sh
DEBUG:pax 2656 2992 con 15:27:00 /usr/bin/sleep
testarg.09Jun10.2656.log
as expected.
Or this if you think your executable may output something. This one stores the PID into a variable:
#!/bin/bash
function fgpid() {
"$#" &
pid=$!
ps -ef | grep ${pid} | sed 's/^/DEBUG:/' >&2 # debugging
wait ${pid}
}
fgpid sleep 5
fspec=testarg.$(date +%d%b%y).${pid}.log
echo ${fspec}
There are two simple ways to get the PID of some process you've just spawned.
One would be to modify the program being spawned (the subprocess) to have it write its PID to a file. You'd then read it therefrom with something like:
$CUSTBIN/testarg.sh
TSTARGSPID=$(cat /var/run/custbin.testarg.pid)
Another more elegant method would be:
$CUSTBIN/testarg.sh &
TSTARGSPID=$!
wait
# Do stuff with PID and output files

Resources