117

Is there a more compact form of killing background jobs than:

for i in {1..5}; do kill %$i; done

Also, {1..5} obviously has a hard-coded magic number in it, how can I make it "N" with N being the right number, without doing a:

$(jobs | wc -l)

I actually use \j in PS1 to get the # of managed jobs, is this equivalent?

Braiam
  • 35,380
  • 25
  • 108
  • 167
Robottinosino
  • 5,271
  • 12
  • 39
  • 51
  • 8
    `kill $(jobs -p)` seems easier. – jw013 Jul 19 '12 at 22:00
  • I would prefer to kill jobs individually, if possible. (I might have misunderstood your comment, though) – Robottinosino Jul 19 '12 at 22:12
  • 1
    `for pid in $(jobs -p); do kill $pid; done`? – jw013 Jul 19 '12 at 22:27
  • 3
    @jw013 It's not only easier, it's actually correct (please post it as an answer), unlike a solution based on counting the lines of the output of `jobs` which only works if the jobs happen to be numbered consecutively. Oh, and “kill jobs individually” is meaningless: passing multiple PIDs to the `kill` command does exactly the same thing as passing them separately. – Gilles 'SO- stop being evil' Jul 19 '12 at 23:52
  • I was entering the command incorrectly, kill $(jobs -p) words and looks very correct to me too. Ready to accept. – Robottinosino Jul 20 '12 at 00:31
  • Incidentally, if you want to kill a fixed range of job ids, you can do e.g. `kill %{1..5}`. – augurar Aug 20 '15 at 23:34

12 Answers12

178

To just kill all background jobs managed by bash, do

kill $(jobs -p)

Note that since both jobs and kill are built into bash, you shouldn't run into any errors of the Argument list too long type.

jw013
  • 50,274
  • 9
  • 137
  • 141
  • 2
    Also for posterity, what bahamat thinks is the way to do it in `zsh` disqualifies them as any authority on the topic. – peth Mar 08 '13 at 23:46
  • 1
    I feel like I should know this, but how does the '$' work here? – fersarr Jan 08 '15 at 17:21
  • 1
    @fersarr [Here](http://unix.stackexchange.com/q/147420/9537) you go – jw013 Jan 08 '15 at 21:27
  • @bahamat That doesn't actually work since the PID may be in field 2 or 3 depending on whether the job is one of %+ or %- or not. What works is `kill %${(k)^jobdirs}`, which is indeed longer; if you need to list the PIDs then you can use the even longer `${${jobstates#*:*:}%%=*}`. – Gilles 'SO- stop being evil' Jan 28 '16 at 10:06
  • 1
    On CentOS, my prompt is waiting for more input `>` – Janac Meena Nov 13 '18 at 19:18
  • From @jw013's link: `It's very similar to the backticks \`\`. It's called command substitution (posix specification) and it invokes a subshell.`, and `Unlike the backticks, the $(...) form can be nested.` – Luke Griffiths Dec 06 '19 at 09:42
  • 6
    This answer is incorrect. This will not kill all the processes from the background jobs, but only the process group leaders. Try with `sleep 3333 | sleep 3333 &`; after `kill $(jobs -p)`, `jobs` will still show the background job as running -- only one of the 2 `sleep` processes will be killed. –  May 28 '20 at 14:49
27

Use xargs instead of the $(jobs -p) subcommand, because if jobs -p is empty then the kill command will fail.

jobs -p | xargs kill
Taavi
  • 3
  • 4
brunocascio
  • 387
  • 3
  • 3
13

I guess depending on what output jobs -p gives, the solution could be slightly different. In my case

$ jobs -p
[1]  - 96029 running    some job
[2]  + 96111 running    some other job

Therefore, doing the following is no good.

$ jobs -p | xargs kill
kill: illegal process id: [1]

On the other hand, running kill $(jobs -p) does work but entails a lot of error messages, since the non-PID strings get passed to kill as well.

Therefore, my solution is to grep the PID first and then use xargs, as follows:

$ jobs -p | grep -o -E '\s\d+\s' | xargs kill
Fanchen Bao
  • 247
  • 2
  • 4
  • 5
    What shell are you using? What does `echo $0` show? What does `help jobs` show? The output illustrated is [not POSIX-compliant](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/jobs.html); *any* implementation of `jobs` should have a `-p` option with the intended behavior. – Wildcard Jul 05 '19 at 22:16
  • 7
    Thanks for pointing this out. I am using zsh, and that is the reason for the strange output. I switched to bash and `jobs -p` did only output PID. I've just learned that zsh is not completely POSIX-compliant. – Fanchen Bao Jul 06 '19 at 02:56
7

@pizdelect point is what brought me here. Jobs spawned using pipes have multiple pids. jobs -p is only going to provide the first.

for j in $(jobs | awk '{gsub("[^0-9]","",$1);printf "%%%s\n", $1}');do kill $j;done

If you want to get them all, you have to use the job ID.

Frobozz
  • 391
  • 2
  • 5
  • 1
    Finally, this works and sends kill signal to all background process groups, sending it to all background processes. Parsing `jobs` output to find out jobs numbers, then passing job numbers to `kill` for _jobspec_ is the way to go. Can be shortened a bit to `for j in $(jobs | awk '{gsub("[^0-9]","",$1);print $1}'); do kill %$j; done`. Thank you. – KamilCuk Apr 16 '21 at 17:43
3

I prefer to check if there's any jobs that exist before killing them - this way the script won't fail if there's nothing running.

It's also shorter to type. Throw this in your .bash_profile:

function killjobs () {
    JOBS="$(jobs -p)";
    if [ -n "${JOBS}" ]; then;
        kill -KILL ${JOBS};
    fi
}

Then run:

killjobs

To kill any jobs running.

mikemaccana
  • 1,723
  • 1
  • 12
  • 16
  • 1
    Why do you use the KILL signal? If you use Bash, you could use `-r` with `jobs` to list only running jobs. – jarno Jan 25 '20 at 20:41
  • Because the question asker said to 'kill all background jobs' not 'terminate all background jobs'. I appreciate that it's possible they may have not been speaking exactly though, especially as they send a TERM in their example. – mikemaccana Jan 27 '20 at 15:29
  • You should use KILL signal usually only, if nothing else works, because the processes can not do any cleanup tasks with the signal. – jarno Jan 27 '20 at 15:35
  • @jarno Yes, I understand. The question is does the question asker? – mikemaccana Jan 27 '20 at 15:47
  • 1
    It is still possible that some of the processes are terminated when running the function before calling kill. – jarno Jan 28 '20 at 12:33
3

In bash and zsh I would use something like this (after considering the comment from pizdelect about the process group):

jobs -p | extract_pids | xargs --no-run-if-empty kill -TERM -- || true

where

extract_pids() {
    awk '{
        if (NF == 1) { print -$1 }
        else if (NF > 1) {
            if ($2 == "+" || $2 == "-") { print -$3 }
            else { print -$2 }
        }
    }'
}

As far as I tested jobs -p returns the PIDs in bash, f.i.:

$ jobs -p
41659

but can have these formats in zsh, f.i.:

$ jobs -p
[1]    206 running    sleep 10
[2]  - 208 running    sleep 10
[3]  + 210 running    sleep 10

extract_pids tries to handle these (4) cases.

Note: checking the processes with jobs -p can return PIDs, which are already gone at the time xargs kill is invoked, so we ignore errors with a trailing || true.

András
  • 51
  • 3
  • 2
    That will only kill the job (process group) leaders, not the background *jobs*. Try with `sleep 3333 | sleep 3333 &` -- only one of the sleep processes will be killed and `jobs` will still show the background job as running. You should negate the pids if you want to kill entire jobs, not just job leaders. Yes this is a problem with the other answers, too. –  May 28 '20 at 14:34
2

I have several backgrounded compound commands I want to terminate gracefully, by sending SIGINT to each process, on macOS. None of the other solutions worked properly for that, so I came up with this:

jobs -p | xargs -n1 pkill -SIGINT -g

jobs -p lists background processes started by the current shell.

xargs -n1 executes pkill once for each job.

pkill -SIGINT -g sends SIGINT (same as ctrl+c) to all processes in the process group.

2

The accepted answer by @jw013 works in most situations, except when a job has background jobs since only the group leader is killed. (@pizdelect identified this limitation in this comment.)

@pizdelect also pointed out that kill accepts negative PID values to choose the whole process group in this comment.

Furthermore, one might not want to kill Stopped jobs, hence jobs -r might be useful.

Combining all these elements, here's a solution that kills all running jobs, including background jobs:

for i in $(jobs -rp); do kill -- "-$i"; done

Here's a solution that kills all jobs, including background jobs:

for i in $(jobs -p); do kill -- "-$i"; done

Daniel Le
  • 256
  • 2
  • 10
1

Processes can ignore some signals. However, SIGKILL can not be ignored nor caught to do cleanups. To ensure all background jobs managed by bash are killed, try,

kill -9 $(jobs -p)
Shogun
  • 11
  • 2
0

Seems like jobs -p | xargs kill does the job, however it produces some unwanted output piped to kill. Here I am grouping output of jobs -p by having/not having + or - character

function killjobs() {
    JOBS=$(jobs -p)
    echo $JOBS | grep -v "+\|-"| awk '{print $2}' | xargs kill -9
    echo $JOBS | grep "+\|-"| awk '{print $3}' | xargs kill -9
}

And later you can just call killjobs

samvel1024
  • 109
  • 1
0

As others have said already, jobs -p | xargs kill or kill $(jobs -p) is wrong as:

  • jobs -p prints the process group ids, with extra information with zsh
  • kill number kills the process with number as id, not process group. There's not even a guarantee that the process group ids returned by jobs -p will have a corresponding running process by the same number. And even if there is, kill will not kill the other processes in the job / process-group
  • if there's no job in the job table, kill will be run without arguments causing an error.

Here you want to run either kill -- -<pgid> or kill %<jobnumber> for all the jobs.

With bash or other shells that only print the pgid upon jobs -p, you can do:

jobs -p | sed 's/^/-/' | xargs -r kill --

(-r being a GNU extension)

Note that it doesn't work in dash where jobs running in a subshell (because as part of a pipeline) only lists the jobs of that subshell. There, you could instead redirect the output of jobs -p to a temporary file and then run sed|xargs on it, but anyway dash is not really intended to be run interactively.

With zsh:

() { (($#)) && kill %${^@}; } ${(k)jobstates}

Where we pass the list of job numbers (the keys of the $jobstates special associative array) to an anonymous function that runs kill with % prepended to the job number if its number of arguments is non-zero.

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
0

Do the same, faster:

jobs -p | xargs -P100 kill

Runs 100 simultaneous kill threads

Roel Van de Paar
  • 240
  • 2
  • 12