0

I need help for the following problem: I'd like to kill all instances of a program, let's say xpdf. At the prompt the following works as intended:

$ ps -e | grep xpdf | sed -n -e "s/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p" | xargs kill -SIGTERM

(the sed-step is required to extract the PID).

However, there might be the case that no xpdf-process is running. Then it would be difficult, to embed the line into a script, because it aborts after it immediately with a message from kill. What can I do about it?

I tried (in a script)

#!/bin/bash
#
set -x
test=""
echo "test = < $test >"
test=`ps -e | grep xpdf | sed -n -e "s/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p"`
echo "test = < $test >"
if [ -z "$test" ]; then echo "xpdf läuft nicht";
else echo "$test" | xargs -d" " kill -SIGTERM
fi

When running the script above I get

$ Kill_ps
+ test=
+ echo 'test = <  >'
test = <  >
++ ps -e
++ grep xpdf
++ sed -n -e 's/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p'
+ test='21538
24654
24804
24805'
+ echo 'test = < 21538
24654
24804
24805 >'
test = < 21538
24654
24804
24805 >
+ '[' -z '21538
24654
24804
24805' ']'
+ xargs '-d ' kill -SIGTERM
+ echo '21538
24654
24804
24805'
kill: failed to parse argument: '21538
24654
24804
24805

Some unexpected happens: In test there are more PIDs then processes

At the prompt:

$ ps -e | grep xpd
21538 pts/3    00:00:00 xpdf.real
24654 pts/2    00:00:00 xpdf.real

When running the script again, the 24* PIDs change.

So here are my questions:

  1. Where do the additional PIDs come from?
  2. What can I do to handle the situation, in which no process I want to kill is running (or why does xargs not accept echo "$test" as input)? (I want my script not to be aborted)

Thanks in advance.

GIC
  • 3
  • 2
  • `xargs -r kill -SIGTERM`? `xargs -r`: `--no-run-if-empty`. BTW, why are you not using [`killall`](https://www.unix.com/man-pages.php?section=0&os=Linux&query=killall)/[`pkill`](https://www.unix.com/man-pages.php?section=0&os=Linux&query=pkill) instead? Any complications there? – anishsane Dec 30 '20 at 13:11
  • One additional pid comes from your `grep` command, which has the term `xpdf`. Your script will work (not optimal) if you use `xargs -n 1 kill -TERM`. – Marco Dec 30 '20 at 14:10

2 Answers2

0

You can use pkill(1) or killall(1) instead of parsing ps output. (Parsing human-readable output for scripting is not recommended. This is an example.)

Usage:

 pkill xpdf
 killall xpdf # Note that on solaris, it kills all the processes that you can kill. https://unix.stackexchange.com/q/252349#comment435182_252356
 pgrep xpdf # Lists pids
 ps -C xpdf -o pid=  # Lists pids

Note that tools like pkill, killall, pgrep will perform similar to ps -e | grep. So, if you do a pkill sh, it will try to kill sh, bash, ssh etc, since they all match the pattern.

But to answer your question about skipping xargs from running when there is nothing on the input, you can use -r option for (GNU) xargs.

   -r, --no-run-if-empty
          If the standard input does not contain any nonblanks, do not run the command.
          Normally, the command is run once even if there is no input.
          This option is a GNU extension.
anishsane
  • 20,270
  • 5
  • 40
  • 73
  • When we put `ls` aside, what is not recommended is to iterate over an output of command that could contains spaces with a `for i in $(cmd)` loop. However `cmd|while read -r` or `awk` are perfectly fine and optimized for parsing column based or CSV outputs. I've wrote about that here: https://stackoverflow.com/a/19607361/2900196 – Idriss Neumann Dec 30 '20 at 13:36
  • BTW, I agree for using `killall` or `pkill` for this case – Idriss Neumann Dec 30 '20 at 13:38
  • And POSIX `xargs` doesn't provide a `--no-run-if-empty` extensions so I'll keep my own answer just in case :) – Idriss Neumann Dec 30 '20 at 13:59
  • And as I said in the answer, `-r` is a GNU extension. FWIW, I did not downvote your answer. So, feel free to not delete it. – anishsane Dec 30 '20 at 15:21
  • Avoid parsing when direct tools are available. Also, the output of `cmd` must be reliable and not hackable. e.g. in the case of `ps`, I don't know if the non-printable characters in the process name are ALWAYS printed as `?` or It is just `ps` installed on my system. Also, I have seen the `ps` output columns to be different on some "non-standard" unix-like distros (cough vmware cough). So, I try not to rely on such parsing. – anishsane Dec 30 '20 at 15:24
  • @anishane Most of time using `ps -ef` is pretty the same output on BSD based systems and GNU systems. But yes, gnu/linux users have sometimes the bad habbit to use `ps aux` instead. I understand your point of using the right tool when it exists though but I just reacted on the statement "Parsing human-readable output for scripting is not recommended."... not always :) ) – Idriss Neumann Dec 30 '20 at 15:46
  • Warning: on some OSes, `killall` kills processes matching the specified command, but on [some it kills *all* processes](https://docs.oracle.com/cd/E86824_01/html/E54764/killall-1m.html) (normally used as part of system shutdown). Be sure you [know which type of system you're on before using it](https://xorl.wordpress.com/2011/10/19/admin-mistakes-solaris-killall/). – Gordon Davisson Dec 30 '20 at 19:53
  • @GordonDavisson, yes. I just realized after posting the answer. I will update it. – anishsane Dec 31 '20 at 03:20
  • @IdrissNeumann, I meant, try to make a habit of finding the right tool, before you use such a long pipeline for the job. Especially, don't try to parse human-readable output. I would not have said this if it were machine-readable output, like csv. I am saying this because I have done similar mistakes. Once I wanted to find the domain id of the VM running on xen. I did a long, non-robust pipeline to parse `xl list`; and a senior pointed me to `xl domid vmname`. – anishsane Dec 31 '20 at 03:33
  • Whoever downvoted, please leave a comment for the reason. If I am following some bad coding practice, I can improve. Thank you. – anishsane Jan 12 '21 at 05:21
-1

This answer is for the case where killall or pkill (suggested in this answer) are not enough for you. For example if you really want to print "xpdf läuft nicht" if there is no pid to kill or applying kill -SIGTERM because you want to be sure of the signal you send to your pids or whatever.

You could use a bash loop instead of xargs and sed. It's pretty simple to iterate over CSV/column outputs:

count=0
while read -r uid pid ppid trash; do
  kill -SIGTERM $pid
  (( count++ ))
done < <(ps -ef|grep xpdf)
[[ $count -le 0 ]] && echo "xpdf läuft nicht"

There is a quicker way using pgrep (the previous one was for illustrate how to iterate over column-based outputs of commands with bash):

count=0
while read -r; do
  kill -SIGTERM $REPLY
  (( count++ ))
done < <(pgrep xpdf)
[[ $count -le 0 ]] && echo "xpdf läuft nicht"

If you're version of xargs can provide you the --no-run-if-empty you can still use it with pgrep (like suggested in this answer), but it's not available on the BSD or POSIX version. It is my case on macOS for example...

awk could also do the trick (with only one command after the pipe):

ps -ef|awk 'BEGIN {c="0"} {if($0 ~ "xpdf" && !($0 ~ "awk")){c++; system("kill -SIGTERM "$2)}} END {if(c <= 0){print "xpdf läuft nicht"}}'
Idriss Neumann
  • 3,760
  • 2
  • 23
  • 32
  • @GIC glad I could help. If your problem is solved, do not hesitate to pick the answer you want and mark at solved. – Idriss Neumann Dec 30 '20 at 22:48
  • A slight corrigendum: while opens a subshell, so count =0 after the while loop (see [link](https://stackoverflow.com/questions/13726764/while-loop-subshell-dilemma-in-bash). So, that part does not work – GIC Jan 02 '21 at 11:02
  • @GIC Yes, you perfectly right. So I've updated my answer with another way to loop (with `while read`) over a command output without opening a subshell. You can keep `cmd|while read` if you don't need to keep the count. – Idriss Neumann Jan 02 '21 at 12:44