如何使用 cat <<EOF >> 命令将代码添加到文件中?

197

我想使用 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

但是当我检查文件输出时,我得到了这个:

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

我尝试使用单引号,但输出的结果也带有单引号。我该如何避免这个问题?


7
你还需要修复shebang。第一行必须是字面上的“#!/bin/bash”,仅此而已 - “#!”是使其成为有效shebang行的原因,而后面的内容则是解释器的路径。 - tripleee
4
作为一个后来才出现的补充说明,现在用于进程替换的现代语法是 $(command) 而不是 \command`。要获取文件的内容,Bash有 $(<file)`。 - tripleee
8个回答

302
你只需要做一个很小的改动;在这个here-document的分界符后面加上单引号。
cat <<'EOF' >> brightup.sh

或者等价地反斜杠转义它:
cat <<\EOF >>brightup.sh

不引用的话,这个文档会进行变量替换,反引号会被评估等等,就像你发现的那样。

如果你需要扩展一些值,但不是全部,你需要逐个转义你想要防止的值。

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

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

根据 @fedorqui 的建议,这里是来自 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 `.

对于一个相关的问题,还可以参考https://dev59.com/iG445IYBdhLWcg3wTIZf#54434993

2
我看到你把“如何避免heredoc扩展变量?”标记为这个问题的重复。没有异议,只是我会包括Bash文档的参考,就像我在我的问题中所做的那样(虽然只有13k次访问,但获得了近100个声望,所以似乎相当有帮助)。 - fedorqui
@fedorqui 也许你想将你的回答移植到这个问题上?或者我们可以将重复标记反过来;我只是看了问题分数,没有看答案分数。 - tripleee
嗯,把它们合并,以这个问题作为目标问题怎么样?重复的问题有一个好标题,只是太长了。然而,最近它在某种程度上引起了相当多的关注(我每个月都会得到一些赞)。 - fedorqui
@fedorqui,我喜欢合并的概念,但我从未在实践中见过它发生;如果我正确理解情况,版主们只是无法处理非常规的合并,因为麻烦和复杂性远大于好处。我偶尔会这样做一两次,即删除一个有用且得到赞同的答案,并在不同的问题下重新发布它,尽管我几乎不能推荐这种解决方法。 - tripleee
无论如何,我注意到这两个问题都涉及到引用EOF的相同问题。然而,这个问题是因为引用阻止了命令替换,而另一个问题是因为它阻止了参数扩展。 - fedorqui
显示剩余2条评论

36

这应该可以工作,我刚刚测试了一下,它按预期工作:没有发生扩展、替换或其他任何操作。

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. 

使用以下方法也可以。

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

值得注意的是,在使用heredocs(如<< EOF)时,会进行替换和变量扩展等操作。因此,可以这样做:
cat << EOF > file
cd "$HOME"
echo "$PWD" # echo the current path
EOF

始终会扩展变量$HOME$PWD。因此,如果您的主目录是/home/foobar,当前路径为/home/foobar/bin,则file将如下所示:

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

预期的结果应该是:

cd "$HOME"
echo "$PWD"

2
在单引号中使用的 Here 字符串显然不能包含任何单引号,这可能是一个禁止使用的问题。 如果您有一个脚本需要同时包含单引号和双引号,那么 Here 文档是唯一明智的解决方法,虽然这在 OP 的简单示例中当然不是这种情况。 而且,顺便说一下,Here-strings <<< 仅从 Bash 3 开始提供,并且无法在其他 shell 中移植。 - tripleee
<<< 在 Zsh 中也可用。 - Alexej Magura
3
今天我了解到,在heredoc开头可以直接跟文件名。谢谢@AlexejMagura的提示! - chaseadamsio

32

或者,使用EOF标记时,您需要引用初始标记,以免进行扩展:

#-----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


1
我可以编辑你的帖子,但为了安全起见,应该是#!/bin/bash而不是!/bin/bash吧? - Matthew Hoggan
@MatthewHoggan:没错,你说得对!感谢你指出来。我现在正在修复它。 - shellter
"'EOF'" 是正确的写法。非常好,谢谢。 - acgbox
✅ 这非常清晰简单。 - xgqfrms

7

我知道这是一个两年前的问题,但这是一个快速回答,供那些正在寻找“如何做”的人使用。

如果您不想在任何内容周围加引号,您可以直接将文本块写入文件,并转义您想要导出为文本的变量(例如用于脚本),并且不转义您想要导出为变量值的内容。

#!/bin/bash

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

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

将'"${VAR_EXAMPLE}="string" in test.txt'写入test.txt文件中

也可以省略文件名将文本块输出到控制台,规则相同

#!/bin/bash

VAR_EXAMPLE="\"string\""

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

将 '"${VAR_EXAMPLE}="string" to console' 输出到控制台。

这个问题实际上是六年前提出的。 - tripleee

1

使用带有<<EOF>>的cat命令将创建或追加内容到现有文件中,不会覆盖原有内容。而使用带有<<EOF>的cat命令将创建或覆盖内容。

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

3
不回答问题 - Unchained
你的分析很奇怪。在这两种情况下,heredoc标记都是<<EOF,而输出重定向中>file>>file之间的不足为奇且有充分文献记录的区别是前者会覆盖文件,而后者则会追加到文件末尾。 - tripleee

0

一些解决方案

  1. 使用 cat >> filename <<EOF...EOF 并且使用 \ 转义 ` 符号。✅
$ 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

使用单引号包裹 <<'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

enter image description here


0

不确定不使用bash是否算作答案。

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""")'

将脚本复制粘贴到控制台中(在结尾处按Ctrl+d):
python -c 'import sys; fp= open("brightup.sh", "w"); print("paste script:"); fp.write("".join(sys.stdin.readlines())); fp.close()'

0
经过多次测试,我找到了一个简单但不明显的解决方案。
*任务:您需要在另一个脚本中生成一个脚本,并将外部变量的值传递到内部脚本中。
--1.外部脚本"test.sh"
#!/bin/bash

OUT_FILE=out.sh
#EXT_MSG=external ## doesnt work
export EXT_MSG=external

rm -f $OUT_FILE

#--2.internal script "out.sh"
cat <<'EOF'>> $OUT_FILE
#!/bin/bash

INT_MSG=internal

MSG1=$INT_MSG
MSG2=$EXT_MSG

echo ${MSG1}
echo ${MSG2}
echo "---"
echo $EXT_MSG

EOF

chmod +x $OUT_FILE
./$OUT_FILE

--3. 输出结果
internal
external
---
external

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接