191

I want to print code into a file using cat <<EOF >>:

cat <<EOF >> brightup.sh
!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
   curr=$((curr+406));
   echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi
EOF

but when I check the file output, I get this:

!/bin/bash
curr=1634
if [  -lt 4477 ]; then
   curr=406;
   echo   > /sys/class/backlight/intel_backlight/brightness;
fi

I tried putting single quotes but the output also carries the single quotes with it. How can I avoid this issue?

codeforester
  • 39,467
  • 16
  • 112
  • 140
UberNate
  • 2,109
  • 2
  • 15
  • 15
  • 7
    You should also fix the shebang. The first line needs to be literally `#!/bin/bash` and nothing else -- the `#!` is what makes it into a valid shebang line, and what comes after it is the path to the interpreter. – tripleee Jan 05 '15 at 05:15
  • 4
    As a late-coming aside, the modern syntax for process substitution is `$(command)` instead of `\`command\``. For obtaining the contents of a file, Bash has `$( – tripleee Jun 26 '15 at 03:26

7 Answers7

295

You only need a minimal change; single-quote the here-document delimiter after <<.

cat <<'EOF' >> brightup.sh

or equivalently backslash-escape it:

cat <<\EOF >>brightup.sh

Without quoting, the here document will undergo variable substitution, backticks will be evaluated, etc, like you discovered.

If you need to expand some, but not all, values, you need to individually escape the ones you want to prevent.

cat <<EOF >>brightup.sh
#!/bin/sh
# Created on $(date # : <<-- this will be evaluated before cat;)
echo "\$HOME will not be evaluated because it is backslash-escaped"
EOF

will produce

#!/bin/sh
# Created on Fri Feb 16 11:00:18 UTC 2018
echo "$HOME will not be evaluated because it is backslash-escaped"

As suggested by @fedorqui, here is the relevant section from man bash:

Here Documents

This type of redirection instructs the shell to read input from the current source until a line containing only delimiter (with no trailing blanks) is seen. All of the lines read up to that point are then used as the standard input for a command.

The format of here-documents is:

      <<[-]word
              here-document
      delimiter

No parameter expansion, command substitution, arithmetic expansion, or pathname expansion is performed on word. If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion. In the latter case, the character sequence \<newline> is ignored, and \ must be used to quote the characters \, $, and `.

