How to run a pkill when invoking a shell to execute a string of commands? - linux

To automate a system administration task, I wrote down the following line of shell code:
bash -c 'pkill -TERM -f java; true'
The problem is that pkill kills the bash immediately after the pkill command executes, and therefore subsequent commands do not have a chance to execute.
Apart from splitting the them into two lines:
bash -c 'pkill -TERM -f java'
bash -c 'true'
Is there any other workaround?

If you want to kill all java processes, simply drop the -f:
bash -c 'pkill -TERM java; true'
If you really also want to kill non-java processes like mplayer "jungle_gremlins_of_java.avi", the typical "solution" is to rewrite the command so that the pattern doesn't match itself:
bash -c 'pkill -TERM -f "[j]ava"; true'

Related

How do I make a "stop" script in bash that closes gnome-terminal tabs that I had previously opened with a different bash script?

I have a script called start.sh:
#!/bin/bash
gnome-terminal --tab -- bash -c "./application1; bash"
gnome-terminal --tab -- bash -c "./application2; bash"
gnome-terminal --tab -- bash -c "./application3; bash"
This script opens three new tabs in the current terminal window and runs a unique application in each one.
Now I want to write stop.sh, which will kill the three application processes and then close the three tabs they were running in. So the first three commands in the script will probably be:
pkill -9 application1
pkill -9 application2
pkill -9 application3
But how do I then close the gnome-terminal tabs I had previously opened in the other script?
Each application has a bash as the parent process, so you should kill the parent as well.
You may find the parent with ps command:
APP1PID=`pgrep application1`
APP1PPID=`ps j $APP1PID | awk 'NR>1 {print $1}'`
kill -9 $APP1PPID
It is sufficient to kill the parent process, it will kill all children as well.

finding the process group id created through setsid

In a shell script, I see that using setsid, we could create a new process group. I am not able to find a reliable way to get the group id after the creation. My requirement is simple, launch a process, and after it is done, clean up any descendant (if any). I dont want to kill the main process, hence I have to wait for the main process to end. After which, I can kill the leftover child processes if I had somehow got the group id. which can be done with kill -- -pgid. The missing piece is how do I get the group id ?
This script is what I came up with finally. Hope this helps someone.
$! will give the pid, and a ps has to be used to find its gid.
there was an extra space in front while using ps,the next line of variable expansion removes the leading space.
Finally after waiting for the main process,it kills the group.
#!/bin/sh -x
setsid "$#" &
pid=$!
gidspace=$(ps -o pgid= $pid)
gid="${gidspace## }"
echo "gid $gid"
echo "waiting"
wait $pid
ps -s $gid -o pid,ppid,pgid,command
kill -- -$gid
I managed to do it with a coproc, and a sleep to ensure we have enough time to read back the pid. This is bash-specific of course, and the only way to avoid using a hackish sleep inside a coproc is to write to a temp file and wait for the command to terminate (no need for coproc then).
Using a coproc
Note that I open filehandle 3 to write the pgid back to the parent shell and close it before executing the command.
#!/bin/bash -x
coproc setsid bash -c 'ps -o pgid= $BASHPID >&3; exec 3>&-; exec "$#" & sleep 1' -- "$#" 3>&1
read -u ${COPROC[0]} gid
echo "gid $gid"
ps -s $gid -o pid,ppid,pgid,command
kill -- -$gid
Using a temp file
To avoid having to pass the temp file to the subshell (and the risk the parent dies and removes it before child writes to it) I again open fh 3 so the children can write its pgid to it.
#!/bin/bash -x
t=$(mktemp)
trap 'rm -f "$t"' EXIT
exec {fh}>"$t"
setsid bash -c 'ps -o pgid= $BASHPID >&3; exec 3>&-; exec "$#" &' -- "$#" 3>&${fh}
read gid <$t
echo "gid $gid"
ps -s $gid -o pid,ppid,pgid,command
kill -- -$gid

pkill returns 255 in combination with another command via remote ssh

