我有一个包含换行符的字符串。我想通过用两个字符的字符串替换所有换行符来转义该字符串中的所有换行符:“\n”。我怎样才能在 POSIX sh 中做到这一点?
这是目标:
$ printf 'a\nb\nc\nd' | escape_newlines | od -a
0000000 a \ n b \ n c \ n d
141 134 156 142 134 156 143 134 156 144
0000012
我该如何定义escape_newlines
?
我尝试过的方法:
tr
— 问题:无法将单个字符转换为多个字符。awk 'BEGIN{ORS="\\n"} {print}'
— 问题:即使字符串不以换行符结尾,也始终在字符串末尾插入两个字符的字符串“\n”。例子:$ printf 'hi\n' | awk 'BEGIN{ORS="\\n"} {print}' | od -ab 0000000 h i \ n 150 151 134 156 0000004 $ printf 'hi' | awk 'BEGIN{ORS="\\n"} {print}' | od -ab 0000000 h i \ n 150 151 134 156 0000004
sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/\\n/g'
— 问题:如果字符串末尾有换行符,则不会被转换。例子:$ printf 'h\ni' | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/\\n/g' | od -ab 0000000 h \ n i 150 134 156 151 0000004 $ printf 'h\ni\n' | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/\\n/g' | od -ab 0000000 h \ n i nl 150 134 156 151 012 0000005
答案1
尝试awk
使用:
string='x
y
'
new_string=$(
LC_ALL=C awk -- '
BEGIN {
gsub("\n", "\\n", ARGV[1])
printf "%s", ARGV[1]
}' "$string"
)
无论如何,请注意命令替换会删除所有尾随换行符。这里没问题,因为 的输出awk
不包含任何内容,但这意味着我们也可以使用 来print
代替printf "%s"
.
和sed
:
new_string=$(
printf '%s\n' "$string" |
LC_ALL=C sed '
:1
$ ! {
N
b1
}
s/\n/\\n/g'
)
请注意,根据 POSIX,N
在最后一行使用意味着放弃模式空间并退出。 GNU仅在环境中sed
时执行此操作,但在最后一行调用时仍会退出(但仍打印模式空间)。$POSIXLY_CORRECT
N
我们用来LC_ALL=C
避免在用户区域设置的字符映射中解码字符串时出现潜在问题。
sed
是一个文本实用程序,因此它需要文本输入并生成文本输出。不为空且不以换行符结尾的内容不是文本。在这里,我们向输入添加一个换行符,并依靠命令替换来删除sed
输出中添加的换行符。
另请注意,如果输入的行长度以字节为单位大于 LINE_MAX(可以低至 1024),那么它也将成为非文本,并且行为未指定。 IIRC,模式空间也不需要能够容纳超过 10 x LINE_MAX。
该awk
方法也有一些限制,从 ARG_MAX 开始,它在系统上将低于 10 x LINE_MAX。该限制sed
也适用于printf
非内置 shell(例如基于 ksh88 或 pdksh 的 shell)。
shell 变量的大小没有限制,但如果将其导出到环境中,它将针对所有执行的外部命令运行 ARG_MAX 限制。
要处理流,您需要类似的东西:
... | (cat; echo) | LC_ALL=C awk '
{printf "%s", sep $0; sep = "\\n"}'
但请注意,输出不是文本,因此无法由 POSIX 文本实用程序处理。