user2357112
  • 260,549
  • 28
  • 431
  • 505
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • 2
    I see you marked [How to avoid heredoc expanding variables?](https://stackoverflow.com/q/27920806/1983854) as a duplicate of this one. No objections, only that I would include the Bash docs reference, as I did in mine (which having only 13k visits got almost 100 rep, so it seems to be quite helpful). – fedorqui Jun 06 '18 at 09:57
  • @fedorqui Maybe you want to port your answer to this question actually? Or we can switch the duplicate marking to be the other way around; I just looked at the question score, not the answer scores much. – tripleee Jun 06 '18 at 10:08
  • Mmm what about [merging](https://meta.stackexchange.com/q/158066/209901) them, having this one as the target question? The duplicate question has a good title, only that it is toooo long. However, it somehow got quite a lot of attention out of late (I am getting [some upvotes per month](https://stackoverflow.com/posts/27921346/timeline)). – fedorqui Jun 06 '18 at 10:32
  • @fedorqui I like the concept of merging but I have never seen it happen in practice; if I understand the situation correctly, the mods are simply not able to handle nontrivial merges because the hassles and complexities far outweigh the benefits. What I've done once or twice is delete an useful and upvoted answer and reposting it under a different question, though I can hardly recommend this workaround. – tripleee Jun 06 '18 at 10:53
  • It is hard to tell when it is worth doing. I've done it in a site I mod when a good question was posted without noticing there was another good one from before, both having good answers. Removing my 90+ score answer does not seem a good plan, since it would leave that question orphan. – fedorqui Jun 06 '18 at 11:50
  • In any case, I notice both questions lie on the same problem of quoting EOF. However, this one is because quoting prevents command substitution, while the other is because it prevents parameter expansion. – fedorqui Jun 06 '18 at 11:51
  • This one gets a steady trickle of upvotes as well. I added the man section with a link to your answer. Perhaps this should be brought up on meta? – tripleee Jun 06 '18 at 11:55
35

This should work, I just tested it out and it worked as expected: no expansion, substitution, or what-have-you took place.

cat <<< '
#!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
  curr=$((curr+406));
  echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi' > file # use overwrite mode so that you don't keep on appending the same script to that file over and over again, unless that's what you want. 

Using the following also works.

cat <<< ' > file
 ... code ...'

Also, it's worth noting that when using heredocs, such as << EOF, substitution and variable expansion and the like takes place. So doing something like this:

cat << EOF > file
cd "$HOME"
echo "$PWD" # echo the current path
EOF

will always result in the expansion of the variables $HOME and $PWD. So if your home directory is /home/foobar and the current path is /home/foobar/bin, file will look like this:

cd "/home/foobar"
echo "/home/foobar/bin"

instead of the expected:

cd "$HOME"
echo "$PWD"
Alexej Magura
  • 4,833
  • 3
  • 26
  • 40
  • 2
    A here string in single quotes obviously cannot contain any single quotes, which may be a prohibitive issue. Here documents are the only sane workaround if you have a script which needs to contain both single and double quotes, although that is not of course the case with the OP's simple example. Also, tangentially, here-strings `<<<` are only available starting from Bash 3, and not portable to other shells. – tripleee Jun 26 '15 at 03:23
  • `<<<` is also available in Zsh – Alexej Magura Oct 13 '16 at 16:35
  • 3
    today I learned that you can have the filename immediately follow the opening to the heredoc. Thanks @AlexejMagura! – chaseadamsio Jan 04 '17 at 06:13
30

Or, using your EOF markers, you need to quote the initial marker so expansion won't be done:

#-----v---v------
cat <<'EOF' >> brightup.sh
#!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
   curr=$((curr+406));
   echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi
EOF

IHTH

shellter
  • 36,525
  • 7
  • 83
  • 90
7

I know this is a two year old question, but this is a quick answer for those searching for a 'how to'.

If you don't want to have to put quotes around anything you can simply write a block of text to a file, and escape variables you want to export as text (for instance for use in a script) and not escape one's you want to export as the value of the variable.

#!/bin/bash

FILE_NAME="test.txt"
VAR_EXAMPLE="\"string\""

cat > ${FILE_NAME} << EOF
\${VAR_EXAMPLE}=${VAR_EXAMPLE} in ${FILE_NAME}  
EOF

Will write '"${VAR_EXAMPLE}="string" in test.txt' into test.txt

This can also be used to output blocks of text to the console with the same rules by omitting the file name

#!/bin/bash

VAR_EXAMPLE="\"string\""

cat << EOF
\${VAR_EXAMPLE}=${VAR_EXAMPLE} to console 
EOF

Will output '"${VAR_EXAMPLE}="string" to console'

GCUGreyArea
  • 141
  • 2
  • 7
1

cat with <<EOF>> will create or append the content to the existing file, won't overwrite. whereas cat with <<EOF> will create or overwrite the content.

cat test.txt 
hello

cat <<EOF>> test.txt
> hi
> EOF

cat test.txt 
hello
hi

cat <<EOF> test.txt
> haiiiii
> EOF

cat test.txt
haiiiii
Aneer Geek
  • 2,795
  • 1
  • 16
  • 11
0

some solutions

  1. use cat >> filename <<EOF...EOF & \ escape the ` symbol. ✅
$ cat >> brightup.sh <<EOF
!/bin/bash
curr=\`cat /sys/class/backlight/intel_backlight/actual_brightness\`
if [ $curr -lt 4477 ]; then
   curr=$((curr+406));
   echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi
EOF

enter image description here

  1. use single quotes wrapped <<'EOF', no need escape the ` symbol. ✅
$ cat <<'EOF' >> brightup.sh
!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
   curr=$((curr+406));
   echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi
EOF

enter image description here

xgqfrms
  • 10,077
  • 1
  • 69
  • 68
0

Not sure if doing it without bash counts as an answer.

python -c 'with open("brightup.sh", "w") as fp: fp.write("""
!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
   curr=$((curr+406));
   echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi""")'

or pasting the script into the console (Ctrl+d in the end):

python -c 'import sys; fp= open("brightup.sh", "w"); print("paste script:"); fp.write("".join(sys.stdin.readlines())); fp.close()'
fbraga
  • 711
  • 4
  • 9