Prevent grep from exiting in case of nomatch - bash

Prevent grep returning an error when input doesn't match. I would like it to keep running and not exit with exit code: 1
set -euo pipefail
numbr_match=$(find logs/log_proj | grep "$name" | wc -l);
How could I solve this?

In this individual case, you should probably use
find logs/log_proj -name "*$name*" | wc -l
More generally, you can run grep in a subshell and trap the error.
find logs/log_proj | ( grep "$name" || true) | wc -l
... though of course grep | wc -l is separately an antipattern;
find logs/log_proj | grep -c "$name" || true

I don't know why you are using -e and pipefail when you don't want to have this behaviour. If your goal is just to treat exit code 2 (by grep) as error, but exit code 1 as no-error, you could write a wrapper script around grep, which you
call instead of grep:
#!/bin/bash
# This script behaves exactly like grep, only
# that it returns exit code 0 if there are no
# matching lines
grep "$#"
rc=$?
exit $((rc == 1 ? 0 : rc))

Related

Different output of command substitution

Why does adding | wc -l alters the result as in the following?
tst:
#!/bin/bash
pgrep tst | wc -l
echo $(pgrep tst | wc -l)
echo $(pgrep tst) | wc -l
$ ./tst
1
2
1
and even
$ bash -x tst
+ wc -l
+ pgrep tst
0
++ pgrep tst
++ wc -l
+ echo 0
0
++ pgrep tst
+ echo
pgrep and subshells can have weird interactions, but in this case that's just a red herring; the actual cause is missing double-quotes around the command substitution:
$ cat tst2
#!/bin/bash
pgrep tst | wc -l
echo "$(pgrep tst | wc -l)"
echo "$(pgrep tst)" | wc -l
$ ./tst2
1
2
2
What's going on in the original script is that in the command
echo $(pgrep tst) | wc -l
pgrep prints two process IDs (the main shell running the script, and a subshell created to handle the echo part of the pipeline). It prints each one as a separate line, something like:
11730
11736
The command substitution captures that, but since it's not in double-quotes the newline between them gets converted to an argument break, so the whole thing becomes equivalent to:
echo 11730 11736 | wc -l
As a result, echo prints both IDs as a single line, and wc -l correctly reports that.
The command substitution induces an additional process that has tst in its name, which is included in the input to wc -l.

grep -c kills script when no match using set -e

Basic example:
#!/bin/bash
set -e
set -x
NUM_LINES=$(printf "Hello\nHi" | grep -c "How$")
echo "Number of lines: ${NUM_LINES}" # never prints 0
Output:
++ grep -c 'How$'
++ printf 'Hello\nHi'
+ NUM_LINES=0
If there are matches, it prints the correct number of lines. Also grep "How$" | wc -l works instead of using grep -c "How$".
You can suppress grep's exit code by running : when it "fails". : always succeeds.
NUM_LINES=$(printf "Hello\nHi" | grep -c "How$" || :)

What is wrong in this Shell script?

I keep getting unary operator expected. It seems PRC is not being assigned the value.
PRC=`ps -ef | grep test | wc -l`
if [ ${PRC} -eq 1 ]
then
echo "Congrats"
Note that ps -ef | grep test will usually include the grep process in the output, which you probably don't want. A "clever trick" to avoid this is to match for the string "test" with a regular expression that is not the simple string "test":
$ ps -ef | grep test
jackman 27787 24572 0 09:53 pts/2 00:00:00 grep --color=auto test
$ ps -ef | grep test | wc -l
1
versus
$ ps -ef | grep '[t]est'
(no output)
$ ps -ef | grep '[t]est' | wc -l
0
I do this often enough that I wrote this bash function psg (for "ps grep"):
psg () {
local -a patterns=()
(( $# == 0 )) && set -- $USER # no arguments? vanity search
for arg do
patterns+=("-e" "[${arg:0:1}]${arg:1}")
done
ps -ef | grep "${patterns[#]}"
}
You could also just use
pgrep -f test
Don't forget your closing "fi":
PRC=`ps -ef | grep test| wc -l`
if [ "${PRC}" -eq 1 ]
then
echo "Congrats"
fi
You didn't mention what shell, but this works in bash.
Save a process by using the -c (count) option to grep:
PRC=`ps -ef | grep -c test`
Be advised your pipeline includes in the count the grep command itself, so as you mentioned in your comment above your count is most likely misleading as it is only counting itself. Instead, use this:
PRC=`ps -ef | grep -c [t]est`
This will match commands with "test" in them but not the grep command itself. This is because this is using a regular expression that matches a word starting with a "t". Your command starts with a square bracket so it won't match itself. Resist doing a "grep test | grep -v grep" which is sloppy and just unnecessarily uses a process.

system command returns strange value

I have this script
#!/bin/bash
npid=$(pgrep -f procname | wc -l)
echo $npid
if [ $npid -lt 3 ];then
service procname start;
fi
When procname is running, it works fine showing the correct number for $npid. It fails when there isn't any procname running. It should return zero, but it returns npid=3, exactly 3. The same problem I see for ps auxw | grep procname | grep -v grep | wc -l as well.
Something trivially wrong I just couldn't figure out, any suggestions ?
* EDIT
# This returns nothing if therisn't a process name poopit running
pgrep -f poopit
# In the case when no process running, below returns zero if typed on a bash cmd line
pgrep -f poopit | wc -l
# If running,
pgrep -f poopit | wc -l
17
# If running, the script $npid shows
19

Prevent grep returning an error when input doesn't match

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
Sure:
ps -ef | grep bar | { grep -v grep || true; }
Or even:
ps -ef | grep bar | grep -v grep | cat
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
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'
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.
foo=`ps -ef | grep bar | grep -v grep` || true
Try to make so:
ps auxw | grep -v grep | cat
cat returns always 0 and ignores exit code of grep

Resources