7

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?

3
  • missing closing double quote.
    – choroba
    Dec 22, 2014 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. Dec 22, 2014 at 18:30
  • what shell are you using? type echo $0
    – tachomi
    Dec 22, 2014 at 19:07

3 Answers 3

6

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*
4
  • 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. Dec 22, 2014 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, 2014 at 21:08
  • "pathname expansion will affect simple command limits" – Would you mind giving an example for that? Dec 22, 2014 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, 2014 at 21:17
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
5
  • The given code was just an example, the command is not always the same and is generated.
    – Kaj
    Dec 22, 2014 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, 2014 at 17:34
  • @Luxo see edited answer
    – Costas
    Dec 22, 2014 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, 2014 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, 2014 at 18:39
-1

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"';
2
  • I'm still having the same errors
    – Kaj
    Dec 22, 2014 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. Dec 22, 2014 at 20:55

You must log in to answer this question.

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