4

I have a unix command in a variable, it looks like this:

cmd="find /path/to/webpage -type f | grep -v .svn | xargs grep $@"
`$cmd`
find: paths must precede expression
Usage: find [-H] [-L] [-P] [path...] [expression]

When I try to execute the command $cmd in a bash script, it won't work. However, when I copy and paste the exact same command, it does work. Can you let me know what I am doing wrong?

I have tried putting quotes around the path, same error occurs

cmd="find \"/path/to/webpage\" -type f | grep -v .svn | xargs grep $@"
find: paths must precede expression
Usage: find [-H] [-L] [-P] [path...] [expression]

When I remove the -type f parameter, I get this error:

cmd="find /path/to/webpage | grep -v .svn | xargs grep $@"
find: invalid predicate `-v'

That makes me think that the pipe is not being recognized. What can I do to get this to work?

2
  • 1
    Long command pipelines in shell variables should make anyone nervous. What are you trying to do? I recommend taking some time to read and digest BashFAQ 50 before taking this approach any further.
    – jw013
    Commented Jul 26, 2012 at 20:19
  • are you trying to execute the contents of a variable or assign the output of a command to a variable? Your question says the former, but your response to Tim's answer says the latter.
    – cas
    Commented Jul 26, 2012 at 22:25

4 Answers 4

7

Others have already explained what to do. Let me explain what's happening here: the pipe character | doesn't make a pipeline as the variable is expanded, but acts like a literal character. Therefore, find is executed with the following arguments:

{"/path/to/webpage", "-type", "f", "|", "grep", "-v", ".svn", "|", ...}

and it interprets the | as a path and complains that it should have appeared before the expression (-type f).

Another big mistake is that you're using `$cmd` as the sole command line. If $cmd (i.e. find ...) succeeded and produced output like rm -rf /, it would be executed on your behalf. Always take caution when you take data as code!

Improvement 1. find ... | grep -v ... is a poor way to exclude something from the output: find will traverse whole subdirectories named .svn, produce the lines, only to be thrown away later. Why not tell find to do it directly?

find path -type f | grep -v .svn                # don't do this
find path -name .svn -prune -o -type f -print   # do this instead

Improvement 2. When combining find and xargs, always use -print0 in find and -0 in xargs:

find path ... -print0 | xargs -0 -r grep ...    # I'd also recommend -r

or you can do it entirely in grep:

grep --recursive --exclude-dir=.svn pattern path
5

If you really want to execute code in a variable, you can do it using eval.

cmd="find /path/to/webpage -type f | grep -v .svn | xargs grep something"
eval "$cmd"

But since you're trying to pass arguments in with $@, what you need here is a function

webgrep() {
    find /path/to/webpage -type f | grep -v .svn | xargs grep "$@"
}

Note that this will have problems with any paths that include a whitespace character. Or patterns that start with a minus. And it will have to scan all the contents of .svn directories before ignoring them. And it would be nice to handle the user accidentally passing multiple arguments (e.g. because the pattern wasn't quoted properly). A better way is

webgrep() {
    find /path/to/webpage -name .svn -prune -o -type f -exec grep -e "$*" {} +
}

Then call it like this

webgrep PATTERN
2
  • You probably need to use grep -e "$*" to demote the list of command line arguments to a string and to protect against the cases where the string starts with a dash.
    – Kusalananda
    Commented Jul 4, 2018 at 10:33
  • @Kusalananda Quite right, I've updated the last version of webgrep to address those concerns too.
    – Mikel
    Commented Jul 4, 2018 at 18:36
1

Try building your variable like this:

cmd=$(find /path/to/webpage -type f | grep -v .svn | xargs grep $@)

or

cmd=`find /path/to/webpage -type f | grep -v .svn | xargs grep $@`

Or maybe an alias would be better suited:

alias cmd="find /path/to/webpage -type f | grep -v .svn | xargs grep $@"
3
  • Thank you, using the $(command) method is what did the trick!
    – Roy Rico
    Commented Jul 26, 2012 at 19:38
  • No problem! You caught me mid-edit, but should be fixed now.
    – Tim
    Commented Jul 26, 2012 at 19:40
  • 6
    That's not putting the code in a variable. It's putting the output of the command in a variable.
    – Mikel
    Commented Jul 26, 2012 at 21:27
0

Don't put code in a variable, as a rule of thumb. Use a function.

cmd() { 
  find /path/to/webpage -type f |
    grep -v .svn                | 
    xargs grep $@
}

cmd "$@"

If you do eventually end up having to put simple code in a variable (don't, but if you do...) then as mentioned, you can't embed things like pipes or redirections without an eval...and don't do that either, lol

But don't execute it in backticks, which run the commands in a subshell and replace it with the output, so that the output becomes what the parser is receiving as the command to execute:

$: `echo foo`
bash: foo: command not found

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .