0

The book I am reading - Learning the Bash Shell by O'Reilly specifies some code as follows:

if [ -n "$(echo $1 | grep '^-[0-9][0-9]*$')" ]; then 

   howmany=$1

   shift
   ....
   ....
   etc

This uses the grep search utility to test if $1 matches the appropriate pattern. To do this we provide the regular expression ^-[0-9][0-9]*$ to grep, which is interpreted as "an initial dash followed by a digit, optionally followed by one or more digits." If a match is found then grep will return the match and the test will be true, otherwise grep will return nothing and processing will pass to the elif test.

Notice that we have enclosed the regular expression in single quotes to stop the shell from interpreting the $ and *, and pass them through to grep unmodified.

Then, why doesn't the regular expression '^-[0-9]' lose it's meaning as in single quotes, usually everything within single quotes loses it's meaning.

Thank you for your help.

closed as unclear what you're asking by techraf, HalosGhost, GAD3R, Satō Katsura, mdpc Oct 27 '16 at 17:25

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

2

Single quotes tell the shell to keep the enclose characters as is, without any interpretation. The quoted string is passed as-is to grep, without the quotes: when grep looks for its arguments, it sees

grep

and

^-[0-9][0-9]*$

and acts on that. (Read How programs get run if you're curious about argument construction in Linux.)

bash and grep are different. The way this command uses quotes ensures that bash doesn't process the string, but grep does.

3

While others have answered your specific question, I'll add that

if [ -n "$(echo $1 | grep '^-[0-9][0-9]*$')" ]; then 

Is a wrong way to check whether a string matches a regular expression for several reasons:

  1. You can't use echo for arbitrary data
  2. Leaving a parameter expansion unquoted like that $1 above is the split+glob operator.
  3. grep doesn't match the regex against its full input, but on every line of it's input. So it would return true on foo\n-0\nbar for instance.
  4. A regexp can match with zero length, so it's wrong in the general case to check if grep produces some output (note that command substitution strips trailing newline characters). It's better use grep -q and rely on the exit status of grep, rather than that of [, and also avoid the command substitution.
  5. Note that that grep command can be simplified to grep -xE '-[0-9]+'

bash, ksh93 and zsh have a dedicated operator for (extended) regexp matching. To use it portably and reliably across all three (and bash-3.1), the syntax is:

re='^-[0-9]+$'
if [[ $1 =~ $re ]]; then
  echo matches
fi

yash and zsh also support:

if [ "$1" '=~' '^-[0-9]+$' ]; then
  echo matches
fi

The standard command to do string (basic) regex matching is expr:

if expr " $1" : ' -[0-9]\{1,\}$' > /dev/null; then
  echo matches
fi

Note that the ^ (but not $) is implicit in expr. We also use that leading space character to avoid problems with values of $1 that happen to be expr operators.

Also note that if the regex contains \(...\), it will affect the behaviour of expr.

All in all, it's better to use awk instead which is another standard/portable way to do it (note that awk uses extended regexps):

if STRING=$1 RE='^-[0-9]+$' awk '
  BEGIN{exit(ENVIRON["STRING"] !~ ENVIRON["RE"])}'; then
...

Or use a function:

re_match() {
  STRING=$1 RE=$2 awk '
    BEGIN{exit(ENVIRON["STRING"] !~ ENVIRON["RE"])}'
}

if re_match "$1" '^-[0-9]+$'

In this case, you can also achieve it with a standard case construct:

case $1 in
  ("" | *[!0-9-]* | [!-]* | - | ?*-*) ;;
  (*) echo match;;
esac

To use grep, you could use it with the --null option (where supported as that's not a standard option) to tell it to work on NUL delimited records instead of newline delimited records. Since in most shells, $1 cannot contain NULs, that should be safe:

 if printf %s "$1" | grep --null -xq '-[0-9]+$'; then
   echo match
 fi
  • Stéphane, the extent of your knowledge is always astounding. This goes far beyond what was asked in the question, thank you for sharing ! – Valentin B. Oct 27 '16 at 14:49
1

Single quotes prevent globbing (letting bash interpret wildcards like *) and variable expansion through the use of $. Basically you're saying to bash "take literally those characters and pass them to grep". When grep sees them, it is built to understand regular expression so then the regexp is interpretted inside grep.

Shorter version: single quoting arguments provides a mean to escape processing from your shell before the argument is passed to the command.

-2

It does lose its meaning. grep uses a nearly same regex patterns as bash.

  • But doesnt the book say it doesn't. ^-[0-9][0-9]*$ to grep, which is interpreted as "an initial dash followed by a digit, optionally followed by one or more digits – MathMan Oct 27 '16 at 11:52
  • 1
    commands in single quotes ('command') lose their meaning in bash, and passed without any interpreting. – Ipor Sircer Oct 27 '16 at 11:53
  • Bash doesn't use regex patterns, it uses globbing patterns. – Stephen Kitt Oct 27 '16 at 11:56
  • @IporSircer does that mean bash and grep are different? – MathMan Oct 27 '16 at 12:07

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