如何将多行转换为单行但保留段落

如何将多行转换为单行但保留段落

假设我有一堆(降价)文本,每个句子都在单独的行上(以便在出现拼写错误时更容易进行版本控制)。例子file.txt

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Dictum sit amet justo donec enim diam vulputate.
Nunc faucibus a pellentesque sit amet.

Quis enim lobortis scelerisque fermentum dui faucibus in.
Leo duis ut diam quam nulla porttitor massa id neque.
Vitae tortor condimentum lacinia quis vel eros.

如何将每个段落转换为一行,使其看起来像:

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dictum sit amet justo donec enim diam vulputate. Nunc faucibus a pellentesque sit amet.

Quis enim lobortis scelerisque fermentum dui faucibus in. Leo duis ut diam quam nulla porttitor massa id neque. Vitae tortor condimentum lacinia quis vel eros. Velit euismod in pellentesque massa placerat duis ultricies lacus.

我的想法是查找并替换\n句号.和任何非空白字符之间的换行符\S。我已经弄清楚如何在 regex101 中做到这一点这里但想知道是否有更短的 tr/sed/awk 等效项可以在我的 bash shell 中使用。就像是cat file.txt | ???

答案1

perl有段落模式通过-00 perlruninput标志,因此如果我们用空格替换您的所有内部换行符:

$ wc -l input
       7 input
$ perl -00 -pe 's/\n(?!\Z)/ /g' input | wc -l
       3
$ 

(?!\Z)位是不替换每个段落末尾的换行符,从而保留段落边界。

另一种选择是lex。这揭示了一些棘手的问题,特别是如何处理EOF以及是否始终包含最终换行符(如 POSIX 要求),以及您对段落的定义:恰好两个换行符,还是任何数字?

%%

[\n][\n]+ { printf("%s", yytext); }
\n        { int c = input();
            /* TODO book docs say this should return EOF on EOF ?? */
            if (c == 0) {
                putchar('\n');
                yyterminate();
            } else {
                printf(" %c", c);
            }
          }
<<EOF>>   { putchar('\n'); yyterminate(); }

%%

int main(int argc, char *argv[])
{
    return yylex();
}

它可能需要比

$ make paranlneg
lex  -o lex.paranlneg.c paranlneg.l
egcc -O2 -pipe    -o paranlneg lex.paranlneg.c  -ll
rm -f lex.paranlneg.c
$ perl -E 'say "a\nb\n\nc\nd"' | ./paranlneg
a b

c d
$ 

答案2

如同@thrig 基于 Perl 的答案但使用 GNU Awk:

$ gawk -vRS= '{$1=$1; printf $0 RT}' file.txt
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dictum sit amet justo donec enim diam vulputate. Nunc faucibus a pellentesque sit amet.

Quis enim lobortis scelerisque fermentum dui faucibus in. Leo duis ut diam quam nulla porttitor massa id neque. Vitae tortor condimentum lacinia quis vel eros.

对于快速的解决方案,您可以使用fmt具有适当大宽度值的 Coreutils 实用程序:

fmt -w1000 file.txt

(尽管默认情况下这会在每个句点后添加一个双倍空格)。

答案3

基于GNUsed的方法:

您可以使用 用字符tr替换字符,然后使用将两个或多个连续字符的序列更改为双字符,然后使用将剩余字符替换为空格:<newline><NUL>sed<NUL><newline>tr<NUL>

$ tr '\n' '\0' <file.txt | sed 's/\o000\{2,\}/\n\n/g' | tr '\0' ' ' | sed --null-data 's/ $/\n/'

这里,最后一个sed只需要用新行替换最后剩余的空间即可。

或者(更简洁),您可以指示sed将文件视为一系列以 null 结尾的行(即,sed将其视为单行),并用单个空格替换前面和后面出现的所有单个新行非空格字符:

$ sed --null-data 's/\([^[:space:]]\)\n\([^[:space:]]\)/\1 \2/g' file.txt

这也将保留段落之间的垂直间距,即连续新行的数量。我更喜欢搜索一个非空格字符(而不是点),后跟一个新行,只是为了处理句子不以句号结尾的情况。

相关内容