2

UPDATE:

I have changed the grep $1 part to grep '$1' (while I was trying to mean grep "$1") in the script and this time I got the

kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

message (instead of the Terminated: 15 message). I don't understand what's going on.

QUESTION:

I have written a simple shell script named mykill.

mykill:

#!/bin/sh

kill `ps -A | grep $1 | grep -v 'grep' | grep -Eom 1 '^[0-9]+'`

However, there is a weird behavior. When I write the line:

kill `ps -A | grep process_name | grep -v 'grep' | grep -Eom 1 '^[0-9]+'`

manually on bash, if nothing comes up as the output of ps -A | grep process_name, I get the following:

kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

If something comes up, the command executes correctly and terminates silently.

Now, if I run the script by executing the mykill file, if something comes up as the output of ps -A | grep process_name, the script executes correctly and terminates silently, which is the same behavior as executing the command manually.

But if nothing comes up as the output of ps -A | grep process_name, I don't get the message about the usage of the kill command. Instead, I get:

Terminated: 15

I have also checked out the return codes. After I try to terminate a non existent process by manually writing the command on the shell, echo $? gives 1. However, after I try to terminate a non existent process by calling the script, echo $? gives 143.

What's going on here? Why am I observing different behaviors when executing the same command by manually writing it on the shell, vs executing it within a shell script?

NOTE: Both sh and my working shell are bash.

BONUS: Could my shell script be written in a more efficient and/or elegant way, using only POSIX utilities? If so, how?

  • You can write your script more efficiently by using pkill. – Stephen Kitt Oct 3 '16 at 12:16
  • @StephenKitt Updated the BONUS part to allow only POSIX utilities. – Utku Oct 3 '16 at 12:19
  • You're on some BSD system, right? – Stéphane Chazelas Oct 3 '16 at 12:29
  • @StéphaneChazelas Yes, I'm on Darwin. – Utku Oct 3 '16 at 12:29
4

A portability note. The output format for ps -A is unspecified by POSIX for non Unix-conformant systems (like FreeBSD) (you'll notice that the output format sections and the description of the -f option are all tagged XSI in the specification), so you can't really post-process it reliably portably.

For instance, with the ps from procps on Linux, it will output, PID TTY TIME CMD columns (where CMD is the process name, not command args) while on FreeBSD it outputs PID TT STAT TIME COMMAND (with COMMAND being the args).

Given your usage of grep -v grep, I suppose you're expecting the latter or at least that ps -A outputs the arguments of the command that the process executed as opposed to just the process name (usually derived from the file name of the last execute command or the first (0th) argument).

If your grep is intended to grep on the command arguments only, you should use:

ps -A -o pid= -o args=

whose output is specified by POSIX.

Now, your problem is that mykill is killing itself because the mykill foo matches foo.

Another problem is that mykill grep would not kill anything.

Here, you could do:

#! /bin/sh -
PATTERN=${1?} export PATTERN
trap '' TERM # ignore SIGTERM for the shell and its children
ps -A -o pid= -o args= | awk '$0 ~ ENVIRON["PATTERN"] {
  system("kill " $1); exit}'

(note that POSIX doesn't specify the path of the POSIX sh utility nor the she-bang mechanism, so /bin/sh may not be a POSIX shell. In practice though, she-bang is supported on most POSIX systems and /bin/sh is either a POSIX sh or the Bourne sh and the above code should work in both).

Though that's not ideal as it always return a true (0) exit status even when no process is found. A better approach would be:

#! /bin/sh -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
ps -A -o pid= -o args= | grep -e "$pattern" | {
  read pid args && kill "$pid"
}

In both cases, we only kill the first matching process as your grep -m 1 approach suggests you want to do.

Now, with trap '' SIGTERM we make sure our processes are not killed which would be OK if we were to kill all the matching processes but since here, we're only killing the first matching one, the problem is that that first one may very well be the one running mykill pattern or grep pattern.

Rather than adding some grep -ve grep -e mykill (which would not be foolproof as it could exclude more processes than intended), you could try and compare the process IDs of the matched processes.

#! /bin/sh -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
             # just in case
psoutput=$(exec ps -A -o pid= -o ppid= -o args=)
printf '%s\n' "$psoutput" | grep -e "$pattern" | {
  while read -r pid ppid args; do
    if [ "$pid" -ne "$$" ] && [ "$ppid" -ne "$$" ]; then
      kill "$pid"
      exit # with the exit status of kill above
    fi
  done
  exit 1 # not found
}

(note that $(...) and read -r are POSIX but not Bourne).

Or using ksh93, bash, zsh or yash (none of which are POSIX commands), that is a shell with builtin regular expression matching:

#! /bin/bash -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
             # just in case
psoutput=$(exec ps -A -o pid= -o ppid= -o args=)
printf '%s\n' "$psoutput" | {
  while read -r pid ppid args; do
    if ((pid != $$ && ppid != $$)) && [[ $args =~ $pattern ]]; then
      kill "$pid"
      exit # with the exit status of kill above
    fi
  done
  exit 1 # not found
}
  • Regarding the output of ps -A, actually I am getting the former version (PID TTY TIME CMD). I couldn't understand why using grep -v grep would suggest the other output format. – Utku Oct 3 '16 at 12:49
  • @Utku, grep -v grep would be to filter out the grep foo line (that grep foo being started by mykill foo) that would match foo. But if the CMD field only contains the process name (so only grep, not grep foo), then that wouldn't make sense. Does the CMD column contain the process name or the command arguments on your system? – Stéphane Chazelas Oct 3 '16 at 13:04
  • Oh, yes of course. But my CMD column includes both process name and command arguments. That is, it includes the full command line that is used to start the process. – Utku Oct 3 '16 at 13:07
  • @Utku, yes, that's the point. See my edited answer where I hope it's clearer. – Stéphane Chazelas Oct 3 '16 at 13:35
  • 1
    @Utku, it exits the script with an error if $1 is not set. You can also specify the error message like ${1?need a pattern}. In the bash manual, pay attention to the Omitting the colon results in a test only for a parameter that is unset sentence. – Stéphane Chazelas Oct 3 '16 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.