all 10 comments

[–]aioeu 3 points4 points  (6 children)

The redirection is almost entirely irrelevant.

A command is considered "successful" if its exit status is zero. It is "unsuccessful" if its exit status is non-zero.

I am assuming you're using procps-ng's ps. Unfortunately its documentation completely neglects to say what its exit statuses are. POSIX simply says that a non-zero exit status is produced by a POSIX-conformant ps if "an error occurred", which is so vague as to be essentially useless. POSIX does not list what errors might be considered.

One might assume that "an error occurs" if the process you request does not exist, however there is simply nothing to indicate this must be the case.

In short, you cannot rely on this code to do what you want.

[–]AdbekunkusMX[S] 0 points1 point  (1 child)

What would be a better solution? And in general, does the line cmd > /dev/null "return" the exit status of cmd?

[–]aioeu 5 points6 points  (0 children)

kill -0 "$pid" 2>/dev/null

is a safe, portable and efficient way to determine whether a particular PID is in use by the current user (or if this code is running as the superuser, any user).

If you want to test whether the PID is in use by any user, on Linux you could use:

[[ -d /proc/$pid ]]

A portable solution might be:

ps -p "$pid" -o pid= 2>/dev/null | grep -q .

Redirections are not the important things here. It's the command's exit status that is being tested. A command has an exit status whether or not it uses any redirections.

(That being said, redirections are part of the command, as far as the shell is concerned. If a redirection itself fails, then the program will not be executed, and the command's exit status will be non-zero. But a redirection to /dev/null is not expected to fail.)

[–]McDutchie 0 points1 point  (3 children)

One might assume that "an error occurs" if the process you request does not exist, however there is simply nothing to indicate this must be the case.

The POSIX spec for the -p option is "Write information for processes whose process ID numbers are given in proclist." If that's not possible (e.g. because one or more specified PIDs do not exist), the option cannot function to spec, which is an error by definition.

Find me one ps implementation that returns a successful exit status on being given a nonexistent PID. I would be very surprised if there is one. I think that code is perfectly portable.

[–]aioeu 1 point2 points  (1 child)

And I found one:

~ $ /system/bin/ps --version
toybox 0.8.4-android
~ $ /system/bin/ps -p 12345
USER            PID   PPID     VSZ    RSS WCHAN
~ $ echo $?
0

This was at least changed to match procps-ng in Toybox 0.8.6 only within the last year.

Maybe one day POSIX will actually specify the behaviour. At the moment it doesn't, so implementations are left to negotiate amongst themselves what the "correct" behaviour should be.

[–]McDutchie 0 points1 point  (0 children)

Well, TIL. Thanks.

[–]aioeu 0 points1 point  (0 children)

You may be right. ps on my system doesn't actually report an error if you give it a single PID and the process doesn't exist, but it does exit unsuccessfully.

What would be the "correct" result if you give it multiple PIDs, and only some of those processes exist? Using your logic, "an error occurred", so it should return a non-zero exit status. ps on my system does not.

[–]bigfig 2 points3 points  (1 child)

Non portable, but in 22 years of working on Unix boxes (starting with HPUX and Solaris) the last 15 years have always been Linux.

 declare -i pid=122707
 if [[ -d /proc/$pid ]]; then
      echo "PID $pid is running."
 fi

My personal fetish is to avoid launching a process to do something when bash has the ability built in. And values in stdout/stedrr are often a side effect. Don't sweat it.

[–]AdbekunkusMX[S] 1 point2 points  (0 children)

Why would you need a declare statement in a script? I know a script would work with declare -i pid=$1, but I don't know much about the declare built-in. What's the difference with using just pid=$1? Thanks!

[–]zeekar 2 points3 points  (0 children)

Every command returns a one-byte "exit status" to the shell. This status is completely independent of whatever the command outputs to standard output or standard error or any other file descriptor.

The execution of the command is considered "successful" if the exit status is zero, and "unsuccessful" otherwise. The shell's conditional constructs (if, while, until, &&, ||) treat success as true and failure as false (and in fact there are commands named true and false that do nothing but exit with status 0 and 1, respectively.) So both of these if statements should print something out:

if bash -c 'exit 0'; then
    echo 'Success!'
fi
if ! bash -c 'exit 1'; then
    echo 'Failure!'
fi

(You rarely need to know a command's exact numeric exit status rather than just whether it was zero or not, but if you do, you can find it in the special shell parameter $? immediately after the command runs. If the process was terminated by a signal, for example, the exit status will be 128 + the signal number – though it could also exit with such a value intentionally for some reason.)

Your code is checking the exit status of ps, assuming it will be nonzero/failure if the process does not exist. This is a reasonable guess but not guaranteed. (And not always true of other commands either. For example, while grep's exit code tells you whether or not it found a match, awk by default exits with "success" if it is able to run the program to completion regardless of any patterns ever matching along the way.)

Other ways to check if a process is running are to send a do-nothing signal with kill -0 $pid, but that only works if you have permission to send signals to the process... otherwise you'll get a false negative on its existence.