in a sh script, get the pid of a background process - linux

Is it possible to know the pid of the iwevent process in the following bash script:
#!/bin/sh
( iwevent | logger -t IWEVENT ) &
echo the pid is: ???
Note that iwevent run until ctrl-c signal.
FYI.
I run this script in a /etc/network/interfaces "up" statement and I want to kill the running iwevent process in the related "down" statement. My aim is to log wireless events.

Something like this should do the trick:
#!/bin/sh
( { iwevent & printf "The pid is %s\n" $! >&3; } | logger -t IWEVENT ) 3>&1 &
If you need it in a variable, read the output of the above.

check "pidof" function
see this http://en.wikipedia.org/wiki/Pidof
and check the man page: man pidof

Related

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

Get the pid of a command whose output is piped

In order to remotely start a program, log its output and immediately see that log, I'm using this script:
nohup mycommand 2>&1 | tee -a server.log &
Now, I would like to store in a file the pid of the newly started mycommand (whose name is very generic, I can't just use pgrep as there would be a risk of collision).
If I just add echo $! > mycommand.pid I get the pid of tee.
How can I reliably write the pid of mycommand to a file ?
And by the way, why doesn't this give the right pid ?
( nohup mycommand 2>&1 ; echo $! > mycommand.pid ) | tee -a server.log &
OK, this simple variant works :
( nohup mycommand 2>&1 & echo $! > mycommand.pid ) | tee -a server.log &
I'm not sure why the ; didn't work and why I have to use & instead.

Getting BASH command PID

I have this piece of code
#!/bin/bash
streamURL=http://devimages.apple.com/iphone/samples/bipbop/gear4/prog_index.m3u8
(
echo "Debugging for stream: $streamURL";
echo "Starting debugging...";
vlc -vvv --color $streamURL --file-caching=10000 2>&1 | sed "s/^/ `date`/";
) | tee debug.txt &
PROCESS_PID=$!
ps -e | grep $PROCESS_PID
echo " killing process pid: "
echo $PROCESS_PID;
kill -9 $PROCESS_PID
ps -e | grep vlc #still there
My problem is I can't manage to save the "vlc ..." command PID into a variable in order to kill it later. If I move "PROCESS_PID=$!" right after it, it will be empty. Also need the pipe after it for sed. Any suggestions?
You can get the pid by twiddling file descriptors, but it's painful. For example:
{ PID=$({ (
echo foo;
echo bar;
sh -c 'echo $$ >&5; exec echo baz' ) |
tr a o; } 5>&1 1>&3 ); } 3>&1
will assign the pid of 'echo baz' to PID. Replace that echo with your vlc and replace the tr with your sed and you should have a solution.
To try an provide a somewhat simplified explanation of what's going on here, first notice that we are using process substitution to make the assignment to PID. The $() syntax simply takes the command inside the parentheses and assigns to the variable the output of the command. It is important to remember that "output" here means "whatever is printed to file descriptor 1". Inside the sh command, we print a pid to file descriptor 5 and then exec echo. By using exec, that echo has the same pid that the previous echo wrote. Now the echo foo, bar and baz are all writing into the pipe that goes to tr. The output of tr is being redirected to fd 3 (before the edit, this was fd 2. Which file descriptor to use is mostly arbitrary, but modifying 2 is a bad idea in case any errors are generated) and file descriptor 5 is being redirected to fd 1, so that it becomes the "output" of the process substitution that is assigned to PID. Then outside the process substitution, we assign fd 3 to give output where it was originally desired. Hopefully, this paragraph is more explanatory than obfuscating: if confused, look at the code for clarification!
Unfortunately, it gets uglier if you want to run in the background:
{ PID=$({ (
echo foo;
echo bar;
sh -c 'echo $$ >&5; exec 5>&-; exec echo baz' >&3 & ) |
tr a o; } 5>&1 1>&3 ); } 3>&1
Here, you need to close file descriptor 5 to ensure that the process substitution completes.
You can't assign a variable in a subshell and get it back outside it.
In this case, if you kill $! you'll kill tee, which will (AFAIK) send SIGPIPE to the subshell and terminate the whole thing. So there's generally no need for the PID in the subshell.
I'm not sure, but the problem might be that you're nuking the process from orbit with SIGKILL rather than killing it softly with just kill $PID. It might be that tee does not send SIGPIPE in this case, because it doesn't get to clean up after itself.
In other words, just use kill $process_id. Be aware that killing a process is not synchronous - you're just sending it a signal and carrying on. See Kill bash processes “nicely” for details.

Bash: How do I make sub-processes of a script be terminated, when the script is terminated?

The question applies to a script such as the following:
Script
#!/bin/sh
SRC="/tmp/my-server-logs"
echo "STARTING GREP JOBS..."
for f in `find ${SRC} -name '*log*2011*' | sort --reverse`
do
(
OUT=`nice grep -ci -E "${1}" "${f}"`
if [ "${OUT}" != "0" ]
then
printf '%7s : %s\n' "${OUT}" "${f}"
else
printf '%7s %s\n' "(none)" "${f}"
fi
) &
done
echo "WAITING..."
wait
echo "FINISHED!"
Current behavior
Pressing Ctrl+C in console terminates the script but not the already running grep processes.
Write a trap for Ctrl+c and in the trap kill all of the subprocesses. Put this before your wait command.
function handle_sigint()
{
for proc in `jobs -p`
do
kill $proc
done
}
trap handle_sigint SIGINT
A simple alternative is using a cat pipe. The following worked for me:
echo "-" > test.text;
for x in 1 2 3; do
( sleep $x; echo $x | tee --append test.text; ) &
done | cat
If I press Ctrl-C before the last number is printed to stdout. It also works if the text-generating command is something that takes a long time such as "find /", i.e. it is not only the connection to stdout through cat that is killed but actually the child process.
For large scripts that make extensive use of subprocesses the easiest way to ensure the indented Ctrl-C behaviour is wrapping the whole script into such a subshell, e.g.
#!/usr/bin/bash
(
...
) | cat
I am not sure though if this has the exactly same effect as Andrew's answer (i.e. I'm not sure what signal is sent to the subprocesses). Also I only tested this with cygwin, not with a native Linux shell.

