5

I am trying to execute the following loop, however I'm failing. I have read about double parentheses and other things, however, I cannot manage to make it work (especially the second part in combination with grep). I can make each part work separately, but not both together.

First try:

a=1;
b=1;

while { ( n1=$(grep 'too many requests' *.htm* )) && (( $a -eq $b  )) }; do
    echo "aa"
done
    

Second try:

while [[  n=$(grep 'too many requests' *.htm* ) && $a==$b ]]; do
    echo "error"
done

Third try

while [[  n=$(grep 'too many requests' *.htm* ) &&  "$a" -eq "$b"  ]]; do
    echo "aa"
done

What I expect: if the HTML files do not contain the phrase "too many requests", and a is equal b, enter the loop.

1
  • 1
    You have an infinite loop, since you never change any of the variables during the loop.
    – Barmar
    Commented Dec 15, 2022 at 15:19

1 Answer 1

9
a=1
b=1

if ! grep -q -F -- 'too many requests' *.htm* && [ "$a" = "$b" ]; then
    echo error >&2
fi

This uses the negated exit status of grep and the result of comparing $a and $b for string equality (use ['s -eq for integer evaluation of equality). If both are true (the string is not found in any file matching *.htm* and the two variables are identical), then the echo is executed, outputting the string error to the standard error stream.

Note the use of single = with spaces around it. The = (or -eq) operator is an argument to the [ (test) utility and not intrinsically tied to the if keyword.

The options -q and -F to the grep utility cause the utility to be quiet and to exit as soon as a match is found (-q) and to treat the given pattern as a text string rather than as a regular expression (-F).


Your examples use a while loop, which should be an if statement. If you want to handle each file separately, then use a for loop:

for name in *.htm*; do
    if ! grep -q -F -- 'too many requests' "$name" && [ "$a" = "$b" ]
    then
        printf 'error for "%s"\n' "$name" >&2
    fi
done

You don't have to save the output of grep if you only want to test whether a pattern matches in a file or set of files. You may instead use grep -q and react to the exit status of the utility, as shown above.

In your first code snippet, you use an arithmetic evaluation, (( $a -eq $b )). This could be better written as (( a == b )), which evaluates to a result that an if statement could use as a boolean. The -eq operator is specific to the [ utility and not valid in (( ... )).

In the second code snippet, you use $a==$b. This would evaluate into a string, 1==1, which the shell would try to use as a command.

In the third code snippet, you use "$a" -eq "$b" within [[ ... ]], which correctly performs an integer equality test between $a and $b. This does not work as expected due to the grep command and the way you don't negate its exit status (and the fact that you use a while loop rather than an if statement).

Within [ ... ], = tests for string equality and -eq tests for integer equality. Technically, == is not a valid operator for [ ... ] tests, but bash supports it. The bash shell does shell pattern matching within its special [[ ... ]] syntax when using ==, as in

[[ $name == *.txt ]] && echo 'The name matches *.txt'
5
  • So my question is grep part cannot be in parenthesis or {}[]? Right? Only outside can expression valuation happens like grep in if/while statements? Commented Dec 15, 2022 at 20:31
  • 1
    @Estatistics The way I'm using grep in my answer is to look at its exit status, which will tell me whether it found something or not. The [ ... ] test is done if you need to compare a string or integer to something, or when you need to test for existence of a file, or whether something is a directory or a regular file etc. (see the manual for [ and test).
    – Kusalananda
    Commented Dec 15, 2022 at 21:51
  • 1
    @Estatistics With { ...; } you create a compound command. This is mostly useful if you need to fit multiple simple commands wherever only a single command is allowed, for example as a single stage in a pipeline, or to redirect several commands to the same file, as in { echo hello; echo world; } >outfile.
    – Kusalananda
    Commented Dec 15, 2022 at 21:52
  • 1
    @Estatistics The ( ... ) is a sub-shell. This is mostly useful for creating a local temporary environment, as in ( cd dir && touch file ) (the cd won't affect the parent environment's working directory). (( ... )) is an arithmetic evaluation, which will result in an exit status depending on the evaluation of an arithmetic expression, as in if (( a == 0 )); then ...; fi, or (( a++ )).
    – Kusalananda
    Commented Dec 15, 2022 at 21:54
  • Oh Very nice explanation, Now, i started to understand the use of them! Thanks! Commented Dec 16, 2022 at 19:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .