注意:
- 下面的第一个解决方案反映了 OP 的特定空白处理要求; 通用行继续处理见底部。
- 此处的解决方案符合 POSIX 标准,因此它们应该适用于大多数类 Unix 平台(已在 OSX 和 Linux 上验证)。
-
OP's own solution 表明输入具有 Windows 样式的行尾 (
\r\n)。但是,鉴于问题中没有说明这一点,这里的解决方案仅匹配 Unix 风格的解决方案 (\n)。要匹配\r\n 行结尾,请将\n 替换为'"$(printf '\r')"'\n(原文如此),或者在bash 中,在下面的sed 命令中替换'$'\r''\n。 (使用 GNU sed 您可以简单地使用 \r\n,但 POSIX sed 不会将 \r 识别为转义序列)。
OP's own solution 的更正版本,它还可以正确处理以\ 结尾且位于 空 行之前的行。
sed -e ':a' -e '$!{N;ba' -e '}; s/ \\\n[[:blank:]]*/ /g' filename
-
-e ':a' -e '$!{N;ba' -e '}' 是一个常见的 sed 习语:将所有输入行一次读入模式空间(输入缓冲区)的循环 - BSD sed 需要多个 -e 选项才能使这项工作(或者,多行脚本)。
1234563如果您确实需要处理这种情况,请在上面的s/.../.../ 之前插入G;,这会有效地将另一个换行符附加到模式空间,因此也会导致最后一个\ 被删除。
文本替换命令 s/ \\\n[[:blank:]]*/ /g 然后在所有输入行上运行,并且全局 (g) 替换单个空格的运行,然后是 \ ( \\),然后是换行符 (@ 987654351@),后跟任意数量的空格和/或制表符。 ([[:blank:]]*),并用一个空格 () 替换每个这样的运行。
简而言之:在删除尾随 \ 并从下一行去除前导空格之后,行尾的 <space>\ 会导致该行与 下一行 行连接。
注意:
- 以下解决方案有
awk 和 sed 两种风格。
-
通常,
awk 解决方案更可取,因为它们不会一次读取所有输入,这对于大文件可能会出现问题。 (可以说,它们也更容易理解。)
- 请注意,以下用作示例输入的 here-document 使用 引用 EOF 分隔符 (
<<'EOF') 以保留未修改的字符串;在不引用EOF 的情况下,shell 自己的字符串文字处理将解析嵌入的行继续并在命令看到字符串之前加入行。
通用行继续处理没有空格处理:
这些解决方案只需删除\<newline>序列,然后按原样加入行,没有分隔符;例如,这就是 read 默认所做的。
但是,与read相比,这些解决方案有两个优势:
- Line-interior
\ 实例被单独留下。
-
sed 和 awk 的速度要快得多,而不仅仅是几行输入。
awk解决方案:
awk '/\\$/ { printf "%s", substr($0, 1, length($0)-1); next } 1' <<'EOF'
Line1 starts here\
and ends here.
Line2 starts here, \
continues here,\
and ends here.
EOF
Line1 starts here and ends here.
Line2 starts here, continues here, and ends here.
-
/\\$/ 匹配行尾 ($) 的 \,表示线路延续。
-
substr($0, 1, length($0)-1) 从输入行 $0 中删除尾随 \。
- 通过使用
printf "%s",(修改后的)当前行打印时没有尾随换行符,这意味着接下来的任何打印命令都将直接附加到它,从而有效地连接当前行和下一行。
-
next 完成当前行的处理。
-
1 是一个常见的 awk 习语,是 { print } 的简写,即用于简单地打印输入行(尾随 \n)。
sed解决办法:
$ sed -e ':a' -e '$!{N;ba' -e '}; s/\\\n//g' <<'EOF'
Line1 starts here\
and ends here.
Line2 starts here, \
continues here,\
and ends here.
EOF
Line1 starts here and ends here.
Line2 starts here, continues here, and ends here.
注意最后一行的两个 double 空格,因为所有空格都被保留了。
[不推荐] 纯 shell(例如,bash)解决方案:
以下解决方案非常简单,但不完全可靠并且存在安全风险:它可能导致执行任意命令:
# Store input filename, passed as the 1st argument,
# in variable $file.
file=$1
# Construct a string that results in a valid shell command containing a
# *literal* here-document with *unquoted* EOF delimiter 0x3 - chosen so
# that it doesn't conflict with the input.
#
# When the resulting command is evaluated by `eval`, the *shell itself*
# performs the desired line-continuation processing, BUT:
# '$'-prefixed tokens in the input, including command substitutions
# ('$(...)' and '`...`'), ARE EXPANDED, therefore:
# CAUTION: Maliciously constructed input can result in
# execution of arbitrary commands.
eval "cat <<$(printf '\3')
$(cat "$file")"
具有空白规范化的通用行继续处理:
这些解决方案规范化空格如下:删除\<newline> 之前的任何尾随空格,以及下一个行的前导空格;然后生成的行由一个单个空格连接。
行中的空格不参与行延续保留原样。 后者将这些解决方案与choroba's Perl solution区分开来
awk解决方案
awk '
contd { contd=0; sub(/^[[:blank:]]+/, "") }
/\\$/ { contd=1; sub(/[[:blank:]]*\\$/, ""); printf "%s ", $0; next }
1' <<'EOF'
Line1 starts here \
and ends here.
I am a loner.
Line3 starts here, \
continues here, \
and ends here.
EOF
Line1 starts here and ends here.
I am a loner.
Line3 starts here, continues here, and ends here.
- 变量
contd(在布尔上下文中默认为 0 / false)用作标志,以指示前一行是否表示行继续并带有尾随 \。
- 如果设置了标志(模式
contd),它会立即重置(尽管如果继续的行也在下一行继续,它可能会在下面再次设置),并且从当前行修剪前导空格(sub(/^[[:blank:]]+/, ""));请注意,不将目标变量指定为第三个参数会隐式针对整个输入行 $0。
-
/\\$/ 匹配线路末尾 ($) 处的 \,表示线路延续。
- 因此,设置了标志 (
contd=1),
- 行尾
\ 之前的尾随空格被删除(sub(/[[:blank:]]*\\$/, "") 以及 \ 本身,
- 打印结果时带有尾随空格,但没有换行符,由
printf "%s "提供。
-
next 然后继续下一个输入行,而不处理当前行的进一步命令。
-
1 是一个常见的awk 成语,是{ print } 的简写,即用于简单地打印输入行(尾随\n);请注意,在两种情况下会到达此打印命令:
- 任何不涉及行延续的行,都被打印出来未修改。
- 任何 结束 续行的行(构成续行的一部分,但它们本身并不在下一行继续),由于第一个执行的修改,这些行在打印时删除了前导空格行动。
sed解决方案
$ sed -e ':a' -e '$!{N;ba' -e '}; s/[[:blank:]]*\\\n[[:blank:]]*/ /g' <<'EOF'
Line1 starts here \
and ends here.
I am a loner.
Line3 starts here, \
continues here, \
and ends here.
EOF
Line1 starts here and ends here.
I am a loner.
Line3 starts here, continues here, and ends here.
行尾和行首空白被规范化为单个空格,用于延续涉及的行。
请注意没有尾随 \ 的行是如何在未修改的情况下打印的。