bash: silently kill background function process

shell gurus,
I have a bash shell script, in which I launch a background function, say foo(), to display a progress bar for a boring and long command:
foo()
{
while [ 1 ]
do
#massively cool progress bar display code
sleep 1
done
}
foo &
foo_pid=$!
boring_and_long_command
kill $foo_pid >/dev/null 2>&1
sleep 10
now, when foo dies, I see the following text:
/home/user/script: line XXX: 30290 Killed foo
This totally destroys the awesomeness of my, otherwise massively cool, progress bar display.
How do I get rid of this message?
kill $foo_pid
wait $foo_pid 2>/dev/null
BTW, I don't know about your massively cool progress bar, but have you seen Pipe Viewer (pv)? http://www.ivarch.com/programs/pv.shtml
Just came across this myself, and realised "disown" is what we are looking for.
foo &
foo_pid=$!
disown
boring_and_long_command
kill $foo_pid
sleep 10
The death message is being printed because the process is still in the shells list of watched "jobs". The disown command will remove the most recently spawned process from this list so that no debug message will be generated when it is killed, even with SIGKILL (-9).
Try to replace your line kill $foo_pid >/dev/null 2>&1 with the line:
(kill $foo_pid 2>&1) >/dev/null
Update:
This answer is not correct for the reason explained by #mklement0 in his comment:
The reason this answer isn't effective with background jobs is that
Bash itself asynchronously, after the kill command has completed,
outputs a status message about the killed job, which you cannot
suppress directly - unless you use wait, as in the accepted answer.
This "hack" seems to work:
# Some trickery to hide killed message
exec 3>&2 # 3 is now a copy of 2
exec 2> /dev/null # 2 now points to /dev/null
kill $foo_pid >/dev/null 2>&1
sleep 1 # sleep to wait for process to die
exec 2>&3 # restore stderr to saved
exec 3>&- # close saved version
and it was inspired from here. World order has been restored.
This is a solution I came up with for a similar problem (wanted to display a timestamp during long running processes). This implements a killsub function that allows you to kill any subshell quietly as long as you know the pid. Note, that the trap instructions are important to include: in case the script is interrupted, the subshell will not continue to run.
foo()
{
while [ 1 ]
do
#massively cool progress bar display code
sleep 1
done
}
#Kills the sub process quietly
function killsub()
{
kill -9 ${1} 2>/dev/null
wait ${1} 2>/dev/null
}
foo &
foo_pid=$!
#Add a trap incase of unexpected interruptions
trap 'killsub ${foo_pid}; exit' INT TERM EXIT
boring_and_long_command
#Kill foo after finished
killsub ${foo_pid}
#Reset trap
trap - INT TERM EXIT
Add at the start of the function:
trap 'exit 0' TERM
You can use set +m before to suppress that. More information on that here
Another way to do it:
func_terminate_service(){
[[ "$(pidof ${1})" ]] && killall ${1}
sleep 2
[[ "$(pidof ${1})" ]] && kill -9 "$(pidof ${1})"
}
call it with
func_terminate_service "firefox"
Yet another way to disable job notifications is to put your command to be backgrounded in a sh -c 'cmd &' construct.
#!/bin/bash
foo()
{
while [ 1 ]
do
sleep 1
done
}
#foo &
#foo_pid=$!
export -f foo
foo_pid=`sh -c 'foo & echo ${!}' | head -1`
# if shell does not support exporting functions (export -f foo)
#arg1='foo() { while [ 1 ]; do sleep 1; done; }'
#foo_pid=`sh -c 'eval "$1"; foo & echo ${!}' _ "$arg1" | head -1`
sleep 3
echo kill ${foo_pid}
kill ${foo_pid}
sleep 3
exit
The error message should come from the default signal handler which dump the signal source in the script. I met the similar errors only on bash 3.x and 4.x. To always quietly kill the child process everywhere(tested on bash 3/4/5, dash, ash, zsh), we could trap the TERM signal at the very first of child process:
#!/bin/sh
## assume script name is test.sh
foo() {
trap 'exit 0' TERM ## here is the key
while true; do sleep 1; done
}
echo before child
ps aux | grep 'test\.s[h]\|slee[p]'
foo &
foo_pid=$!
sleep 1 # wait trap is done
echo before kill
ps aux | grep 'test\.s[h]\|slee[p]'
kill $foo_pid
sleep 1 # wait kill is done
echo after kill
ps aux | grep 'test\.s[h]\|slee[p]'

Resources