grep for multiple strings in a single line - shell

I need to check if any of the strings "Added/Changed/Fixed/Deleted" are in a commit log message. I am writing an svn precommit hook, and the expected commit comment should have one of these 4 strings in the message.
The code I am using is as below
REPOS=$1
TXN=$2
SVN="/usr/bin/svn";
SVNLOOK="/usr/bin/svnlook";
$SVNLOOK log "$REPOS" -t "$TXN" | \
grep "[a-zA-Z0-9]" > /dev/null
GREP_STATUS=$?
if [ $GREP_STATUS -ne 0 ]
then
"${ECHO}" "No Log comments present" >> "${LOG}"
echo "Your commit has been blocked because you didn't give any log message" 1>&2
echo "Please write a log message describing the purpose of your changes and" 1>&2
echo "then try committing again. -- Thank you" 1>&2
exit 1
fi
In the above code,
$SVNLOOK log "$REPOS" -t "$TXN"
will give me the commit message that the user has entered. Now I have to check for the presence of any of the strings "Added, Changed, Fixed, Deleted" in the message.
That is,
if (any of the above 4 strings are not present),
exit 1
I tried with
$($SVNLOOK log -t "$TXN" "$REPOS" | grep -q "Added\|Changed\|Fixed\|Deleted"|)
but it doesnt seem to be working.

Use grep -e option (multiple times) like this:
grep -e Added -e Changed -e Fixed -e Deleted
otherwise go to the regex route:
grep --regexp=Added|Changed|Fixed|Deleted

To used alternation you need Extended Regexp:
grep -qE 'Added|Changed|Fixed|Deleted'
Or:
egrep -q 'Added|Changed|Fixed|Deleted'

Remove the backslashes and use egrep I also recommend -i for case insensitive matching:
egrep -q -i "added|changed|fixed|deleted"

Related

How to easily find out which part of a bash/zsh pipeline failed due to `set -o pipefail`?

