10

Why does

grep e\\.g\\. <<< "this is an e.g. wow"

and

grep e\.g\. <<< "this is an e.g. wow"

do the same thing?

If I add a third slash, it also has the same result. BUT, once I add a fourth slash it no longer works. This has to do with a question from an old exam for a class. It asked if the one with two backslashes one would work to output the line with "e.g." I originally thought it wouldn't work, but I tried to make sure and it did. What is the explanation?

  • I'd thought bash would take \\\. and give grep \. but it doesn't. good question – user4069 Oct 19 '14 at 23:40
9

First, note that the single slash matches too much:

$ echo $'eegg \n e.g.' | grep e\.g\.
eegg
 e.g.

As far as Bash is concerned, an escaped period is the same as a period. Bash passes on the period to grep. For grep, a period matches anything.

Now, consider:

$ echo $'eegg \n e.g.' | grep e\\.g\\.
 e.g.
$ echo $'eegg \n e.g.' | grep e\\\.g\\\.
 e.g.
$ echo $'eegg \n e.g.' | grep e\\\\.g\\\\.
$

When Bash sees a double-slash, is reduces it to a single slash and passes that onto grep which, in the first of the three tests above, sees, as we want, a single slash before a period. Thus, this does the right thing.

With a triple slash, Bash reduces the first two to a single slash. It then sees \.. Since an escaped period has no special meaning to Bash, this is reduced to a plain period. The result is that grep sees, as we want, a slash before a period.

With four slashes, Bash reduces each pair to a single slash. Bash passes on to grep two slashes and a period. grep sees the two slashes and a period and reduces the two slashes to a single literal slash. Unless the input has a literal slash followed by any character, there are no matches.

To illustrate that last, remember that inside single-quotes, all characters are literal. Thus, given the following three input lines, the grep command matches only on the line with the literal slash in the input:

$ echo 'eegg
e.g.
e\.g\.' |  grep e\\\\.g\\\\.
e\.g\.

Summary of Bash's behavior

For Bash, the rules are

  • Two slashes are reduced to a single slash.

  • A slash in front of a normal character, like a period, is just the normal character (period).

Thus:

$ echo \. \\. \\\. \\\\.
. \. \. \\.

There is a simple way to avoid all this confusion: on the Bash command line, regular expressions should be placed in single-quotes. Inside single quotes, Bash leaves everything alone.

$ echo '\. \\. \\\. \\\\.'  # Note single-quotes
\. \\. \\\. \\\\.
  • Question: It takes two backslashes for bash to view it as a backslash (one is the escape sequence, the other is the literal backslash). So, when there are 3 does bash treat the third straggler as an escape sequence as well? Since it is escaping nothing, is it then discarded? – Franz Kafka Oct 20 '14 at 0:13
  • @DanielAmaya The third is treated as an escape for the character which follows. In our case, that character is the period and, for bash (unlike grep), an escaped period is just a plain period. bash then passes the plain period on to grep. – John1024 Oct 20 '14 at 0:15
  • @DanielAmaya See updated answer for an echo statement that illustrates what bash does in these cases. – John1024 Oct 20 '14 at 0:24
  • 2
    @DanielAmaya In both cases, bash reduces The first two slashes to a single slash. What remains is \. or .. For bash, both of those are the same: they are equivalent to a plain period. Hence, in total, what bash delivers to grep is the same for both: a single-slash followed by a period. – John1024 Oct 20 '14 at 1:18
  • 1
    Just a small addition - using echo is not very reliable way to test regexp because of many implementation of this program. For example under my zsh (built-in echo) echo \. \\. \\\. \\\\. \\\\\. gives . \. \. \. \., but /bin/echo \. \\. \\\. \\\\. \\\\\. returns . \. \. \\. \\.. Something like printf "%s" ... is probably the better way. – jimmij Oct 20 '14 at 1:32
4

