4

I want to search for multiple words inside the current directory. To accomplish this I use something like this.

grep -e "word1" -e "word2" -R .

This works fine.

Taking this one step further, I created a dictionary file that contains every word on a newline and I wanted to create the grep command with this dictionary.

For example I have this dictionary file

one
two
three
more

and I want to create this command

grep -e "one" -e "two" -e "three" -e "more" -R .

I know it can be done with bash and arrays like this:

pattern=()
while IFS= read -r line; do
    pattern+=("-e" "$line")
done < "$dictionary"

grep "${pattern[@]}" -R .

But how could this be done with a POSIX only shell?

I know I could create a string that is the grep command and execute it with eval, but since the dictionary file could be anything this doesn't sound safe to me.

  • Is there a solution without eval?
  • Is there a "secure" solution with eval?
1

A POSIX shell has a single array: the positional parameters ($1, $2, …), collectively accessed as "$@", and set with the set -- builtin. More precisely, there is one array per function instance on the current call stack, but only the array of positional parameters of the currrent function (or of the script, if outside any function) is accessible at any point. Thus, if you want to make use of this array but not clobber the parameters of the script, work in a function.

As sml has already remarked, your specific use case with grep is more simply solved by using the -F option of grep to make it read one pattern per file line. But here's how you can solve the same problem for a command that doesn't have anything like grep -F.

set -- -a -b
while IFS= read -r line; do
  set -- -e "$line"
done < "$dictionary"
mycommand "$@" more stuff

This calls mycommand -a -b line1 line2 … more stuff.

If you need to manipulate several lists of file names (or other strings with arbitrary contents), you can do it with judicious use eval and very careful quoting. Getting the quoting hard is tricky. The following function takes two argument: a variable name and a string; it appends a quoted form of the string to the variable. The quoted form is a single-quoted literal, with single quotes suitably escaped, and is suitable for shell parsing.

append_to_quoted_list () {
  set -- "$1" "$(printf %s. "$2" | sed "s/'/'\\\\''/")"
  eval "$1=\"\${$1} '${2%?}'\""
}

Here's an example usage, to build two lists at the same time. It calls $command1 with the lines passed as arguments to -e options, then $command2 with the lines passed as --foo=… options.

list1= list2=
append_to_quoted_list list1 "$command1"
append_to_quoted_list list1 "$command2"
while IFS= read -r line; do
  append_to_quoted_list list1 -e
  append_to_quoted_list list1 "$line"
  append_to_quoted_list list2 "--foo=$line"
done < "$dictionary"
eval "$list1; $list2"
| improve this answer | |
3

With grep would you not want to make use of the -f or -F switches that grep offers? From the grep man page:

   -F, --fixed-strings
        Interpret PATTERN as a list of fixed strings, separated by 
        newlines, any of which is to be matched.  (-F is specified by 
        POSIX.)

   -f FILE, --file=FILE
        Obtain patterns from FILE, one per line.  The empty file contains 
        zero patterns, and therefore matches nothing.  (-f is specified by
        POSIX.)

Example

$ grep -F somefile.txt -R .
| improve this answer | |
  • Ah thanks that solves the pattern problem. But not the problem in general. Is there a solution for the general case? I am asking because my whole command creation currently looks like this grep "${arguments[@]}" "${pattern[@]}" "${files[@]}" – Raphael Ahrens Sep 26 '14 at 6:20

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.