【问题标题】:How to join lines not starting with specific pattern to the previous line in UNIX?如何将不以特定模式开头的行连接到 UNIX 中的上一行?
【发布时间】:2016-10-23 08:11:15
【问题描述】:

请查看下面的示例文件和所需的输出以了解我在寻找什么。

这可以通过 shell 脚本中的循环来完成,但我很难获得一个 awk/sed 一个衬里。

SampleFile.txt

These are leaves.
These are branches.
These are greenery which gives
oxygen, provides control over temperature
and maintains cleans the air.
These are tigers
These are bears
and deer and squirrels and other animals.
These are something you want to kill
Which will see you killed in the end.
These are things you must to think to save your tomorrow.

期望的输出

These are leaves.
These are branches.
These are greenery which gives oxygen, provides control over temperature and maintains cleans the air.
These are tigers
These are bears and deer and squirrels and other animals.
These are something you want to kill Which will see you killed in the end.
These are things you must to think to save your tomorrow.

【问题讨论】:

  • 模式是什么?没有注释,下一行以小写字符开头?还是只是“不是这些”?
  • 必须是单线吗?
  • 如果您可以在 shell 脚本中执行此操作,请调用该 shell 脚本。那是你的单线。
  • 我是认真的。如果您有解决方案,为什么还需要另一个解决方案?您的 shell 循环解决方案是否太慢了,因为您有大量输入,还是存在其他问题?
  • @Kusalananda:它的文件大小超过 4GB,我正在尝试处理。除了时间因素(一个主要原因),正如您正确提到的,它的好奇心!

标签: bash shell unix awk sed


【解决方案1】:

请尝试以下方法:

awk 'BEGIN {accum_line = "";} /^These/{if(length(accum_line)){print accum_line; accum_line = "";}} {accum_line = accum_line " " $0;} END {if(length(accum_line)){print accum_line; }}' < data.txt

代码由三部分组成:

  1. BEGIN 标记的块在执行其他任何操作之前执行。对全局初始化很有用
  2. END 标记的块在常规处理完成时执行。用来包东西很有用。如果此行开头没有These,就像打印最后收集的数据一样(这种情况)
  3. 其余的是为每一行执行的代码。首先,搜索模式并完成相关的事情。其次,无论字符串内容如何,​​都会完成数据收集。

【讨论】:

  • 谢谢 - 这工作但省略了最后一行。如果您能在我尝试提高 awk 读写能力时解释代码的工作原理,那就太好了。
【解决方案2】:
awk '$1==These{print row;row=$0}$1!=These{row=row " " $0}'

你可以从那里拿走它。空行、分隔符、
其他未指明的行为(未经测试)

【讨论】:

  • 谢谢汤姆!我会检查一下。
  • @Kusalananda 在扩展和解释方面做得更好
【解决方案3】:

不是单行(但请参阅答案的结尾!),而是awk-script:

#!/usr/bin/awk -f

NR == 1     { line = $0 }
/^These/    { print line; line  = $0 }
! /^These/  { line = line " " $0 }
END         { print line }

解释:

我正在累积、建立以“这些”开头的行和不以“这些”开头的行,每当我找到以“这些”开头的下一行时输出已完成的行。

  1. 存储第一行(第一个“记录”)。
  2. 如果该行以“这些”开头,则打印累积的(以前的,现在完成的)行,并将我们目前找到的任何内容替换为当前行。
  3. 如果它不以“这些”开头,则累积该行(即将它与之前读取的不完整行连接起来,中间有一个空格)。
  4. 当没有更多输入时,打印最后累积的(现在完成的)行。

像这样运行:

$ ./script.awk data.in

作为单行:

$ awk 'NR==1{c=$0} /^These/{print c;c=$0} !/^These/{c=c" "$0} END{print c}' data.in

...但是你为什么要在命令行上运行类似的东西是我无法理解的。

EDIT 看到应该查找的是特定字符串“这些”(/^These/)。以前让我的代码在行首查找大写字母 (/^[A-Z]/)。

【讨论】:

  • 太棒了!这行得通,而且我能够彻底理解它是如何工作的(从你的详细解释中)。谢谢!
【解决方案4】:

使用 sed:

sed ':a;N;/\nThese/!s/\n/ /;ta;P;D' infile

导致

