在Unix中,如何在文件开头添加文本?

199

有没有一条Unix命令可以将一些字符串数据添加到文本文件的开头?

类似于:

prepend "to be prepended" text.txt

你是在寻找一个单一的命令,还是一个小脚本/别名命令可以吗? - Levon
4
如果将所有答案合并,这可能是最简洁的方法:<<(echo "to be prepended") < text.txt | sponge text.txt。该命令将文本文件中的内容与准备添加的文本组合起来,并将结果写回到原始文件中,同时确保安全地处理输出。 - Sridhar Sarnobat
这个问题真的激发了很多人的创造力! - Richard Jessop
有很多好的答案。但是如果你有一个复杂的字符串需要嵌套转义,那么将它放入一个文件中并在前面添加文件会更容易一些。请参考https://gist.github.com/LLyaudet/874f2530cec5ec35b5726c0554ef151c。 - Laurent Lyaudet
21个回答

198
printf '%s\n%s\n' "to be prepended" "$(cat text.txt)" >text.txt

3
这是正确的简洁答案: 我建议添加新的一行\n echo -e "要加在前面的内容\n$(cat text.txt)" > text.txt - Vince
47
这个解决方案存在问题……它会将“\n”转换为实际的换行符……如果你将字符串包含“\n”添加到代码文件开头,这将是有问题的。 - Paul Gordon
15
这个操作 (1) 将整个文件加载到内存中,(2) 把整个文件放入单个命令行参数中。对于简单的东西来说可以,但要注意其限制。 - jwd
2
我上次尝试时,我相信这会在非常大的文件上出现问题,在这种情况下,cat 无法在 text.txt 的内容被覆盖之前读取整个文件。如果 echo 命令中的 -e 选项有问题,您可以在 bash 中引用换行符或执行 echo "to be prepended"$'\n'"$(cat text.txt)" - jbo5112
2
@shime,你会接受修复错误的编辑吗?printf '%s\n%s\n' "要预置的内容" "$(cat text.txt)" >text.txt可以避免echo -e的更不幸的副作用。(这仍然不是特别好的做法——取决于在重定向之前执行命令替换的shell——但至少通过这种修复,在通常方式下行为的shell中,它不会在一般情况下进行不需要的修改)。 - Charles Duffy
显示剩余8条评论

185
sed -i.old '1s;^;to be prepended;' inFile
  • -i 写入原地更改,并在给定任何扩展名的情况下进行备份。 (在这种情况下,.old
  • 1s;^;要添加的内容; 将第一行的开头替换为给定的替换字符串。 1 表示作用于第一行,s 表示替换,; 是我们选择的 s 的分隔符(/ 是常见选择,如 s///,但可以使用任何字符),^ 是匹配行开头的正则表达式

如果您想在文件开头添加一个新行,您需要在替换字符串中添加 \n,如下所示:

sed -i.old '1s;^;to be prepended\n;' inFile

30
如果您能在命令中添加一些说明,对于不太熟悉sed的其他人会有所帮助。原作者可能希望在prepend后插入\n,具体取决于他们的需求。很棒的解决方法。 - Levon
是的,解释一下会很好。inFile 的路径中可以包含目录吗? - zakdances
4
@Levon 我完全想要插入一个\n。本应先阅读评论的。该死。 - chishaku
9
请注意,'\n' 只适用于 GNU 版本的 sed,而不适用于 BSD 版本(例如 OSX、FreeBSD 等)。为了在这些系统上更具可移植性,请参考:https://dev59.com/6HM_5IYBdhLWcg3wUxjF - jwd
1
我猜作者在使用 ; 时,本应该使用 /。因为如果你在替换文本中使用了分隔符(例如,在文件路径前缀中),就不能再使用 / 作为分隔符。 - Mark McDonald
显示剩余6条评论

94

进程替换

我很惊讶没有人提到这个。

cat <(echo "before") text.txt > newfile.txt

这种方法可以说比被接受的答案更自然(将内容打印并输入到替换命令中是字典顺序上不直观的)。

...并且借用ryan上面说的,使用 sponge 你就不需要一个临时文件:

sudo apt-get install moreutils
<<(echo "to be prepended") < text.txt | sponge text.txt

编辑:看起来这在 Bourne Shell /bin/sh 中不起作用。


Here String(仅限 zsh)

使用 Here-String - <<<,你可以做到:

<<< "to be prepended" < text.txt | sponge text.txt

6
这个回答对我来说是获胜者。简单易行,仅使用内置功能。做得好! - PiersyP
1
@PiersyP 我同意!这帮了很多忙,谢谢Sridhar-Sarnobat。 - user2278642
2
问题在于只有1个文件,而不是2个文件。因此,答案的第一行并不真正起作用。最后一行可能有效(但需要sponge)。 - VasiliNovikov
1
海绵是做什么的? - Hemanta
1
你的 <<(echo "to be prepended") < text.txt<<< "to be prepended" < text.txt 构造在bash中不起作用,它们需要zsh。 - Anders Kaseorg
显示剩余5条评论

53

这是一种可能性:

(echo "to be prepended"; cat text.txt) > newfile.txt

你可能不容易避免使用中间文件。

其他选择(可能需要费力地进行shell转义):

sed -i '0,/^/s//to be prepended/' text.txt

3
策略1)是我感觉这里所有答案中最直观的。稍微变化一下:cat <(echo "to be prepended") text.txt > newfile.txt。想想看,我不确定我的回答是否相关,所以我发布了一个单独的答案。 - Sridhar Sarnobat
1
第一种方法不会保留文件权限。 - XMB5
保留权限的唯一方法是使用 sponge。 - Sridhar Sarnobat
使用 ( ... ) 生成子shell 是不必要的:shell 可以完全通过重定向组命令 { ... } 的输出来处理。需要注意的是,shell 语法要求在 } 前加上终止分号或换行符,但在 ) 前不需要。 - Alex Shpilkin

