rouble
rouble

Reputation: 18191

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?

Upvotes: 80

Views: 48725

Answers (11)

Thibault LE PAUL
Thibault LE PAUL

Reputation: 171

 #!/bin/bash
 exec {safe2}>&2 2>/dev/null {fdevent}< <(
   sh -c 'echo $$; exec inotifywait -mqe CLOSE_WRITE /tmp' 2>&$safe2
   ) 2>&$safe2
 read -u $fdevent pidevent
 trap "$(trap -p EXIT)"$'\n'"kill $pidevent" EXIT

 grep -m1 somevent <&$fdevent

Illustrate a particular case because the own error file descriptor of the process substitution control is not accessible otherwise.

The same exec statement successively save error file descriptor, replace error by /dev/null to be inherited by process substitution, assign a new file descriptor to the process substitution output, and restore the original error file descriptor.

Inside the process substitution itself, the original error file descriptor is made active, but the undesired "process complete" message will be flushed to /dev/null.

Given that, on exit, inotifywait monitor will be silently killed.

However, depending on the process to be killed, SIGPIPE or some signal other than SIGTERM causes a silent exit wihout any effort and reflects a meaningful logic :

 #!/bin/bash
 exec {fdevent}< <(sh -c 'echo $$; exec inotifywait -mqe CLOSE_WRITE /tmp')
 read -u $fdevent pidevent
 ## work with file descriptor, sigpipe exception when done
 kill -PIPE $pidevent

Upvotes: 0

James Z.M. Gao
James Z.M. Gao

Reputation: 646

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]'

Upvotes: 0

AhmadAssaf
AhmadAssaf

Reputation: 3664

You can use set +m before to suppress that. More information on that here

Upvotes: 1

user184968
user184968

Reputation:

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.

Upvotes: 7

pix
pix

Reputation: 5070

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).

Upvotes: 37

phily
phily

Reputation: 11

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

Upvotes: 1

Mike Q
Mike Q

Reputation: 7327

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"

Upvotes: 0

bbbco
bbbco

Reputation: 1530

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

Upvotes: 6

jilles
jilles

Reputation: 11232

Add at the start of the function:

trap 'exit 0' TERM

Upvotes: 2

Mark Edgar
Mark Edgar

Reputation: 4817

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

Upvotes: 86

rouble
rouble

Reputation: 18191

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.

Upvotes: 5

Related Questions