When I try to execute pkill on a remote host in combination with another command, it always returns 255, even though both the commands were successful.
Examples
ssh <remoteHost> 'pkill -f xyz' # returns 0 (rightly so when xyz is a process)
ssh <remoteHost> 'source /etc/profile' # returns 0 (rightly so)
But when I run the combination command:
ssh <remoteHost> 'source /etc/profile; pkill -f xyz' # returns 255 - why?
There's something about "pkill" in combination with another command because the following returns zero even though it's a combination:
ssh <remoteHost> 'source /etc/profile; ls' # returns 0
Assume that xyz is running at all times when we try to kill it.
I do not understand this behavior. Why does it return 255 in case 3?
The documentation for the pkill -f option says:
-f
The pattern is normally only matched against the process name. When -f is set, the full command line is used.
So pkill -f xyz will kill any process with "xyz" anywhere on its command line.
When you run ssh <remoteHost> 'source /etc/profile; pkill -f xyz', the remote ssh server will run the equivalent of this on your behalf:
$SHELL -c 'source /etc/profile; pkill -f xyz'
The resulting shell instance is a process with "xyz" in its command line. My guess is that pkill is killing it, and ssh is reporting the killed session as exit code 255, like this:
$ ssh localhost 'kill $$'
$ echo $?
255
It doesn't happen when you just run ssh <remoteHost> 'pkill -f xyz', because some shells like bash will optimize for this case. Instead of running pkill as a subprocess, the shell instance will replace itself with the pkill process. So by the time pkill runs, the shell process with "xyz" on its command line is gone.
You can probably work around this by running pkill like this:
ssh <remoteHost> 'source /etc/profile; exec pkill -f xyz'
If that doesn't work, you can specify the pkill pattern in such a way that it doesn't match the pattern itself. For example:
ssh <remoteHost> 'source /etc/profile; exec pkill -f "[x]yz"'
The pattern [x]yz matches the text "xyz", so pkill will kill processes where the text "xyz" appears. But the pattern doesn't match itself, so pkill won't kill processes where the pattern appears.

Bash script iterate over PID's and kill items

I try to kill all occurrences of a process, what's happen actually an iteration stops after first item, what's wrong here ?
#!/usr/bin/env bash
SUPERVISORCLS=($(pidof supervisorctl))
for i in "${SUPERVISORCLS[#]}"
do
echo $i
exec sudo kill -9 ${i}
done
Before I tried sth like this as solution for restart script, but as well script was not always executed at total always only one if block was executed.?
ERROR0=$(sudo supervisord -c /etc/supervisor/supervisord.conf 2>&1)
if [ "$ERROR0" ];then
exec sudo pkill supervisord
exec sudo supervisord -c /etc/supervisor/supervisord.conf
echo restarted supervisord
fi
ERROR1=$(sudo supervisord -c /etc/supervisor/supervisord.conf 2>&1)
if [ "$ERROR1" ];then
exec sudo pkill -9 supervisorctl
exec sudo supervisorctl -c /etc/supervisor/supervisord.conf
echo restarted supervisorctl
fi
exec replaces your process with the executable that's the argument to it, so you will never execute another statement in your script after it hits an exec. Your process will no longer exist. In the first example your process will no longer be your script it will be kill and pkill in the second.
To fix it, just remove exec from all those lines. It's not needed. When executing a script the shell will execute the commands on every line already, you don't have to tell it to do so.

How to pgrep over ssh, or use pgrep as a larger bash command?

I'd like to run pgrep to find the ID of some process. It works great, except when run as a larger bash command as pgrep will also match it's parent shell/bash process which includes the match expression as part of it command-line.
pgrep sensibly excludes it own PID from the results, but less sensibly, doesn't seem to have an option to exclude its parent process(es).
Anyone come across this and have a good workaround.
Update.
pgrep -lf java || true
works fine, but
bash -c "(pgrep -lf java || true)"
echo 'bash -c "(pgrep -lf java || true)"' | ssh <host>
also identify the parent bash process.
I'm using pgrep as part of a much larger system, which it why the extra madness.
I ran into this issue using python's os.system(command) which executes the command in a subshell.
pgrep does not match itself, but it does match it's parent shell which includes pgrep's arguments.
I found a solution:
pgrep -f the-arguments-here[^\[]
The [^\[] regex assures that it does not match the [ (the beginning of the regex itself) and thus excludes the parent shell.
Example:
$ sh -c "pgrep -af the-arguments-here"
12345 actual-process with the-arguments-here
23456 sh -c pgrep -af the-arguments-here
vs:
$ sh -c "pgrep -af the-arguments-here[^\[]"
12345 actual-process with the-arguments-here
I'm still not seeing why you need the bash -c see part. You should be able to do
ssh <host> pgrep -lf java || true
which would actually run true on the local machine, but you could do
sssh <host> "pgrep -lf java || true"
if you needed true to be on the remote side. Again, assuming your shell accepts that syntax (i.e., is bash)
You're already running everything on the other side of the ssh in a bash shell, so I don't think you need to explicitly invoke bash again--unless your default shell is something else then you may want to consider either changing that or scripting in the appropriate default shell.

Resources