28

如果可以替换输入文件:

注意:

  • 这样做可能会产生意想不到的副作用,尤其是可能将符号链接替换为常规文件,最终得到的文件权限可能会有所不同,并且更改文件的创建时间。

  • sed -i(例如Prince John Wesley的回答中所述)试图至少恢复原始权限,但其他限制也适用。

这里有一个简单的替代方法,使用临时文件(它避免了像shime的解决方案那样将整个输入文件读入内存的方式):

{ printf 'to be prepended'; cat text.txt; } > tmp.txt && mv tmp.txt text.txt

使用 组命令 ({ ...; ...; }) 比使用 子 shell ((...; ...)) 稍微更有效率,就像0xC0000022L 的解决方案中所示。

优点包括:

  • 很容易控制新文本是直接附加到第一行还是插入为 新行(只需将\n 添加到 printf 参数即可)。

  • sed 的解决方案不同,它适用于输入文件为空的情况(0 字节)。


如果意图是将一个或多个 完整的行 前置到现有内容中,则可以简化sed 解决方案 (假设输入文件非空):

sedi 函数可以插入整行:

使用 GNUsed

# Prepends 'to be prepended' *followed by a newline*, i.e. inserts a new line.
# To prepend multiple lines, use '\n' as part of the text.
# -i.old creates a backup of the input file with extension '.old'
sed -i.old '1 i\to be prepended' inFile

一种便携的变体,也可在 macOS / BSD 上使用的 sed:

# Prepends 'to be prepended' *followed by a newline*
# To prepend multiple lines, escape the ends of intermediate
# lines with '\'
sed -i.old -e '1 i\
to be prepended' inFile

请注意,\ 后面的字面换行符是必需的。


如果输入文件必须进行原地编辑(保留其 inode 和所有属性):

使用古老的 ed POSIX 实用程序:

注意:

  • ed 无论如何都会首先将整个输入文件读入内存。

要在直接在第一行之前添加内容(与 sed 类似,如果输入文件为空(0 字节),则此方法将不起作用):

ed -s text.txt <<EOF
1 s/^/to be prepended/
w
EOF
  • -s 抑制ed的状态消息。
  • 请注意,命令是以多行here-document(<<EOF\n...\nEOF)形式提供给ed的,即通过标准输入(stdin)传递;默认情况下,在这些文档中执行字符串扩展(shell变量被插值化);引用开头的定界符可抑制此操作(例如:<<'EOF')。
  • 1将第一行设为当前行。
  • s函数在当前行上执行基于正则表达式的字符串替换,就像sed一样;您可以在替换文本中包含literal新行,但它们必须进行\转义。
  • w将结果写回到输入文件(测试时,将w替换为,p打印结果,而不修改输入文件)。

