What Is The Point of "Grep -Q"

What is the point of grep -q

The exit status of grep doesn't necessarily indicate an error ; it indicates success or failure. grep defines success as matching 1 or more lines. Failure includes matching zero lines, or some other error that prevented matching from taking place in the first place.

-q is used when you don't care about which lines matched, only that some lines matched.

if grep -q foo file.txt; then
echo "file.txt contains foo"
else
echo "file.txt does not contain foo"
fi

Why doesn't if [ echo $foo | grep -q bar ] work?

Short Answer

For your immediate use case, you simply want:

if echo "$confirm" | grep -q y; then

...or its much more efficient equivalent (if your shell is bash):

if [[ $confirm = *y* ]]; then

...or its much more efficient equivalent (for any POSIX shell):

case $confirm in *y*) echo "Put your code for the yes branch here" ;; esac

Why was the original wrong?

[ is not part of if syntax: if simply takes a (potentially compound) command as its argument before the then. [ is different name for the test command, which runs checks on its arguments; however, if what you want to test is the exit status of grep -q, then the test command doesn't need to be invoked for this purpose at all.

If you put a | inside a [ command, that makes your compound command a pipeline, and starts a new simple command. Arguments after the | are thus no longer passed to [.

With your original code:

if [ echo $confirm | grep -q y ]; then

...this was running two commands, with a pipeline between them:

[ echo $confirm # first command
grep -q y ] # second command

Since [ requires that its last argument be ], it reported that that mandatory argument was missing; and since grep treats extra arguments as filenames to read, it complained that no file named ] could be found.

Also, [ "$foo" ] checks whether the contents of foo is nonempty. Since the output of grep -q is always empty, [ "$(echo "$confirm" | grep -q y)" ], while syntactically correct, would always evaluate to false, even while exit status of grep -q changes to indicate whether a match was found. ([ "$(echo "$confirm" | grep y)" ], by contrast, is an alternative that emits a correct result - using [ ] to test whether the output from grep is or is not empty -- but is much less efficient than the best-practice approaches).


Formal if syntax

From help if:

if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi

The if COMMANDS list is executed. If its exit status is zero, then the
then COMMANDS list is executed. Otherwise, each elif COMMANDS list is
executed in turn, and if its exit status is zero, the corresponding
then COMMANDS list is executed and the if command completes. Otherwise,
the else COMMANDS list is executed, if present. The exit status of the
entire construct is the exit status of the last command executed, or zero
if no condition tested true.

Notably, if takes a list of COMMANDS, and no [ is included in the syntax specification.

Why exit code 141 with grep -q?

This is because grep -q exits immediately with a zero status as soon as a match is found. The zfs command is still writing to the pipe, but there is no reader (because grep has exited), so it is sent a SIGPIPE signal from the kernel and it exits with a status of 141.

Another common place where you see this behaviour is with head. e.g.

$ seq 1 10000 | head -1
1

$ echo ${PIPESTATUS[@]}
141 0

In this case, head read the first line and terminated which generated a SIGPIPE signal and seq exited with 141.

See "The Infamous SIGPIPE Signal" from The Linux Programmer's Guide.

tail | grep -q always returning true

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 ...).

Why does if [ !$(grep -q) ] not work when if grep -q does?

A number of things are wrong with that first snippet.

You don't want [ ... ] if you want to test the return code. Drop those.

[] is not part of the if syntax (as you can see from your second snippet).

[ is a shell built-in and binary on your system. It just exits with a return code. if ...; then tests the return code of ....

$() is command substitution. It replaces itself with the output from the command that was run.

So [ !$(grep ...) ] is actually evaluating [ !output_from_grep ] and [ word ] is interpreted as [ -n word ] which will be true whenever word is non-empty. Given that ! is never non-empty that will always be true.

Simply, as indicated by @thom in his comment (a bit obliquely), add the ! negation to your second snippet with a space between it and grep.

Ubuntu got EPIPE error when using -q flag with grep

Try something like this:

forever list | grep -q 'process/index.js'
if [ $? -eq 0 ]; then
echo 'Processes are running'
else
echo 'Processes are not running'
fi

grep -q says do not write anything to standard output.

$? is used to find the return value of the last executed command.

/usr/xpg4/bin/grep -q [^0-9] does not always work as expected

Your validity test function happens to be more complicated than it should be. E.g. why do you use a command substitution with print for ${#1}? Why don't you use ${#1} directly? Next, forking grep to test for a non-number is a slow and expensive operation. What about this equivalent function, 100% POSIX and blazingly fast:

is_valid_id () {
# Takes one argument, which is the ID being tested.

if test ${#1} -ne 10; then
return 1 # ID length not exactly 10.
fi
case $1 in
(*[!0-9]*) return 1;; # ID contains a non-digit.
(*) return 0;; # ID is exactly 10 digits.
esac
}

Or even more simple, if you don't mind repeating yourself:

is_valid_id () {
# Takes one argument, which is the ID being tested.
case $1 in
([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]) # 10 digits.
return 0;;
(*)
return 1;;
esac
}

This also avoids your unquoted use of a grep pattern, which is error-prone in the presence of one-character file names. Does this work better?



Related Topics



Leave a reply



Submit