3

I created a snippet to test the here document

$ cat test101.sh
ls
$ bash test101.sh
bmdt.md       brmdh.md  fild.md  test     test101.sh  test2  test5  testfile
breakfast.md  exec  file.md  test.sh  test12      test3  test7

It come to Here document

$ $(cat << EOF
→ ls
→ EOF)
bmdt.md       brmdh.md  fild.md  test     test101.sh  test2  test5  testfile
breakfast.md  exec  file.md  test.sh  test12      test3  test7

It works properly,
Unfortunately, it's not the case of structured command

$ $(cat << EOF
→ for i in *
→ do 
→   stat $i
→ done
→ EOF)
-bash: for: command not found

I tried alternatively

$ bash $(cat << EOF
→ for i in *
→ do
→   stat $i
→ done
→ EOF)
bash: for: No such file or directory

What's the problem not allow the for command working?

  • What are you trying to accomplish by using loop inside heredoc? – user1700494 Oct 28 '18 at 14:14
  • learning to get a better understanding about how heredoc work. @user1700494 – Algebra Oct 28 '18 at 14:16
  • You'd need to escape the special characters, e.g. * -> \*, $i -> \$i – steve Oct 28 '18 at 14:21
  • 1
    @steve The * does not need special treatment as the shell won't do filename globbing on the contents of the document, only expansions. – Kusalananda Oct 28 '18 at 15:09
5

A here-document is a form of redirection. In your commands, you redirect into the cat command, and then try to use the output as a command in a command substitution.

  1. $i will be expanded when the contents of the here-document is formed. This happens long before the loop in the document actually runs. If the i variable is unset, it will expand to an empty string. You may choose to quote the here-document (by quoting the first EOF as 'EOF' or \EOF) so that no expansions are done in it, or to explicitly escape the $ as \$ to protect it from expansion.
  2. The contents of the here-document will be interpreted as a single string with new-line delimited lines. It will not undergo the usual token recognition and other steps involved in the parsing of ordinary commands, but will be split up into individual words since the command substitution is unquoted. In particular, for will not be recognised as a shell keyword. This is why your first failing example fails. To re-evaluate the string, you would have to eval it, which would re-evaluate the string as the shell would have done had it been given on the command line.
  3. The last example would expand to bash followed by a number of words. The first word is for, so bash would expect to run a shell script called for in the current directory, but fails in doing so.

  4. In all examples, bash should also have complained that the here-document was not properly terminated (since the last line is EOF) with a trailing right parenthesis, not EOF), saying something like

    bash: warning: here-document at line 1 delimited by end-of-file (wanted `EOF')
    

    unless you are using an older bash release, like the default one on macOS.

Instead, this would be a better choice of actions as it avoids converting code into a string that needs to be re-interpreted and instead gives the document as a in-line shell script directly to a shell interpreter to execute.

bash <<'END_SCRIPT'
for i in *; do
    printf 'Filename: "%s"\n' "$i"
done
END_SCRIPT

The first of your examples work because it's a simple command.

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.