Combine these two IF statements into one? - bash

Any way to combine these two IF statements into one...
if [ -n "$(system_profiler SPPrintersDataType | grep Shared | grep Yes)" ]; then
echo 1
fi
if [ -n "$(system_profiler SPPrintersDataType | grep 'System Printer Sharing: Yes')" ]; then
echo 1
fi

Add || short circuit evaluation in between:
if [ -n ... ] || [ -n ... ]; then ## Something; fi
|| is treated as logical OR (and && is logical AND).
In your case:
if [ -n "$(system_profiler SPPrintersDataType | grep Shared | grep Yes)" ] || [ -n "$(system_profiler SPPrintersDataType | grep 'System Printer Sharing: Yes')" ]; then
echo 1
fi
Just to note, if you use the bash keyword [[, then the following is valid too:
if [[ -n ... || -n ... ]]; then ## Something; fi

[[ -n $(system_profiler SPPrintersDataType | grep Shared | grep Yes)$(system_profiler SPPrintersDataType | grep 'System Printer Sharing: Yes') ]] && echo 1
Note:
You want to echo 1 if either one of the strings is non-empty or if the other one is non-empty. In this case, it is simpler to catenate the strings and look at the result: If the result is non-empty, at least one of the imput strings must be non-empty.
There is no need to use an if statement in this case (although it is not forbidden).
You don't need to quote the argument to -s, if you use [[ ... ]] for testing the string.
When you grep for Shared, should it be allowed that the word Yes appears before the word Shared in the line? If not, it would be simpler to write grep 'Shared.*Yes.
Since you are not interested in the actual output of the grep command, but only in the fact, that it matches, something like this would also work:
{system_profiler SPPrintersDataType|grep -q 'Shared.*Yes} || {system_profiler SPPrintersDataType|grep -Fq 'System Printer Sharing: Yes'} && echo 1
Finally, assuming that the system_profiler command produces the same output in both invocations, the code could be simplified to:
{system_profiler SPPrintersDataType|grep -Eq 'Shared.*Yes|System Printer Sharing: Yes'} && echo 1
This basically says: If there is a line in system_profiler which contains Shared...Yes OR a line containing System Printer Sharing Yes, then echo 1. You need the -E in inorder to get the | to work in the regexp pattern.
Admittedly, all these suggestions mean that you get only one 1 being echoed, if the condition is fulfilled, while in your original solution, you get two 1 being echoed, if both conditions are fulfilled. Therefore, my solution is not exactly equivalent to yours. However, since you explicitly said that you wanted to combine the cases, I think this is acceptable.

I don't know how the output of your system_profiler looks, so going a bit on guesswork here. If the Shared and Yes are always in the same order within a line, you can grep for them together with
grep 'Shared.*Yes'
and you can grep for both of your expressions in one pass with
grep 'Shared.*Yes\|System Printer Sharing: Yes'
You can then write your command as
system_profiler SPPrintersDataType \
| grep -q 'Shared.*Yes\|System Printer Sharing: Yes' \
&& echo 1
Note that we use grep -q to suppress output, as we're only interested in the return code.
Note also that if both of the strings are present, we only output one 1 - I'm guessing that's what you want, but I mention it as it is a difference from your script.

Related

Search for value and print something if found (BASH)

I have the following list:
COX1
COX1
COX1
COX1
COX1
Cu-oxidase
Cu-oxidase_3
Cu-oxidase_3
Fer4_NifH
and I want to search if COX1 and Cu-oxidase is in the list, I want to print xyz, if Cu-oxidase_3 and Fer4_NifHis in the list too (independent if the first two are in the list, then it should print abc.
This is what I could script so far:
if grep 'COX1' file.txt; then echo xyz; else exit 0; fi
but it is of course incomplete.
Any solution to that?
ideally my output would be:
xyz
abc
Awk lets you easily search for multiple regular expressions and print something else than the matched string itself. (grep can easily search for multiple patterns, too, but it will print the match or its line number or file name, not some arbitrary string.)
The following assumes that you have a single token per line. This assumption makes the script really simple, though it would also not be hard to support other scenarios.
awk '{ a[$1]++ }
END { if (("COX1" in a) && ("Cu-oxidase" in a)) print "xyz";
if (("Cu-oxidase_3" in a) && ("Fer4_NifH" in a)) print "abc" }' file.txt
This builds an associative array of each token (actually the first whitespace-separated token on each line) and then at the end, when it has read every line in the file, checks whether the sought tokens exist as keys in the array.
Performing a single pass over the input file is a big win especially if you have a large input file and many patterns. Just for completeness, the syntax for performing multiple passes with grep is very straightforward;
if grep -qx 'COX1' file.txt && grep -qx 'Cu-oxidase' file.txt
then
echo xyz
fi
which can be further abbreviated to
grep -qx 'COX1' file.txt && grep -qx 'Cu-oxidase' file.txt && echo xyz
Notice the -x switch to require the whole line to match (otherwise the regex 'Cu-oxidase' would also match on the Cu-oxidase_3 lines).
Above is a very verbose way to achieve this. There are ways to write the same with less ifs and less greps, but I really wanted to show you the logic:
you run a grep command, check for its return value with $?, and finally acts on the conditions.
# default values
HAS_COX1=0
HAS_CUOX=0
HAS_CUO3=0
HAS_FER4=0
# run silently grep
grep -q 'COX1' file.txt
# check for return value and set variable accordingly
if [ $? -eq 0 ]; then HAS_COX1=1; fi
# same as above
grep -q 'Cu-oxidase' file.txt
if [ $? -eq 0 ]; then HAS_CUOX=1; fi
grep -q 'Cu-oxidase_3' file.txt
if [ $? -eq 0 ]; then HAS_CUO3=1; fi
grep -q 'Fer4_NifH' file.txt
if [ $? -eq 0 ]; then HAS_FER4=1; fi
if [ $HAS_COX1 -eq 1 ]; then
if [ $HAS_CUOX -eq 1 ]; then
echo 'xyz'
exit 0
fi
fi
if [ $HAS_CUO3 -eq 1 ]; then
if [ $HAS_FER4 -eq 1 ]; then
echo 'abc'
exit 0
fi
fi
echo 'None of the checks where matched'
exit 1
Beware: this code is untested, so there might be bugs ☺
The code isn't perfect, as it cannot print both 'xyz' and 'abc' when both conditions are met (but that would be an easy fix with the syntax I provide). Also $HAS_CUOX will be set to 1 whenever $HAS_CUO3 is found (no boundary checking in the grep regex).
You could take that code further by using a single grep for each set of conditions to check, using something like 'COX1\|Cu_oxidase' as the regex for grep. And also fix the minor issues I mentioned above.
ideally my output would be:
xyz
abc
You added your expected output after I wrote the above script, but given the elements I gave you, you should be able to figure how to improve that (basically removing the exit 0 where I placed them, and doing exit 1 when no output has been given.
Or just remove all exits as a dirty solution.

How can I get the return value and matched line by grep in bash at once?

I am learning bash. I would like to get the return value and matched line by grep at once.
if cat 'file' | grep 'match_word'; then
match_by_grep="$(cat 'file' | grep 'match_word')"
read a b <<< "${match_by_grep}"
fi
In the code above, I used grep twice. I cannot think of how to do it by grep once. I am not sure match_by_grep is always empty even when there is no matched words because cat may output error message.
match_by_grep="$(cat 'file' | grep 'match_word')"
if [[ -n ${match_by_grep} ]]; then
# match_by_grep may be an error message by cat.
# So following a and b may have wrong value.
read a b <<< "${match_by_grep}"
fi
Please tell me how to do it. Thank you very much.
You can avoid the double use of grep by storing the search output in a variable and seeing if it is not empty.
Your version of the script without double grep.
#!/bin/bash
grepOutput="$(grep 'match_word' file)"
if [ ! -z "$grepOutput" ]; then
read a b <<< "${grepOutput}"
fi
An optimization over the above script ( you can remove the temporary variable too)
#!/bin/bash
grepOutput="$(grep 'match_word' file)"
[[ ! -z "$grepOutput" ]] && (read a b <<< "${grepOutput}")
Using double-grep once for checking if-condition and once to parse the search result would be something like:-
#!/bin/bash
if grep -q 'match_word' file; then
grepOutput="$(grep 'match_word' file)"
read a b <<< "${grepOutput}"
fi
When assigning a variable with a string containing a command expansion, the return code is that of the (rightmost) command being expanded.
In other words, you can just use the assignment as the condition:
if grepOutput="$(cat 'file' | grep 'match_word')"
then
echo "There was a match"
read -r a b <<< "${grepOutput}"
(etc)
else
echo "No match"
fi
Is this what you want to achieve?
grep 'match_word' file ; echo $?
$? has a return value of the command run immediately before.
If you would like to keep track of the return value, it will be also useful to have PS1 set up with $?.
Ref: Bash Prompt with Last Exit Code

tail | grep -q always returning true

When I execute this code, the loop always ends at first time (even when the last two lines of auth.log doen't contain "exit"), which means that $c always gets some string:
while true;
do
c=$(tail -2 /var/log/auth.log | grep -q "exit")
if $c ;
then
echo "true"
unset c
break
fi
done
Do you know why c=$(tail -2 /var/log/auth.log | grep -q "exit") is always getting some kind of string? I think it is becaues of tail.
I can use the -o option and then compare strings, but I prefer to use a boolean inside the if condition.
grep -q by design returns no output, it simply signals via its exit code whether a match was found.
Thus, you can simply use your pipeline directly as a condition:
while true;
do
if tail -2 /var/log/auth.log | grep -q "exit";
then
echo "true"
break
fi
done
As for what you tried:
As Benjamin W. implies in a comment on the question, executing a command expanding to the empty string is always considered a successful command.
Note: Whether the command is effectively empty because the variable in question is unset or, as in this case, was explicitly assigned a null (empty) string, doesn't matter.
Thus, given that $c is invariably empty - because grep -q by design never returns stdout output - the if condition always evaluates to true.
To be clear: $c, since it is not being used in a conditional (if $c; ... rather than if [ "$c" ]; ...), is interpreted as a command to execute rather than as a string to test for emptiness.
If the command whose output is captured in $c were to generate stdout output, you'd have to test for that with a conditional: if [ -n "$c" ]; then ... (or, more succinctly, if [ "$c" ]; then ...).

Check number of lines returned by bash command

I have a command similar to this:
LIST=$(git log $LAST_REVISION..$HEAD --format="%s" | egrep -o "[A-Z]-[0-9]{1,4}" | sort -u)
Now, I need to do something if $LIST returned zero or more lines. Here's what I've tried:
if [ ! $($LIST | wc -l) -eq 0 ]; then
echo ">0 lines returned"
else
echo "0 lines returned"
fi
But it throws me an error. What's the correct syntax of doing this (with some details on the syntax used, if possible)?
To check whether a variable is empty, use test -z, which can be written several ways:
test -z "$LIST"
[ -z "$LIST" ]
with bash (or many other "modern" shells):
[[ -z $LIST ]]
I prefer the last one, as long as you're using bash.
Note that what you are doing: $($LIST | ...) is to execute $LIST as a command. That is almost certain to create an error, and guaranteed to do so if $LIST is empty.

shell script : how to validate function is returning 1 text line?

I try to generate a csv file through a lot of functions like that :
function get_sudo_version {
sudo -V 2>/dev/null|grep -i "sudo version"
}
sudo_version=$(get_sudo_version)
Function above is a simple example but in some cases i cannot be sure the output is correct.
I would like to know what is the best way to validate the function return one text line only.
I thought about something like that
function validate_output {
output=$1;
echo $1|grep -q "\n";
echo $?;
}
mytest="val1
err2
err3"
But it's obviously not working because the variable does not keep the retrun line character:
echo $mytest
val1 err2 err3
So if someone has a good idea of how i could wirte a generic check function i would be glad.
Thanks
If you have GNU grep, you could simply ensure that grep doesn't produce more than one line of output in the first place via grep -m 1. Alternatively, use sed '/sudo version/!d;q' instead of grep.
A function that simply checks lines of input while passing them through might look like:
shopt -s lastpipe # if bash
# ksh or bash
validate_output() {
(( $(tee >(wc -l) >&2) == 1 ))
} 2>&1
# bash
validate_output2() {
local lines
tee /dev/fd/2 | mapfile -tc1 -C '((++lines < 2)) || return; :'
((lines))
} 2>&1
get_sudo_version | validate_output || echo failed
Many variations on that possible of course. IMO it's pretty pointless and shouldn't be used for something like this. Just design your get_sudo_version so that it guarantees the right results.
You can count lines with wc -l:
$ LINES="$(echo -n | wc -l)"; [[ "$LINES" -gt 0 ]] && echo 'at least one line'
$ LINES="$(echo -e 'a\nb\nc' | wc -l)"; [[ "$LINES" -gt 0 ]] && echo 'at least one line'
at least one line

Resources