4

I'm trying to execute a command, stored within a variable:

cmd="grep -i \"word1\" filename | grep -i \"word2\""
eval $cmd

But when I execute the script I get the errors:

grep: |: No such file or directory
grep: grep: No such file or directory

How can I execute commands like the one in my example without getting this errors?

  • missing closing double quote. – choroba Dec 22 '14 at 16:39
  • 1
    I cannot confirm that. This works for me, even with whitespace: cmd="grep \"foo bar\" file | grep \"match\""; eval $cmd. In general you should use eval "$cmd" but I have no idea how that can cause this error. Please run set -x before executing the commands and add the output to your question. – Hauke Laging Dec 22 '14 at 18:30
  • what shell are you using? type echo $0 – tachomi Dec 22 '14 at 19:07
4

You need to quote "$cmd" - and maybe avoid the "double-quotes. Anyway, to run this you do need to eval it - and this is due to the |pipe. A shell variable does not expand beyond the limits of a single simple command - and you're trying to run a compound command.

So:

cmd='grep -i "word1" filename | grep -i "word2"'

eval "$cmd"

Probably when you were expanding $cmd without quotes you ran into filename generation and/or $IFS issues. Just make sure it's reliably quoted and you'll be fine. Also verify that whatever is in "word[12]" doesn't contain double-quotes or backslashes or whatever - else you'll need to reevaluate your quoting style there.

Looking closer now at your error and I can tell you exactly what it was:

grep: |: No such file or directory
grep: grep: No such file or directory

So if I do:

echo | echo

The first echo prints a \newline to the second echo's stdin. If I do:

set \| echo
echo "$@"

The first echo prints each of its arguments, which are |pipe and echo respectively.

The difference is that the |pipe in the first case is interpreted by the shell's parser to be a token and is interpreted as such. In the second case, at the same the shell is scanning for the |pipe token it is also scanning for the $expand token - and so the |pipe is not yet there to be found because "$@" has not yet been replaced with its value.

So if you do:

grep -i "word" filename \| grep -i "word2"

...you're likely to get the same results because the | there does not delimit the commands, and is instead an argument to grep in its infiles position.

Here's a look at how many fields you get when you split $cmd with a default $IFS:

printf '<%s> ' $cmd
<grep> <-i> <"word1"> <filename> <|> <grep> <-i> <"word2">

And here's what filename generation might do:

touch 'echo 1simple &&' 'echo 2simple'
eval echo*
| improve this answer | |
  • There was always an eval in the question thus I don't understand your second sentence. What effect do you think the "$cmd" quotes have on the pipe? There could indeed be problems with pathname expansion but they would not affect the limits of a single simple command because they are determined before. I guess your last remark is the point: "Dangerous" characters in word1 or filename. – Hauke Laging Dec 22 '14 at 21:01
  • 1
    @HaukeLaging - the eval stuff was mostly about a lot of the other answers here that suggest it not be used. An alias could work, too, but I was just talking about the mode the asker says he uses. The "$cmd" quotes serve to better protect the parameters, and there is the way that eval will concat on spaces if it's handed multiple args. And various characters - newlines, whatever - will split out the command in all kinds of ways if the it is evaluated at the wrong time. It is best to leave it a single field. And yes - pathname expansion will affect simple command limits. – mikeserv Dec 22 '14 at 21:08
  • "pathname expansion will affect simple command limits" – Would you mind giving an example for that? – Hauke Laging Dec 22 '14 at 21:12
  • @HaukeLaging - touch 'echo 1simple command &&' 'echo 2simple commands'; v=echo*; eval $v; The whole point of eval is that it gives the shell's parser another pass at the arguments - that is what is meant by twice-evaluated. – mikeserv Dec 22 '14 at 21:17
0

For the example you posted, you shouldn't need the eval. Just execute the variable like so:

cmd="grep -i \"word1\" filename | grep -i \"word2\""; # added missing " at the end    
$cmd

Watch your quotes. Maybe something like this is less prone to mistakes:

cmd='grep -i "word1" filename | grep -i "word2"';
| improve this answer | |
  • I'm still having the same errors – Kaj Dec 22 '14 at 16:56
  • if you could paste something more like your exact code I could probably help more. Im guessing it's likely a quote issue, but hard to tell. I ran that example I posted and it worked for me. – Gregory Patmore Dec 22 '14 at 20:55
0

As for me it wrong way to use variable to save and to execute any command. There is better choice -- function:

my_cmd() {grep -i "word1" filename | grep -i "word2"}

That is all.

Or you can use different arguments like:

my_cmd() {grep -i "$1" "$3" | grep -i "$2"}

Then call it by

my_cmd word1 word2 filename
| improve this answer | |
  • The given code was just an example, the command is not always the same and is generated. – Kaj Dec 22 '14 at 17:07
  • It is not a problem. You can call function with arguments: my_cmd(){grep -i "$1" "$3" | grep -i "$2"} Then call it my_cmd word1 word2 filename – Costas Dec 22 '14 at 17:34
  • @Luxo see edited answer – Costas Dec 22 '14 at 17:50
  • With generating the command I ment that it's possible that you end up getting a command like grep -i "word1" filename | grep -i "word2" | grep -i "word3" | grep -vi "excludeword" – Kaj Dec 22 '14 at 18:07
  • @Luxo There are many ways to avoid eval use. One of it can be a loop: my_cmd() { result=$(grep -i "$1" "${!#}") ; for i in $(seq 2 $[$#-1]) ; do result=$(echo "$result" | grep -i "${!i}") ; done ; echo "$result" }) – Costas Dec 22 '14 at 18:39

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.