在Bash中向文件添加前导heredoc

7

我使用了:

cat <<<"${MSG}" > outfile

首先要写一条消息到 outfile,然后进一步处理,从我的 awk 脚本附加到那个 outfile

但现在我的程序逻辑已经改变了,所以我必须先通过将行从我的 awk 程序追加到 outfile 中(从我的 bash 脚本外部调用)来填充 outfile,然后作为最后一步,在我的 outfile 头部预先添加 ${MSG} 的 heredoc

如何在我的 bash 脚本中完成这项工作,而不是在 awk 脚本中?

编辑

这是 MSG 的 heredoc

read -r -d '' MSG << EOF
-----------------------------------------------
--   results of processing - $CLIST
--   used THRESHOLD ($THRESHOLD)
-----------------------------------------------
l
EOF
# trick to pertain newline at the end of a message
# see here: http://unix.stackexchange.com/a/20042
MSG=${MSG%l}

注意:使用herestring的cat?在可移植的方式下,应该使用printf '%s\n' "$MSG" > outfile。尽可能避免使用bashisms。 - Jens
3
FYI,你在这里做的事情是必然低效的——“必然”意味着 POSIX 没有提供任何方法(在任何编程语言中)可以在不重写整个文件的情况下在文件前面添加内容。如果这是一个大文件,那么你应该尽量避免频繁执行这种操作。 - Charles Duffy
1
顺便提一下:如果您使用以下内容,即可在不使用 l 技巧来保留尾随换行符的情况下完成操作:IFS= read -r -d '' MSG << EOF ... - mklement0
@mklement0 我需要将它恢复为旧的 IFS 值吗,还是无所谓,因为脚本已经完成了? - branquito
不需要恢复值,因为将变量赋值直接添加到命令之前会将其“本地化”到该命令 - 这意味着(粗略地说),当命令完成时,它会自动恢复为先前的值。 - mklement0
显示剩余2条评论
3个回答

5

使用命令组:

{
    echo "$MSG"
    awk '...'
} > outfile

如果outfile已经存在,你只能使用一个临时文件并将其复制到原始文件上。这是由于所有(?)文件系统实现的方式; 你不能在流中插入前缀。
{
     # You may need to rearrange, depending on how the original
     # outfile is used.
     cat outfile
     echo "$MSG"
     awk '...'
} > outfile.new && mv outfile.new outfile

你可以使用 cat 的另一个非 POSIX 功能——进程替换,这使得任意命令的输出看起来像是 cat 可以处理的文件:

cat <(echo $MSG) outfile <(awk '...') > outfile.new && mv outfile.new outfile

我不能这样做,因为这发生在生成outfile的时刻。我有一个检查outfile是否存在的情况,如果存在,那么我需要在$MSG前面加上一些东西。 - branquito
我确实按照这样做了:cat <<< $MSG outfile > outfile.tmp && mv outfile.tmp outfile,但最终发现outfile是空的,并且在我的目录中仍然有outfile.tmp。这就是我在这里发布问题的原因;) - branquito
我的错..现在我看到了,在mv outfile.tmpoutfile之间输入了>,忽略了这一点,导致我在这里发布..抱歉浪费了你的时间。 - branquito
不知道为什么,但是 cat <<< $MSG outfile > outfile.tmp && mv outfile.tmp outfile 没有在前面加上 $MSG,outfile 在那里,但没有 $MSG 前缀? - branquito
2
<<< 提供标准输入,而 cat 在接收一个或多个文件名参数时会忽略标准输入。 - chepner
哦,原来如此。谢谢。 - branquito

5
您可以使用 awk 在文件开头插入多行字符串:
awk '1' <(echo "$MSG") file

甚至这个 echo 也应该能工作:

echo "${MSG}$(<file)" > file

$MSG 包含多行内容,它是一个类似于横幅的 heredoc 消息。 - branquito
1
MSG变量实际上可以有任意数量的行。awk的substr函数已被用于从前面剥离$'和从末尾剥离\n'(这些是由printf "%q"添加的)。在发布答案之前,我已在我的OSX上进行了测试。 - anubhava
1
+1 对于简单的解决方案,但你应该注意到 (a) awk 通常不是这个工作的正确工具 - 它在这里是有意义的,因为 OP 的特定要求,以及 (b) echo 解决方案只适用于小文件,因为整个字符串是在内存中构建的(对吧?)。此外,听起来 $MSG 已经是 \n 结尾的,所以你应该使用 <(printf '%s' "$MSG") - mklement0
1
@CharlesDuffy:为了结束这个讨论,即使printf %q不再存在混淆中:我分享你的担忧;例如,如果该字符串恰好不包含需要转义的字符(尽管在这种情况下它会),则printf %q的结果不是$'...'所包含的。请注意,GNU awkmawk实际上接受在命令行上嵌入换行符的字符串 - FreeBSD / OSX awk不接受(这实际上是符合POSIX的行为)。awk-符合POSIX的解决方案是将新行替换为'\n'字符串:'-v MSG ="${MSG//$'\n'/\n}"。有关更多信息,请参见http://goo.gl/uT1oh9。 - mklement0
1
非常被低估的答案。谢谢!为了完整起见,echo版本只是输出到标准输出 - 它不会重写原始文件(根据问题)。调整很简单:echo "${MSG}$(<file)" > file - cautionbug
显示剩余5条评论

5

cat 命令行中使用 - 作为占位符,用于指示您希望插入新内容的位置:

{ cat - old-file >new-file && mv new-file old-file; } <<EOF
header
EOF

2
+1 是前缀形式,但使用 cat <<EOF >>old-file 是后缀形式更简单。 - mklement0
1
@branquito,确实,使用herestring会很好。 - Charles Duffy
我最喜欢你的答案,但不确定现在是否可以接受它,因为我之前已经接受了另一个答案,而你的回答是在那之后发布的。 - branquito
1
@branquito,当前的答案没有任何问题,即使这个答案更加简洁,也完全可以保留它。 - Charles Duffy
是的,这就是为什么我说你的解决方案看起来很整洁。无论如何,我从大家身上学到了一些新的重要知识。 - branquito
显示剩余2条评论

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