Search code examples
bashshellgrepdouble-quotes

grep with double quotation in bash script


I'm trying to find IPs from whitelist IP list for nginx configuration.

/tmp/iplist:

11.2.3.4
22.2.3.4

/tmp/whitelist:

"1.2.3.4"
"11.2.3.4"
"1.2.3.44"
"11.2.3.44"

And when I run grep like grep '"11.2.3.4"' /tmp/whitelist, I can get a right answer like "11.2.3.4".

However, within a bash script, I could not get any answers.
Here are some patterns that I tried:

/tmp/findIPs.sh:

#!/bin/bash

for ip in $(cat /tmp/iplist)
do
  grep '"$ip"' /tmp/whitelist
  grep '\"$ip\"' /tmp/whitelist
  grep '\\\"$ip\\\"' /tmp/whitelist
  x="\"$ip\""
  fgrep '$x' /tmp/whitelist
  grep '$x' /tmp/whitelist
  y="$ip"
  fgrep '$y' /tmp/whitelist
  grep '$y' /tmp/whitelist

And, this is a result, which is blank.

> bash /tmp/findIPs.sh
>

What is the point that I missing?


Solution

  • The simplest adaptation of the code you're using is:

    for ip in $(cat /tmp/iplist)
    do
        grep "$ip" /tmp/whitelist
    done
    

    Once you've got the value in the variable, its double quotes aren't damaged by further double-quoted variable expansion. If the /tmp/iplist file doesn't contain the double quotes but they're critical, then you can use:

    grep "\"$ip\"" /tmp/whitelist
    

    or you could use this:

    grep \""$ip"\" /tmp/whitelist
    

    (and there are two asymmetric permutations available too). It's a good idea to make sure you do know why that works. There are some ways to use single quotes if you want to, but the "$ip" part must be outside of the single quotes.

    All your examples start the pattern argument to grep with a single quote. Single quotes suppress all expansions until the next single quote. So, for example, grep '"$ip"' /tmp/whitelist is looking for 5 characters — ", $, i, p, " — in the file. In none them is the variable ip ever expanded.

    There will be problems if any IP address ever gets a space in it. Be cautious about using for ip in $(cat /tmp/iplist). Very often you'd do better with:

    while read -r ip
    do
        grep "$ip" /tmp/whitelist
    done < /tmp/iplist
    

    Another way to do this is with grep -F or fgrep:

    grep -F -f /tmp/iplist /tmp/whitelist
    

    This doesn't insist on double quotes around the IP addresses, but makes a single pass over the /tmp/whitelist file (and a single pass over the /tmp/iplist file too), which is about as efficient as it gets. This will produce lines in a slightly different order from before, which probably won't matter, but you should be aware of it.

    If you must have the double quotes (to avoid selecting 11.2.3.44 when searching for 11.2.3.4, then:

    grep -F -f <(sed 's/^/"/; s/$/"/' /tmp/iplist) /tmp/whitelist
    

    This uses process substitution to pass an edited version of the /tmp/iplist file to the grep command. If you don't have process substitution in your shell, you can probably use:

    sed 's/^/"/; s/$/"/' /tmp/iplist | grep -F -f - /tmp/whitelist
    

    which makes grep read the list of patterns to match from standard input instead of a named file. If perchance -f - doesn't work (e.g. because you're working on a Mac using macOS, or probably a BSD machine), then -f /dev/stdin probably will, or -f /dev/fd/0.

    You could also generate the /tmp/iplist file with the double quotes in place. You could generate the /tmp/whitelist file without the double quotes and then use grep -x to specify an exact match.

    In case you haven't already gathered, there are quite a lot of different ways to do this.