0

This works fine:

$ eval 'echo "1" > ~/Desktop/in/foo'

But not this:

$ while IFS= read _cmd; do eval "$_cmd"; done < <(cat << EOF
'echo "1" > ~/Desktop/in/foo'
'echo "2" > ~/Desktop/in/bar'
EOF
)
bash: echo "1" > ~/Desktop/in/foo: No such file or directory
bash: echo "2" > ~/Desktop/in/bar: No such file or directory

How to fix it?


update

As suggested, dropping single quotes works, but not for this variation (ditto by surrounding only the filename with single quotes):

$ while IFS= read _cmd; do eval "$_cmd"; done < <(cat << EOF 
echo "1" > ~/Desktop/in/foo (bis)
echo "2" > ~/Desktop/in/bar
EOF
)

Altogether with the provided solution:

"$SHELL" << EOF
echo "1" > ~/Desktop/in/'foo (bis)'
echo "2" > ~/Desktop/in/bar
EOF
Erwann
  • 677

1 Answers1

1

When you run eval 'echo "1" > ~/Desktop/in/foo', the shell removes the single-quotes, then eval evaluates echo "1" > ~/Desktop/in/foo.

When you run eval "$_cmd" (in your pre-update code), the shell expands $_cmd and removes the double quotes, then eval evaluates 'echo "1" > ~/Desktop/in/foo'. Note the single quotes. You don't want eval to see them, remove them from the here document.

In your updated code you probably want eval to evaluate echo "1" > ~/Desktop/in/'foo (bis)', so use this exact line in the here document.


Well, "exact" is not the right word in general. In general << EOF (as opposed to e.g. << 'EOF') expands variables and few other things in the here document (there are none in your example, but in general this is how it works). Then IFS= read _cmd is not IFS= read -r _cmd. Finally eval gets a string which is not necessarily the exact string you used.

Evaluating line by line makes evaluating multi-line commands impossible.

Unless you really want to rely on what << EOF and IFS= read do to the string(s) you provide (and you know what you're doing), just write the code directly in the script. After all, you want the shell interpreting the script to evaluate echo "1" > ~/Desktop/in/foo. It's not even you want some other shell process. There is no need for a here document here.

I guess preprocessing code for another shell process may make sense (in some cases and when you're careful), but then you should use e.g. bash << EOF, not read.


Another thing: the snippet done < <(cat << EOF can be simplified to done << EOF (note this change requires removing the ) from the end of your code).

  • "you probably want eval to evaluate echo "1" > '~/Desktop/in/foo (bis)', so use this exact line in the here document." But it's faulty. – Erwann Mar 19 '22 at 06:23
  • 1
    @Erwann I forgot the tilde must not be quoted. Fixed now, I hope. – Kamil Maciorowski Mar 19 '22 at 06:25
  • I guess you mean bash < <(cat << EOF... instead of bash << EOF, right? (the latter yields bash: syntax error near unexpected token)'`) – Erwann Mar 19 '22 at 06:43
  • 1
    @Erwann No, I mean bash << EOF. There is no ( in it. I think your contraption with cat works because it provides ( matching the ) you had. My guess is you did not remove this ) when using bash << EOF, hence the error. The real fix is in removing the ), not in voodoo with cat. – Kamil Maciorowski Mar 19 '22 at 06:55
  • "There is no need for a here document here." It's to be able to run the code from the command line, as one block. – Erwann Mar 20 '22 at 02:42
  • 1
    @Erwann Still there is no need for a here document here. :) Type {, Enter, then (possibly multi-line) code, place sole } in the last line; hit Enter. Or (in Bash) Ctrl+x, Ctrl+e, type the code, save and exit the editor (interesting variant here, other shells may provide similar functionality). Next time do not fall into XY problem. – Kamil Maciorowski Mar 20 '22 at 07:59