These are leaves.
These are branches.
These are greenery which gives oxygen, provides control over temperature and maintains cleans the air.
These are tigers
These are bears and deer and squirrels and other animals.
These are something you want to kill Which will see you killed in the end.
These are things you must to think to save your tomorrow.

这是它的工作原理:

sed '
:a                   # Label to jump to
N                    # Append next line to pattern space
/\nThese/!s/\n/ /    # If the newline is NOT followed by "These", append
                     # the line by replacing the newline with a space
ta                   # If we changed something, jump to label
P                    # Print part until newline
D                    # Delete part until newline
' infile

N;P;D 是在模式空间中保持多行的惯用方式;条件分支部分负责处理我们追加多行的情况。

这适用于 GNU sed;对于其他 sed,例如 Mac OS 中的 sed,必须拆分 oneliner,以便分支和标签在单独的命令中,换行符可能必须转义,我们需要一个额外的分号:

sed -e ':a' -e 'N;/'$'\n''These/!s/'$'\n''/ /;ta' -e 'P;D;' infile

最后一条命令未经测试;请参阅this answer 了解不同 sed 之间的差异以及如何处理它们。

另一种选择是按字面意思输入换行符:

sed -e ':a' -e 'N;/\
These/!s/\
/ /;ta' -e 'P;D;' infile

但是,根据定义,它不再是单线。

【讨论】:

  • 感谢本杰明!正如您正确提到的,这在 GNU 中运行良好,但在 Solaris 中给出以下错误:“标签太长::a;N;/\n这些/!s/\n/ /;ta;P;D”(对于第一个命令)。 "sed: command garbled: N;/" (对于第二个命令)。不过,这对于您提供的解释非常有用。当我的脚本在 solaris 上运行时,我将进一步检查。
  • @instinct246 它可能适用于文学换行符,请参阅答案的补充。
【解决方案5】:

另一个awk,如果你支持多字符RS(gawk有)

$ awk -v RS="These" 'NR>1{$1=$1; print RS, $0}' file

These are leaves.
These are branches.
These are greenery which gives oxygen, provides control over temperature and maintains cleans the air.
These are tigers
These are bears and deer and squirrels and other animals.
These are something you want to kill Which will see you killed in the end.
These are things you must to think to save your tomorrow.

说明 将记录分隔符设置为“这些”,跳过第一条(空)记录。重新分配字段强制awk重构记录;打印记录分隔符和记录的其余部分。

【讨论】:

  • 如果These 出现在一行的中间,那将是不受欢迎的。 OP说他对“以...开头的行”感兴趣。也许您打算使用 RS='(^|\n)These' 或类似的。它还将所有空白链压缩为单个空白字符。也许你的意思是添加-F'\n'
【解决方案6】:
$ awk '{printf "%s%s", (NR>1 ? (/^These/?ORS:OFS) : ""), $0} END{print ""}' file
These are leaves.
These are branches.
These are greenery which gives oxygen, provides control over temperature and maintains cleans the air.
These are tigers
These are bears and deer and squirrels and other animals.
These are something you want to kill Which will see you killed in the end.
These are things you must to think to save your tomorrow.

【讨论】:

    【解决方案7】:

    这是一个避免分支的 sed 程序。我使用 --posix 选项对其进行了测试。诀窍是使用“锚”(文件中没有出现的字符串):

     sed --posix -n '/^These/!{;s/^/DOES_NOT_OCCUR/;};H;${;x;s/^\n//;s/\nDOES_NOT_OCCUR/ /g;p;}'
    

    解释:

    1. 在不以“这些”开头的行的开头写上 DOES_NOT_OCCUR:

      /^这些/!{;s/^/DOES_NOT_OCCUR/;};

    2. 将模式空间附加到保持空间

      H;

    3. 如果最后一行被读取,交换模式空间和保持空间

      ${;x;

    4. 删除模式空间开头的换行符,该换行符是 H 命令在将第一行添加到保留空间时添加的

      s/^\n//;

    5. 用空格替换所有后跟 DOES_NOT_OCCUR 的换行符并打印结果

      s/\nDOES_NOT_OCCUR/ /g;p;}

    请注意,整个文件是在 sed 的进程内存中读取的,但只有 4GB 这应该不是问题。

    【讨论】:

      猜你喜欢
      • 2012-06-25
      • 2016-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-02
      • 2013-09-12
      • 1970-01-01
      相关资源
      最近更新 更多