0

The following code saves my_string in the file my_filename and then checks whether pattern1 and pattern2 are in the file. The first is; the second isn't.

#!/bin/bash

my_string="One two three four" 
pattern1="two three"
pattern2="two four"

my_filename="my_temp_file"

safeGrepCommand() {
    typeset command_to_run="$*"
    typeset ret_code

    # echo command_to_run=$command_to_run
    eval $command_to_run
    ret_code=$?
    if [ $ret_code != 0 ]; then
        printf "Pattern %s is not in the file %s.\n" "${my_pattern}" "${my_filename}"
        exit $ret_code
    fi
}

echo $my_string > $my_filename

grep_command1="grep --quiet ${pattern1} ${my_filename}"
safeGrepCommand "$grep_command1"

grep_command2="grep --quiet ${pattern2} ${my_filename}"
safeGrepCommand "$grep_command2"

rm -f $my_filename

I am expecting to see the output

Pattern two four is not in the file my_temp_file.

Instead I see

grep: three: No such file or directory
grep: four: No such file or directory

As you'll see if you uncomment the echo line inside the function, the problem is that the quotation marks are not seen by grep.

command_to_run=grep --quiet two three my_temp_file
command_to_run=grep --quiet two four my_temp_file

How do I get bash to pass the quotation marks to grep? Alternatively, how do I specify "two three" as a regexp with [:space] and not worry about quotation marks.

Calaf
  • 10,113
  • 15
  • 57
  • 120
  • 3
    Do not stick commands in strings. If you need to do this sort of thing use an array. You cannot get this to work reliably. See http://mywiki.wooledge.org/BashFAQ/050 for more details and discussion. In your case this is even simpler as you can just not use `grep_command1` at all and just pass the arguments to `safeGrepCommand` directly. (Though assigning `$*` to a variable will still bite you. Don't do that. Just use "$@" where you need the expansion (and you don't even need eval for that). – Etan Reisner Nov 13 '14 at 16:21
  • possible duplicate of [Preserving escapes in bash arguments $@](http://stackoverflow.com/questions/21820240/preserving-escapes-in-bash-arguments) – rici Nov 13 '14 at 16:24
  • possible duplicate of [Passing arguments to a command in Bash script with spaces](http://stackoverflow.com/questions/4753993/passing-arguments-to-a-command-in-bash-script-with-spaces) – Etan Reisner Nov 13 '14 at 16:25
  • 3
    I would suggest that using `eval` is the *opposite* of "safe" – glenn jackman Nov 13 '14 at 16:32
  • @rici Indeed. I picked the one with the highest vote count I could find quickly. – Etan Reisner Nov 13 '14 at 16:34

2 Answers2

1

Here's one way of resending arguments in a shell function:

doit_or_complain() {
  # The arguments are captured only to show how to do it. In practice,
  # I would just use "${@}" to run the command.
  local -a command=("$@")
  "${command[@]}"
  local rc=$?
  if ((rc)); then
    echo "The command failed" >> /dev/stderr
  fi
  return $rc
}

That will work with any random arguments:

doit_or_complain grep "a  b" "./my file with spaces in its name"

If you want to pass a complicated command to the shell function, you should use an array:

theCommand=(grep "a  b" "./my file with spaces in its name")
#...
doit_or_complain "${theCommand[@]}"

Note: The function in the OP used exit $rc if the command failed. That will exit the current shell, not just the function, which might be considered a tad unexpected, if not unsafe.

rici
  • 234,347
  • 28
  • 237
  • 341
0

Wrap the pattern variable in double quotes as

grep_command1="grep --quiet \"${pattern1}\" \"${my_filename}\""
safeGrepCommand "$grep_command1"

grep_command2="grep --quiet \"${pattern2}\" \"${my_filename}\""
safeGrepCommand "$grep_command2"
nu11p01n73R
  • 26,397
  • 3
  • 39
  • 52
  • Thanks for your reply, but it seems something else is still wrong. If I replace "twofour" with "twothree" (i.e., with two spaces), I am expecting that the grep will fail. Yet after using the double quotes it succeeds. What is happening? – Calaf Nov 13 '14 at 16:57
  • @Calaf its because the multiple spaces within double quotes are treated as a single space. If you want to force double space, escape the spaces as well. Which will work – nu11p01n73R Nov 13 '14 at 17:00
  • 1
    @Calaf: this is not the way to go. Please see any of the answers using arrays in the linked duplicate questions, and/or read the bash faq http://mywiki.wooledge.org/BashFAQ/050 . (However, the problem is that you are not quoting the argument to `eval`.) – rici Nov 13 '14 at 17:01
  • @nu11p01n73R I had tried that, but instead of getting "Pattern twothree is not in the file my_temp_file." I got the two lines "Pattern two is not in the file my_temp_file.\nPattern three is not in the file my_temp_file." – Calaf Nov 13 '14 at 17:05
  • @rici I tried to make the question as brief as possible. This seems like a trivial exercise. Still, my use of a function was intended only for safety. How would you write it? – Calaf Nov 13 '14 at 17:10
  • @Calaf but in the script in question you havnt' set values for `my_pattern` or `my_filename` – nu11p01n73R Nov 13 '14 at 17:14
  • @Calaf: what safety issue are you thinking of? It seems to me that your function is less safe. Put my response in an answer. – rici Nov 13 '14 at 17:54