在一个或多个整行之前添加内容

sed类似,i函数会始终向要插入的文本添加一个结尾换行符。

ed -s text.txt <<EOF
0 i
line 1
line 2
.
w
EOF
  • 0 i 立即将当前光标移动到文件开头的第0行并开始插入模式(i)。请注意,行号从1开始计数。
  • 紧接着是要在当前行之前插入的文本,以单独一行的.作为终止符。

23
这将形成输出。- 表示标准输入,它通过来自echo的管道提供。
echo -e "to be prepended \n another line" | cat - text.txt

需要重写文件,因为不能将内容传送回原输入文件,所以需要一个临时文件。

echo "to be prepended" | cat - text.txt > text.txt.tmp
mv text.txt.tmp text.txt

2
这并不会像要求的那样将文本添加到文件text.txt的开头,而是在标准输出上显示它。如果适用于原帖作者,该输出可以被输入到另一个文件中。 - Levon
1
这肯定会打印出“要预置的内容”,然后是“text.txt”的内容。但我想把文本预置到文件中。 - One Two Three
1
如果我有多行文本怎么办?我该如何在其中插入换行符? - One Two Three
这很酷,但bash不喜欢它:$ echo RewriteBase / | cat - web/.htaccess > web/.htaccess cat:web/.htaccess:输入文件是输出文件 - geek-merlin

14

更推荐使用Adam的答案

我们可以更轻松地使用sponge。现在我们不需要创建临时文件并重命名它了。

echo -e "to be prepended \n another line" | cat - text.txt | sponge text.txt

1
这是我唯一有效的解决方案。有趣的是,我的服务器名称是海绵宝宝。 - Ömer An

8
可能没有内置的功能,但你可以很容易地编写自己的代码,像这样:
#!/bin/bash
echo -n "$1" > /tmp/tmpfile.$$
cat "$2" >> /tmp/tmpfile.$$
mv /tmp/tmpfile.$$ "$2"

至少要有类似的东西...


6
+1 是为了在临时文件名中使用 $$(当前PID)。如果要创建通用脚本,则这样做可以防止其他用户在同时运行相同脚本时潜在地覆盖临时文件。 - E Brown
3
在生产代码中,使用 $$ 是不够安全的(会受到谷歌符号链接攻击);但是相对于静态文件名,它肯定更好。 - tripleee
2
只需使用 mktemp - mewa

8

解决方法:

printf '%s\n%s' 'text to prepend' "$(cat file.txt)" > file.txt

请注意,此方法适用于所有类型的输入,因为没有任何扩展。例如,如果您想要在开头添加!@#$%^&*()丑陋的文本\n\t\n,它将正常工作:
printf '%s\n%s' '!@#$%^&*()ugly text\n\t\n' "$(cat file.txt)" > file.txt

最后需要考虑的是在命令替换时删除文件末尾的空格 "$(cat file.txt)"。所有解决方法都比较复杂。如果您想保留file.txt文件末尾的换行符,请参阅此链接:https://dev59.com/zWs05IYBdhLWcg3wIedj#22607352

喜欢它。超级便携,不需要生成新文件。谢谢! - pyb
1
唯一的问题在于,如果file.txt的内容大于可以适合传递给printf的参数列表,则会出现问题。 - Jonathan Leffler

7

另一种使用 sed 的方法:

sed -i.old '1 {i to be prepended
}' inFile

如果要添加的行是多行的:
sed -i.old '1 {i\ 
to be prepended\
multiline
}' inFile

为什么不使用 sed -i '1ito be prepended' inFile 呢?难道这只适用于 GNU sed 吗? - user unknown
@userunknown:在标准的sed中,i命令后面跟着一个反斜杠和一个换行符,每个输入行除了最后一行都以反斜杠结尾。GNU sed允许类似你所问的简写方式,但只有GNU sed才能这样做。 - Jonathan Leffler

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