在bash中为每行文本添加前缀

16
假设有一个文本文件 a.txt,内容如下:
aaa
bbb
ccc
ddd
我需要在文件中每一行前面加上前缀(例如 myprefix_):
myprefix_aaa
myprefix_bbb
myprefix_ccc
myprefix_ddd

可以使用 shell 中的 sed 命令实现:
sed -i 's/^/myprefix_/' a.txt 
其中,-i 表示直接修改文件,而不是输出到终端;s/^/myprefix_/' 表示将每一行的开头(即 ^)替换成 myprefix_

你可以只使用 bash(而不是调用 sed/awk/perl/python 等外部可执行文件)来完成它,但最好使用最适合此任务的工具,对于这种简单情况,可能是 sedawk - paxdiablo
我完全同意,“awk”看起来有点过度,这就是为什么我要问这个问题。你会用“bash”怎么做呢? - Michael
我不会这样做。我会像@fedorqui展示的那样使用sed - paxdiablo
4个回答

28

使用sed:

$ sed 's/^/myprefix_/' a.txt
myprefix_aaa
myprefix_bbb
myprefix_ccc
myprefix_ddd

这将把以^开头的每一行替换为myprefix_。请注意,^并没有丢失,因此这允许在每行开头添加内容。

你可以通过以下方式使你的awk版本更短:

$ awk '$0="myprefix_"$0' a.txt
myprefix_aaa
myprefix_bbb
myprefix_ccc
myprefix_ddd

或传递值:

$ prefix="myprefix_"
$ awk -v prefix="$prefix" '$0=prefix$0' a.txt
myprefix_aaa
myprefix_bbb
myprefix_ccc
myprefix_ddd

也可以使用nl实现:

$ nl -s "prefix_" a.txt | cut -c7-
prefix_aaa
prefix_bbb
prefix_ccc
prefix_ddd

最后,正如John Zwinck解释的那样,你也可以这样做:

paste -d'' <(yes prefix_) a.txt | head -n $(wc -l a.txt)

在OS X上:

paste -d '\0' <(yes prefix_) a.txt | head -n $(wc -l < a.txt)

1
+1 给 sed 解决方案,使用 awkPerl 就像用热核弹打蚊子一样不必要。 - paxdiablo
1
或者带着恐惧:paste -d'' <(yes prefix_) a | head -n $(wc -l a)。如果paste有一个选项可以在第一个EOF处停止而不是继续直到所有EOF,那将会更好。 - John Zwinck
我不知道yes命令,@JohnZwinck,听起来很酷。然而,它对我不起作用,只显示文件a - fedorqui
1
这是适用于OS X的版本:paste -d '\0' <(yes prefix_) a.txt | head -n $(wc -l < a.txt) - 在Linux上,您可能需要使用''而不是'\0',但其余部分应该可以工作。 - John Zwinck
1
你的 nl 解决方案非常棒,我稍微改进了一下,使其更加稳定和通用,--------------------- nl -w1 -s" $prefix" | cut -d' ' -f2- - Bruce
显示剩余4条评论

16

纯Bash:

while read line
do
    echo "prefix_$line"
done < a.txt

我会在这里使用 read -r line - jno
@jno 如果您解释一下为什么,并提供一个链接,那么您的评论会更有用。 - John Zwinck
例如,在bash中,可以通过help read命令查看read命令的输出。使用-r选项可以使read命令读取“原始”文本,而不处理\\连续符号。通常情况下,这些连续符号不是您想要在流中添加字符串的前缀。
-r选项不允许反斜杠转义任何字符。
- jno
在bash中,可以使用help read命令来查看read命令的输出。例如,使用-r选项可以使read命令读取“原始”文本,而不处理\\连续字符。通常情况下,这些连续字符不是你想要在流中作为字符串前缀的内容。
-r选项:不允许反斜杠转义任何字符。
- undefined
使用 read -r 是有意义的,除非您的实际数据格式将结尾的 \ 视为行续。因此,如果您的数据格式是程序源代码,则 read -r 可能不是您想要的(您可能只想为“逻辑”行而不是“物理”行添加前缀)。 - John Zwinck
1
实际上,任何\X序列都将在没有-r标志的情况下被解释。 是的,这取决于实现者是否决定使用它。 - jno

12

关于这个问题中 awk, sed, 和 bash 的速度,供参考:

bash 中生成一个 800K 的输入文件:

line="12345678901234567890123456789012345678901234567890123456789012345678901234567890"
rm a.txt
for i in {1..10000} ; do
    echo $line >> a.txt
done

那么请考虑bash脚本timeIt

if [ -e b.txt ] ; then
    rm b.txt
fi
echo "Bash:"
time bashtest
rm b.txt
echo
echo "Awk:"
time awktest
rm b.txt
echo
echo "Sed:"
time sedtest

bashtest是什么

while read line
do
    echo "prefix_$line" >> b.txt
done < a.txt

awktest是:

awk '$0="myprefix_"$0' a.txt > b.txt

并且 sedtest 是:

sed 's/^/myprefix_/' a.txt > b.txt

我在我的电脑上得到了以下结果:

Bash:

real    0m0.401s
user    0m0.340s
sys 0m0.048s

Awk:

real    0m0.009s
user    0m0.000s
sys 0m0.004s

Sed:

real    0m0.009s
user    0m0.000s
sys 0m0.004s

看起来使用bash解决方案会慢一些...


1
生成外部程序是 shell 语言的目的。如果您每行都调用 awk 来添加前缀,那就有些过头了。但是一次调用它来处理整个文件是可以的。 - chepner
1
如果bashtest在内存中处理所有行,然后写入输出文件,而不是在每次读取行时附加到输出文件,那么这个测试将更能反映性能差异。 - Psyrus
@Psyrus 同意。感谢您的评论!请随时更新答案并提供新的时间结果。 - Håkon Hægland
我已经调查过了,不会编辑答案,因为根据我的测试,在内存中使用bash进行字符串连接的速度大约是你提出的方法的两倍慢(而且由于字符串连接非常糟糕,这实际上是有道理的)。顺便说一句,这是一个很好的答案,+1。 - Psyrus
1
参考代码如下:for line in $( b.txt;我尝试了各种不同的调整,但结果要么更糟,要么类似。 - Psyrus

3
您可以使用xargs实用程序:
cat file | xargs -d "\n" -L1 echo myprefix_ 

-d选项用于允许输入行带有尾随空格(与-L规范相关)。


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