98

I want to write in a bash script a piece of code that checks if a program is already running. I have the following in order to search whether bar is running

 foo=`ps -ef | grep bar | grep -v grep`

The

 grep -v grep

part is to ensure that the "grep bar" is not taken into account in ps results

When bar isn't running, foo is correctly empty. But my problem lies in the fact tha the script has

 set -e

which is a flag to terminate the script if some command returns an error. It turns out that when bar isn't running, "grep -v grep" doesn't match with anything and grep returns an error. I tried using -q or -s but to no avail.

Is there any solution to that? Thx

C.d.
  • 9,932
  • 6
  • 41
  • 51
George Kastrinis
  • 4,924
  • 4
  • 29
  • 46
  • Note that `set -e` is not bash-specific, but rather applies to any POSIX-compatible shell (`sh` etc.) – myrdd Sep 04 '18 at 12:46

6 Answers6

88

Sure:

ps -ef | grep bar | { grep -v grep || true; }

Or even:

ps -ef | grep bar | grep -v grep | cat
Sean
  • 29,130
  • 4
  • 80
  • 105
  • 11
    The first is better in that it also works with "-o pipefail", another good fail-fast setting for bash similar to "-e". – Jaka Jančar Jul 13 '16 at 07:49
  • 7
    This is working well if `grep` returns 1 for not having matches, but what happens if it returns 2 (e.g. error)? – André Fratelli Dec 23 '16 at 19:12
  • 5
    As _André Fratelli_ stated, using `grep $options || true` ignores real errors! I've proposed a new answer: https://stackoverflow.com/a/49627999/307637 – myrdd Apr 08 '18 at 10:19
  • **Observation:** the approach using "`... | grep -v grep | cat`" will NOT prevent a script from failing if it has `set -o pipefail` in place, though it may suffice with `set -e`. The "`... | { grep -v grep || true; }`" approach will work with `set -o pipefail`, but for a cleaner solution that specifically tests for a lack of grep matches, see @myrdd's answer. – Trutane Jan 18 '20 at 01:18
  • @Trutane, which answer did you mean? Please include the link next time since usernames can change/get deleted. – Leponzo Dec 29 '22 at 15:13
40

Short answer

Write

ps -ef | grep bar | { grep -v grep || test $? = 1; }

if you are using set -e.

If you use bash's pipefail option (set -o pipefail), remember to apply the exception handling (||test) to every grep in the pipeline:

ps -ef | { grep bar || test $? = 1; } | { grep -v grep || test $? = 1; }

In shell scripts I suggest you to use the ”catch-1-grep“ (c1grep) utility function:

c1grep() { grep "$@" || test $? = 1; }

Explained

grep's exit status is either 0, 1 or 2: [1]

  • 0 means a line is selected
  • 1 means no lines were selected
  • 2 means an error occurred

grep can also return other codes if it's interrupted by a signal (e.g. 130 for SIGINT).

Since we only want to ignore exit status 1, we use test to suppress that specific exit status.

  • If grep returns 0, test is not run.
  • If grep returns 1, test is run and returns 0.
  • If grep returns any other value, test is run and returns 1.

In the last case, the script will exit immediately due to set -e or set -o pipefail. However, if you don't care about grep errors at all, you can of course write

ps -ef | grep bar | { grep -v grep || true; }

as suggested by Sean.


[additional] usage in shell scripts

In shell scripts, if you are using grep a lot, I suggest you to define an utility function:

# "catch exit status 1" grep wrapper
c1grep() { grep "$@" || test $? = 1; }

This way your pipe will get short & simple again, without losing the features of set -e and set -o pipefail:

ps -ef | c1grep bar | c1grep -v grep

FYI:

  • I called it c1grep to emphasize it's simply catching exit status 1, nothing else.
  • I could have called the function grep instead (grep() { env grep "$@" ...; }), but I prefer a less confusing and more explicit name, c1grep.

[additional] ps + grep

So if you want to know how to avoid grep -v grep or even the | grep part of ps|grep at all, take a look at some of the other answers; but this is somewhat off-topic imho.


[1] grep manpage

myrdd
  • 3,222
  • 2
  • 22
  • 22
15

A good trick to avoid grep -v grep is this:

ps -ef | grep '[b]ar'

That regular expression only matches the string "bar". However in the ps output, the string "bar" does not appear with the grep process.


In the days before I learned about pgrep, I wrote this function to automate the above command:

psg () { 
    local -a patterns=()
    (( $# == 0 )) && set -- $USER
    for arg do
        patterns+=("-e" "[${arg:0:1}]${arg:1}")
    done
    ps -ef | grep "${patterns[@]}"
}

Then,

psg foo bar

turns into

ps -ef | grep -e '[f]oo' -e '[b]ar'
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • Heh, I've done exactly this. It works because the expression *does* match **bar** but it *does not* match **b.ar** – DigitalRoss Jul 01 '11 at 17:44
  • 5
    @grok12, what shows up in the ps output will be `grep [b]ar` and the *regular expression* `[b]ar` cannot match the *string* `[b]ar` -- the regex will match exactly 3 chars while the string contains 5 chars – glenn jackman Jul 01 '11 at 18:58
10

Why ask ps to provide massive amounts of output with -ef if you only are going to throw away 99% of it? ps and especially the GNU version is a swiss army knife of handy functionality. Try this:

ps -C bar -o pid= 1>/dev/null

I specify -o pid= here just because, but in fact it's pointless since we throw away all of stdout anyway. It would be useful if you wanted to know the actual running PID, though.

ps automatically will return with a non-zero exist status if -C fails to match anything and with zero if it matches. So you could simply say this

ps -C bar 1>/dev/null && echo bar running || echo bar not running

Or

if ps -C bar 1>/dev/null ; then
    echo bar running
else
    echo bar not running
fi

Isn't that simpler? No need for grep, not twice or even once.

sorpigal
  • 25,504
  • 8
  • 57
  • 75
  • You are answering slightly different question here. The main one is about `grep` that can be used with any input, not only from `ps`. – Victor Yarema Jul 09 '18 at 09:37
1

Try to make so:

ps auxw | grep -v grep | cat

cat returns always 0 and ignores exit code of grep

Vladyslav Savchenko
  • 1,282
  • 13
  • 10
  • 2
    It is not a good idea to ignore all exit codes since there can be other error except `not found` one. This solution will also fail in case of modes caused by either `set -e` or `set -o pipefail` commands. – Victor Yarema Jul 09 '18 at 09:34
  • Also, `| cat` makes your pipeline slower, because it's adding an extra process that needs to read and write all data. `|| :` or `|| true` would be better. – Charles Duffy Jul 20 '23 at 21:14
1
foo=`ps -ef | grep bar | grep -v grep` || true
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329