Is there an easy way to pass a "raw" string to grep?

Viewed 47201

grep can't be fed "raw" strings when used from the command-line, since some characters need to be escaped to not be treated as literals. For example:

$ grep '(hello|bye)' # WON'T MATCH 'hello'
$ grep '\(hello\|bye\)' # GOOD, BUT QUICKLY BECOMES UNREADABLE

I was using printf to auto-escape strings:

$ printf '%q' '(some|group)\n'
\(some\|group\)\\n

This produces a bash-escaped version of the string, and using backticks, this can easily be passed to a grep call:

$ grep `printf '%q' '(a|b|c)'`

However, it's clearly not meant for this: some characters in the output are not escaped, and some are unnecessarily so. For example:

$ printf '%q' '(^#)'
\(\^#\)

The ^ character should not be escaped when passed to grep.

Is there a cli tool that takes a raw string and returns a bash-escaped version of the string that can be directly used as pattern with grep? How can I achieve this in pure bash, if not?

6 Answers
quote() {
    sed 's/[^\^]/[&]/g;s/[\^]/\\&/g' <<< "$*"
}

Usage: grep [OPTIONS] "$(quote [STRING])"

This function has some substantial benefits:

  • quote is independent from the regex flavor. You can use quote's output in
    • grep (-G)` (BRE, the default)
    • grep -E (ERE)
    • grep -P (PCRE)
    • sed (-E) "s/$(quote [STRING])/.../" (as long as you don't use \, [, or ] instead of /).
  • quote even works in corner cases that are not directly quoting related, for instance
    • Leading - are quoted so that they aren't misinterpreted as options by grep.
    • Trailing spaces are quoted so that the aren't removed by $(...).

quote only fails if [STRING] contains linebreaks. But in general there is no fix for this since tools like grep and sed may not support linebreaks in their search pattern (even if they are written as \n).

Also, there is the drawback that the quoted output usually is three times longer than the unquoted input.

Just want to comment example below which shows that substring "-B" is iterpreted by grep as a command line option and the command failed.

echo "A-B-C" | grep -F "-B-"

grep has a special option for this case:

-e PATTERNS, --regexp=PATTERNS Use PATTERNS as the patterns. If this option is used multiple times or is combined with the -f (--file) option, search for all patterns given. This option can be used to protect a pattern beginning with “-”.

So a fix for the issue is:

echo "A-B-C" | grep -F -e "-B-" -
Related