I have a bash/zsh command with multiple pipes | that fails when using set -o pipefail. For simplicity assume the command is
set -o pipefail; echo "123456" | head -c2 | grep 5 | cat
How do I quickly find out which command is the first to fail and why? I know I can check the exit code, but that doesn't show which part of the pipeline failed first.
Is there something simpler than the rather verbose check of building up the pipeline one by one and checking for the first failing exit code?
Edit: I removed the contrived code example I made up as it confused people about my purpose of asking. The actual command that prompted this question was:
zstdcat metadata.tsv.zst | \
tsv-summarize -H --group-by Nextclade_pango --count | \
tsv-filter -H --ge 'count:2' | \
tsv-select -H -f1 >open_lineages.txt
In bash, use echo "${PIPESTATUS[#]}" right after the command to get the exit status for each component in a space separated list:
#!/bin/bash
$ set -o pipefail; echo "123456" | head -c2 | grep 5 | cat
$ echo ${PIPESTATUS[#]}
0 0 1 0
Beware zsh users, you need to use the lower case pipestatus instead:
#!/bin/zsh
$ set -o pipefail; echo "123456" | head -c2 | grep 5 | cat
$ echo $pipestatus
0 0 1 0
In fish you can also simply use echo $pipestatus for the same output.
${PIPESTATUS[#]} right after is the answer you were looking for. However, I want to advise on the first example. It's a good habit to anticipate error, so instead of testing after you should have check the path prior everything.
if [ -d "/nonexistent_directory" ]; then
# here pipe shouldn't fail to grep
# ...unless there's something wrong with "foo"
# ...but "grep" may be a failure if the pattern isn't found
if ! ls -1 "/nonexistent_directory" | grep 'foo' ; then
echo "The command 'grep foo' failed."
# else
# echo "The pipeline succeeded."
fi
else
echo "The command 'ls /nonexistent_directory' failed."
fi
Whenever possible, avoid greping ls output in script, that' fragile...

Shell script if condition not working in SVN pre-commit hook

need some help
I was trying to enhance the pre-commit hook to see if I can ignore parsing the commit message if there is a certain key ("!ignore") in the commit message
The if conditions before this one , like to check if there are empty commit messages works. But this if condition somehow is not working.
Ok when I do a commit with message "SMARTCOMMITTEST" which does not contain my check key "!ignore", the commit succeeds which means the If condition below never executed or did not execute as expected. So trying to understand what is wrong wit it.
SMARTCOMMIT=1
$SVNLOOK log -t "$TXN" "$REPOS" | grep "!ignore" | wc -c || SMARTCOMMIT=0
if [ $SMARTCOMMIT = 0];
then
echo "Please use !ignore if you dont want to use smart commits in your commit message." 1>&2
exit 1
fi
Thanks a lot to Etan for some tips...
I changed the condition like the other if condition in the comments and then it worked
SMARTCOMMIT=$($SVNLOOK log -t "$TXN" "$REPOS" | grep "!ignore" | wc -c)
if [ "$SMARTCOMMIT" = "0" ]; then
echo "Please use !ignore if you dont want to use smart commits in your commit message." 1>&2
exit 1
fi
This one worked fine..
#David W .. I now have a situation to check multiple conditions in the same if
SMARTCOMMIT=$($SVNLOOK log -t "$TXN" "$REPOS" | grep '!ignore' | wc -c)
COMMITMESSAGENOREVIEW=$($SVNLOOK log -t "$TXN" "$REPOS" | grep '#comment' | wc -c)
COMMITMESSAGEWITHREVIEW=$($SVNLOOK log -t "$TXN" "$REPOS" | grep '+review' | wc -c)
if [ "$SMARTCOMMIT" = "0" -a "$COMMITMESSAGENOREVIEW" = "0" -a "COMMITMESSAGEWITHREVIEW" = "0" ]; then
echo "Please use #comment or +review to enable smart commits or !ignore to not use smart commits." 1>&2
exit 1
fi
I tried as given in the link here but still I dont see the if condition getting executed at all. Can you help me with this now?
The if statement can look at the exit output of a command, so you don't need to set an environment variable depending upon the output:
shopt extglob > /dev/null && extglob=1
if ! $SVNLOOK log -t "$TXN" "$REPOS" | grep -q '!ignore'
then
echo 'Please use "!ignore" if you dont want to use smart commits in your commit message.' 1>&2
exit 1
fi
This runs $SVNLOOK log -t "$TXN" "$REPOS" | grep -q '!ignore'
The -q is quiet mode. grep either exits zero if the string exists or non-zero if it doesn't exist. By putting this in the if !, the then clause executes only if !ignore was found.
You have to make sure that !ignore is surrounded by single quotes, or you put a \ in front of the !. Bash has a csh type history mechanism in there, and there's no way to turn it off. Anytime the shell sees !, it assumes it has to do with the process ID number.

Pass command via variable in shell

I have following code in my build script:
if [ -z "$1" ]; then
make -j10 $1 2>&1 | tee log.txt && notify-send -u critical -t 7 "BUILD DONE"
else
make -j10 $1 2>&1 | tee log.txt | grep -i --color "Error" && notify-send -u critical -t 7 "BUILD DONE"
fi
I tried to optimize it to:
local GREP=""
[[ ! -z "$1" ]] && GREP="| grep -i --color Error" && echo "Grepping for ERRORS"
make -j10 $1 2>&1 | tee log.txt "$GREP" && notify-send -u critical -t 7 "BUILD DONE"
But error thrown in make line if $1 isn't empty. I just can't figure out how to pass command with grep pipe through the variable.
Like others have already pointed out, you cannot, in general, expect a command in a variable to work. This is a FAQ.
What you can do is execute commands conditionally. Like this, for example:
( make -j10 $1 2>&1 && notify-send -u critical -t 7 "BUILD DONE" ) |
tee log.txt |
if [ -z "$1" ]; then
grep -i --color "Error"
else
cat
fi
This has the additional unexpected benefit that the notify-send is actually conditioned on the exit code of make (which is probably what you intended) rather than tee (which I would expect to succeed unless you run out of disk or something).
(Or if you want the notification regardless of the success status, change && to just ; -- I think this probably makes more sense.)
This is one of those rare Useful Uses of cat (although I still feel the urge to try to get rid of it!)
You can't put pipes in command variables:
$ foo='| cat'
$ echo bar $foo
bar | cat
The linked article explains how to do such things very well.
As mentioned in #l0b0's answer, the | will not be interpreted as you are hoping.
If you wanted to cut down on repetition, you could do something like this:
if [ $(make -j10 "$1" 2>&1 > log.txt) ]; then
[ "$1" ] && grep -i --color "error" log.txt
notify-send -u critical -t 7 "BUILD DONE"
fi
The inside of the test is common to both branches. Instead of using tee so that the output can be piped, you can just indirect the output to log.txt. If "$1" isn't empty, grep for any errors in log.txt. Either way, do the notify-send.

Execute command determined by output of previous one (i.e., only if there was some output)

Should be fairly simple to answer:
Let's say I wanted to execute a command determined by the output of a previous one in Bash:
curl http://website.com 2> /dev/null | grep -i "test" --count | <MY-COMMAND>
What I need: <MY-COMMAND> should only execute if grep had some matches (at least 1).
How can I achieve that?
Also, please feel free to add matching tags, I couldn't come up with any
ifne utility ("run a program if the standard input is not empty") from Jeoy Hess's moreutils package will serve you.
A description of it:
a command that would run the following
command if and only if the standard
input is not empty. I often want this
in crontabs, as in:
find . -name core | ifne mail -s "Core files found" root
Do you need the output of grep to be piped to your command? The answer is simpler if you do not. In that case since grep's return code is success only if it finds a match, you can use && or if:
curl http://website.com 2> /dev/null | grep -q -i "test" && <MY-COMMAND>
if curl http://website.com 2> /dev/null | grep -q -i "test"; then
<MY-COMMAND>
fi
The && operator is a shorthand way of performing an if-else check. It is a short-circuiting operator, which means that the right hand side will only be executed if the left hand side fails.
If you need to pipe the output to your command then you'll need to save the output to a temporary file, test for a match, and then execute your command:
if curl http://website.com 2> /dev/null | grep -i "test" > /tmp/grep.txt; then
<MY-COMMAND> < /tmp/grep.txt
fi
curl http://website.com 2> /dev/null | grep -i "test" && <MY-COMMAND>
From the grep man page: "the exit status is 0 if selected lines are found and 1 otherwise"
The command after && is executed only if the previous command returned exit status 0.
curl http://www.google.com 2>/dev/null | grep window -i -c && echo "this is a success"

How can I find the names of log files with 'error' on the last line?

I am trying to find the id's of jobs that end in error.
When I use
for i in *log
do tail -n 1 $i | grep error
echo $i
done
It seems to find error on the last line of each file, even for files that don't have errors on the last line, and returns all of the filenames with
STOP fatal_error
out1.log
STOP fatal_error
out2.log
STOP fatal_error
out3.log
....
even though
grep error out1.log
returns nothing
Alternatively, is there an easier way to get a list of the jobs that end in error? I tagged with qsub because I use qsub to submit the jobs
You need an if statement so that you only echo the filename when the grep succeeds:
for i in *.log
do
if tail -n 1 $i | grep error > /dev/null
then
echo $i
fi
done
Also, redirect the grep results to /dev/null so it doesn't appear in the output.
You want to say
do tail -n 1 $i | grep error
not
do tail -n 1 *.log | grep error
Otherwise, you are checking every log file at every iteration and will always get the same results.
Your logic is incorrect.
echo $i will have the listing of the file, not the grep output

Resources