The output is the same only for your string, but in general those regular expressions do different things. Let's modify your example a little by adding second pattern e,g, (with comas), third e\.g\. (dots), fourth e\,g\, (comas), and -o option to grep to print only matched parts.

  • In the following case . match any char (notice '' around e.g., I will come to that later)

    $ grep -o 'e.g.' <<< grep -o 'e.g.' <<< 'this is an e.g. e,g, e\.g\. e\,g\,'
    e.g.
    e,g,
    
  • Next we escape . with backslash \, so only literal . will be matched:

    $ grep -o 'e\.g\.' <<< 'this is an e.g. e,g, e\.g\. e\,g\,'
    e.g.
    
  • But we can escape \ with another \, so that literal \ will be matched followed by . (i.e. any char):

    $ grep -o 'e\\.g\\.' <<< 'this is an e.g. e,g, e\.g\. e\,g\,'
    e\.g\.
    e\,g\,
    
  • But if we want to match only \. not \, then yet another \ is needed to escape special meaning of the dot:

    $ grep -o 'e\\\.g\\\.' <<< 'this is an e.g. e,g, e\.g\. e\,g\,'
    e\.g\.
    

Now, because you didn't use '' around grep argument you need to add another backslashes to escape backslashes from shell interpretation, so:

grep 'e\.g\.'     => grep e\\.g\\.
grep 'e\\.g\\.'   => grep e\\\\.g\\\\.  (each backslash has to be quoted separately)
grep 'e\\\.g\\\.' => grep e\\\\\\.g\\\\\\. (3 x 2 = 6 backslashes in total)
3

When you do a grep e\.g\., the shell is consuming the backslash, thus you are doing a grep e.g., which matches. When you do a grep e\\.g\\., the shell is again consuming a slash, and now you are doing a grep e\.\g., which again matches. Now, a backslash to the shell looks like \\. So, when you have \\, the first one is an escape sequence, the second is a literal backslash. When you do a grep e\\\.g\\\., it still ends up being grep e\.\g., because there is not an escape sequence (\) before the first \ to make it a literal \. Keep in mind \ is a backslash, thus grep e\\\\.\\\\g ends up being grep e\\.g\\., which obviously does not match.

To see how the shell is seeing what you're doing, use echo (e.g., echo grep e\\.g\\. <<< "this is an e.g. wow" vs. echo grep e\\\\.g\\\\. <<< "this is an e.g. wow")

0

The two commands produce the same output only for your input but otherwise they are different. For understanding of what is going on we have to know how is the parameter interpreted first by bash and then by grep.

Escaping in bash

\ is a special character which cancels special meaning of the following character including \ itself. If the following character has no special meaning then it is passed without change. Examples with command and a result:

  • echo \a: a — ordinary character escaped gives the character
  • echo \\: \ — special character escaped gives the character
  • echo \\\a: \a — combination special, ordinary
  • echo \\\\: \\ — combination special, special

echo will print the resulting string after bash interprets it. More information: bash documentation, bash hackers wiki, POSIX specification.

. has no special meaning in bash. It is an ordinary character for the shell. Below are the sequences relevant to your examples:

  • echo .: .
  • echo \.: .
  • echo \\.: \.
  • echo \\\.: \.
  • echo \\\\.: \\.

Simpler solution for literal strings in bash

To pass parameters literally by bash you can use single quote ' escaping. Between single quotes you do not have to care about special meaning of characters because single quote is the only character with a special meaning there. You can insert a single quote after enclosing the first part of the string. Example:
echo 'part1'\''part2': part1'part2

Regex in grep

\ is an escape character with similar meaning as in bash. . is a special character which represents a single occurrence of any character. See: POSIX regex, GNU grep regex. Examples of regex expressions:

  • . — matches any character like a or .
  • \. — matches only . literally

Your examples

On the second line of every example below you will find equivalent with single quotes ' showing which literal string is passed by bash to grep. Then after grep performs escaping the only possible special character in the examples is . matching any character. On the third line there is a description what the expression matches.

  • grep e.g. <<< "this is an e.g. wow"
    grep 'e.g.' <<< "this is an e.g. wow"
    eany charactergany charactermatches e.g. and possibly other strings like eagb
  • grep e\.g\. <<< "this is an e.g. wow"
    grep 'e.g.' <<< "this is an e.g. wow"
    eany charactergany charactermatches e.g. and possibly other strings like exgy
  • grep e\\.g\\. <<< "this is an e.g. wow"
    grep 'e\.g\.' <<< "this is an e.g. wow"
    e.g. literally — matches e.g. only
  • grep e\\\.g\\\. <<< "this is an e.g. wow"
    grep 'e\.g\.' <<< "this is an e.g. wow"
    e.g. literally — matches e.g. only
  • grep e\\\\.g\\\\. <<< "this is an e.g. wow"
    grep 'e\\.g\\.' <<< "this is an e.g. wow"
    e\any characterg\any characterdoes not match e.g.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.