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
    Commented 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. Commented Dec 22, 2014 at 18:30
  • what shell are you using? type echo $0
    – tachomi
    Commented 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. Commented 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
    Commented Dec 22, 2014 at 21:08
  • "pathname expansion will affect simple command limits" – Would you mind giving an example for that? Commented 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
    Commented 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
    Commented 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
    Commented Dec 22, 2014 at 17:34
  • @Luxo see edited answer
    – Costas
    Commented 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
    Commented 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
    Commented 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
    Commented 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. Commented Dec 22, 2014 at 20:55

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.