1

I want to grep for a variable that has the string test, but I also want to make sure that it will only find test if that is the only word on a line.

So, I tried this:

string=test
echo "test" | grep "$string$"

I would like to know if grep knows that the first part, $string (in grep "$string$"), is asking to find for the string test, while the last part, $ (in grep "$string $" is asking to find for test, but nothing should be after test in the line. Is grep able to distinguish $string as a variable and only $ as the regex for end of the line, or do I have to use a certain way to distinguish them apart?

1 Answer 1

15

Grep knows nothing it does not see. If grep sees $string$, it will try to match $string{end-of-line}. What happens is that the shell expands the variable $string before grep kicks in, so that grep receives test$ as the regex.

But, given the word $string$, why is the last $ is not expanded while $string is? Because $ is not a valid variable name. This is very clear in the POSIX specification:

If the parameter is not enclosed in braces, and is a name, the expansion shall use the longest valid name (see XBD Name), whether or not the variable represented by that name exists.

And XBD name states

In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set. The first character of a name is not a digit.

And there are also special parameters, such as $$, $@, $#, etc..

If you don't want to remember all this, simply follow Gordon Davisson's advice and escape every $ that should not introduce a variable to ensure it will be preserved.

$ echo "$string$"
test$
$ echo "$string\$"
test$
$ echo "\$string\$"
$string$

Also remember variables are expanded in double-quotes but not in single quotes.

$ echo 'test' | grep '$string$'   # No match, $string is not expanded    
$ echo 'test' | grep "$string$"   # Match, $string is expanded
test

I want to grep for a variable that has the string test, but I also want to make sure that it will only find test if that is the only word on a line.

For that, you need either the regex ^test$ (to anchor the string both to the start and end of line) or to use the -x flag of grep. Your original attempt would match Atest because the regex test$ is not anchored to the start of line.

$ echo Atest | grep "$string$"
Atest
$ echo Atest | grep -x "$string"
$ echo Atest | grep "^$string$"
3
  • 4
    Using "$string$" in the shell seems like bad practice to me; I'd escape the final $ (as in "$string\$") instead. Normally, when $ occurs in a double-quoted section of a shell command, it's initiating a variable substitution ($test), command substitution, etc. But when it's followed by a double-quote, that doesn't make sense, so the shell just passes it through literally. That's what you want here, but it seems like kind of an accident that it works, and (as indicated by the original question) it's not obvious. Escaping is the normal way to include a literal $, so I'd use that. Sep 27, 2020 at 5:08
  • Note that in the fish shell and (t)csh, the second dollar sign in echo "$string$" is an error.
    – ilkkachu
    Sep 27, 2020 at 18:21
  • 1
    @ilkkachu: But csh quoting rules are arcane; (t)csh doesn’t accept "$string\$" (what Gordon suggested) either.  It seems that all shells (I tested ash, bash, dash, fish, tcsh and zsh) accept "$string"\$ (with the \$ outside the quotes) or "$string"'$'. Sep 28, 2020 at 9:36

You must log in to answer this question.

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