120

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?

6
  • 9
    kill $(jobs -p) seems easier.
    – jw013
    Jul 19, 2012 at 22:00
  • I would prefer to kill jobs individually, if possible. (I might have misunderstood your comment, though) Jul 19, 2012 at 22:12
  • 1
    for pid in $(jobs -p); do kill $pid; done?
    – jw013
    Jul 19, 2012 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. Jul 19, 2012 at 23:52
  • I was entering the command incorrectly, kill $(jobs -p) words and looks very correct to me too. Ready to accept. Jul 20, 2012 at 0:31

13 Answers 13

184

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.

7
  • 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 8, 2013 at 23:46
  • 1
    I feel like I should know this, but how does the '$' work here?
    – fersarr
    Jan 8, 2015 at 17:21
  • 1
    @fersarr Here you go
    – jw013
    Jan 8, 2015 at 21:27
  • 1
    On CentOS, my prompt is waiting for more input > Nov 13, 2018 at 19:18
  • 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.
    – user313992
    May 28, 2020 at 14:49
26

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

jobs -p | xargs kill
5
  • 3
    This has the same exact effect though , it prints the help and exits with code 123
    – cat
    Jun 7, 2017 at 20:51
  • 1
    The the command works fine on OSX but doesn't work on Debian Jun 8, 2017 at 12:23
  • This works great on CentOS 6 Nov 13, 2018 at 19:19
  • 1
    jobs -p | xargs -rn10 kill will do better better if jobs -p does not return any PIDs. Note that -r option is GNU extension.
    – NarūnasK
    Feb 1, 2019 at 16:27
  • 4
    As mentioned above, the -r is the short format for the --no-run-if-empty GNU extension to xargs which instructs it to not run the command if stdin has no data. Jun 10, 2019 at 11:09
14

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
2
  • 6
    What shell are you using? What does echo $0 show? What does help jobs show? The output illustrated is not POSIX-compliant; any implementation of jobs should have a -p option with the intended behavior.
    – Wildcard
    Jul 5, 2019 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. Jul 6, 2019 at 2: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.

1
  • 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, 2021 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.

5
  • 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, 2020 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. Jan 27, 2020 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, 2020 at 15:35
  • @jarno Yes, I understand. The question is does the question asker? Jan 27, 2020 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, 2020 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.

1
  • 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.
    – user313992
    May 28, 2020 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

1

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.

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

2
  • Which shell do you run so that you get those +/- in $(jobs -p)?
    – jarno
    Jan 25, 2020 at 20:32
  • zsh is a very common shell that has a non-POSIX-compliant jobs output, which does include plus and minus.
    – AndrewF
    Sep 10, 2020 at 21:59
0

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)
1
  • 1
    Many answers have already discussed jobs -p and specifically $(jobs -p), and a couple have mentioned the KILL signal, so you aren’t adding anything new. Jun 16, 2022 at 4:30
0

Do the same, faster:

jobs -p | xargs -P100 kill

Runs 100 simultaneous kill threads

0
kill_all_jobs () {
local jobs=$(jobs -r | sed -En 's/^\[([0-9]+)\].*$/%\1/p')
[ -n "$jobs" ] && kill $jobs
return 0
}
  • jobs will also list jobs that have finished since the last time jobs were checked,¹ -r will exclude them.²
  • The sed script will do the following:
    • Match each line against the regular expression using the “extended” syntax (-E) ^\[([0-9]+)\].*$ ([digits]anything),
    • if it does not match, print nothing (-n),³
    • if it does, replace (s/) the match (which will be the entire line because of ^…$) with %\1 (%digits) and do print the resulting line (/p). (Bash’s builtin kill will recognize %jobnumber as referring to that job.)
  • We only run kill if $jobs is not empty, because running kill with no arguments would be an error. (Note that we cannot use xargs -r kill here because xargs can only invoke /usr/bin/kill, not Bash’s builtin kill, and the former does not know about the shell’s jobs.)
  • The return 0 prevents the function from returning 1 if there were no jobs, which would usually cause the shell to exit in set -e mode.

¹ Note that “checking jobs” does not necessarily mean “invoking jobs”: Apparently, an interactive shell will usually do so before each “primary prompt” (prompt for the next command), as casually mentioned by the documentation for Bash’s set -b. (I couldn’t find another mention of this in man bash…) To verify this, run sleep 1 & and hit Enter again after at least one second: [1]+ Done sleep 1 appears before the prompt. And to verify that this line is indeed included in the output of jobs without -r, compare the results of output=$(jobs)Enterecho "$output" and output=$(jobs -r)Enterecho "$output" (each at least one second after sleep 1 &). (Mysteriously, the line will appear both before the next prompt and in the variable with output=$(jobs), but only in the file with jobs > somefile.)
² It will also exclude jobs paused with SIGSTOP, but I haven’t found a way to list them without including finished jobs, and sending those jobs any signal other than SIGKILL or SIGCONT won’t do anything in the short-term, anyway. …Nope, using Bash’s builtin kill in conjunction with %jobnumber seems special in this regard: It can terminate a paused sleep inf & even with SIGTERM. Maybe it also sends SIGCONT to a paused job? I couldn’t find this documented anywhere…
³ Every non-empty line printed by jobs should match this pattern as per POSIX, but just in case…

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .