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 Answers 3
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 \n
ewline 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*
-
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 inword1
orfilename
.Hauke Laging– Hauke Laging2014年12月22日 21:01:56 +00:00Commented 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. Analias
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 thateval
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– mikeserv2014年12月22日 21:08:26 +00:00Commented Dec 22, 2014 at 21:08 -
"pathname expansion will affect simple command limits" – Would you mind giving an example for that?Hauke Laging– Hauke Laging2014年12月22日 21:12:16 +00:00Commented Dec 22, 2014 at 21:12
-
@HaukeLaging -
touch 'echo 1simple command &&' 'echo 2simple commands'; v=echo*; eval $v
; The whole point ofeval
is that it gives the shell's parser another pass at the arguments - that is what is meant by twice-evaluated.mikeserv– mikeserv2014年12月22日 21:17:44 +00:00Commented Dec 22, 2014 at 21:17
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
-
The given code was just an example, the command is not always the same and is generated.Kaj– Kaj2014年12月22日 17:07:05 +00:00Commented 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 itmy_cmd word1 word2 filename
Costas– Costas2014年12月22日 17:34:09 +00:00Commented Dec 22, 2014 at 17:34 -
-
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– Kaj2014年12月22日 18:07:15 +00:00Commented 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– Costas2014年12月22日 18:39:23 +00:00Commented Dec 22, 2014 at 18:39
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"';
-
I'm still having the same errorsKaj– Kaj2014年12月22日 16:56:34 +00:00Commented 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.Gregory Patmore– Gregory Patmore2014年12月22日 20:55:25 +00:00Commented Dec 22, 2014 at 20:55
cmd="grep \"foo bar\" file | grep \"match\""; eval $cmd
. In general you should useeval "$cmd"
but I have no idea how that can cause this error. Please runset -x
before executing the commands and add the output to your question.echo 0ドル