繁体   English   中英

如何合并多行以根据字段分隔符创建两个记录?

[英]How can I merge multiple lines to create exactly two records based on field separators?

我需要帮助编写Unix脚本循环来处理以下数据:

200250|Wk50|200212|January|20024|Quarter4|2002|2002
|2003-01-12
|2003-01-18
|2003-01-05
|2003-02-01
|2002-11-03
|2003-02-01|
|2003-02-01|||||||
200239|Wk39|200209|October|20023|Quarter3|2002|2002
|2002-10-27
|2002-11-02
|2002-10-06
|2002-11-02
|2002-08-04
|2002-11-02|
|2003-02-01|||||||

我在文本文件中有上述格式的数据。 我需要做的是删除所有包含|行的换行符 作为下一行的第一个字符。 我需要的输出是:

200250|Wk50|200212|January|20024|Quarter4|2002|2002|2003-01-12|2003-01-18|2003-01-05|2003-02-01|2002-11-03|2003-02-01||2003-02-01|||||||
200239|Wk39|200209|October|20023|Quarter3|2002|2002|2002-10-27|2002-11-02 |2002-10-06|2002-11-02|2002-08-04|2002-11-02||2003-02-01|||||||

我需要一些帮助来实现这一目标。 这些shell命令让我做恶梦!

'sed'方法:

sed ':a;N;$!ba;s/\n|/|/g' input.txt

虽然,awk会更快,更容易理解/维护。 我只是把这个例子放在手边(一个用于删除带有sed的尾随换行符的常用解决方案)。

编辑:

为了澄清这个答案(选项#1)和@potong的替代解决方案之间的区别(我实际上更喜欢: sed ':a;N;s/\\n|/|/;ta;P;D' file ),我称之为选项#2:

  • 请注意,这些是sed的许多可能选项中的两个。 我实际上更喜欢非sed解决方案,因为它们通常运行得更快。 但这两个选项值得注意,因为它们演示了两种不同的处理文件的方法:选项#1全部在内存中,选项#2作为流。 (注意:下面当我说“缓冲区”时,技术上我的意思是“模式空间”):
  • 选项#1将整个文件读入内存:
    • :a只是一个标签; N表示将下一行附加到缓冲区; 如果文件结尾( $ )未达到( ! ),则分支( b )返回标签:a ...
    • 再经过整个文件被读入到存储器中,处理用替换命令(缓冲s ),取代“的所有出现\\n| ”(换行后跟“ |只用‘’) | ”,对整个( g ) 缓冲
  • 选项#2一次只处理几行:
    • 读取/追加下一行( N )到缓冲区,处理它( s/\\n|/|/ ); branches( t )返回标签:a仅在替换成功时; 否则打印( P )并清除/删除( D )当前缓冲区直到第一个嵌入的换行符......然后流继续。
  • 选项#1需要更多内存才能运行。 一般来说,与您的文件一样大。 选项#2需要最少的内存; 如此之小我没有费心去看它与之相关的东西(我猜的是一条线的长度。)
  • 选项#1运行得更快。 通常,速度是选项#2的两倍; 但显然这取决于文件和正在做什么。

在一个~500MB的文件中,选项#1的运行速度大约是其两倍(1.5s vs 3.4s),

$ du -h /tmp/foobar.txt
544M    /tmp/foobar.txt

$ time sed ':a;N;$!ba;s/\n|/|/g' /tmp/foobar.txt > /dev/null
real    0m1.564s
user    0m1.390s
sys 0m0.171s

$ time sed  ':a;N;s/\n|/|/;ta;P;D'  /tmp/foobar.txt  > /dev/null 
real    0m3.418s
user    0m3.239s
sys 0m0.163s

同时,选项#1需要大约500MB的内存,选项#2需要不到1MB的内存:

$ ps -F -C sed
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
username  4197 11001 99 172427 558888 1 19:22 pts/10   00:00:01 sed :a;N;$!ba;s/\n|/|/g /tmp/foobar.txt

note: /proc/{pid}/smaps (Pss): 558188 (545M)

选项#2:

$ ps -F -C sed
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
username  4401 11001 99  3468   864   3 19:22 pts/10   00:00:03 sed :a;N;s/\n|/|/;ta;P;D /tmp/foobar.txt

note: /proc/{pid}/smaps (Pss): 236 (0M)

总之(带评论),

  • 如果你有大小未知的文件,没有缓冲的流媒体是一个更好的决定。
  • 如果每一秒都重要,那么缓冲整个文件并立即处理它可能没问题 - 但是ymmv。
  • 我调整shell脚本的个人经验是awkperl (或tr ,但它是最不便携的)甚至bash可能比使用sed更可取。
  • 然而, sed是一个非常灵活和强大的工具,可以快速完成工作,并可以在以后调整。

这是一个awk解决方案:

$ awk 'substr($0,1,1)=="|"{printf $0;next} {printf "\n"$0} END{print""}' data

200250|Wk50|200212|January|20024|Quarter4|2002|2002|2003-01-12|2003-01-18|2003-01-05|2003-02-01|2002-11-03|2003-02-01||2003-02-01|||||||
200239|Wk39|200209|October|20023|Quarter3|2002|2002|2002-10-27|2002-11-02|2002-10-06|2002-11-02|2002-08-04|2002-11-02||2003-02-01|||||||

说明:

Awk隐式循环遍历文件中的每一行。

  • substr($0,1,1)=="|"{printf $0;next}

    如果此行以竖线开始,则打印它(没有最终换行符),然后跳到下一行。 我们在这里使用printf ,而不是更常见的print ,因此除非我们明确要求,否则不会打印换行符。

  • {printf "\\n"$0}

    如果该行没有以竖线开始,则打印换行符然后打印该行(再次没有最终换行符)。

  • END{print""}

    在文件的末尾,打印换行符。

精致

以上打印出文件开头的额外换行符。 如果这是一个问题,那么只需稍作改动就可以消除它:

$ awk 'substr($0,1,1)=="|"{printf $0;next} {printf new $0;new="\n"} END{print""}' data
200250|Wk50|200212|January|20024|Quarter4|2002|2002|2003-01-12|2003-01-18|2003-01-05|2003-02-01|2002-11-03|2003-02-01||2003-02-01|||||||
200239|Wk39|200209|October|20023|Quarter3|2002|2002|2002-10-27|2002-11-02|2002-10-06|2002-11-02|2002-08-04|2002-11-02||2003-02-01|||||||

这可能适合你(GNU sed):

sed ':a;N;s/\n|/|/;ta;P;D' file

这会一次处理文件,而不是@ michael_n的文件,它在处理之前将文件内容篡改到内存中。

你可以简单地通过perl来做到这一点,

$ perl -0777pe 's/\n(?=\|)//g' file
200250|Wk50|200212|January|20024|Quarter4|2002|2002|2003-01-12|2003-01-18|2003-01-05|2003-02-01|2002-11-03|2003-02-01||2003-02-01|||||||
200239|Wk39|200209|October|20023|Quarter3|2002|2002|2002-10-27|2002-11-02|2002-10-06|2002-11-02|2002-08-04|2002-11-02||2003-02-01|||||||
awk -f test.awk input.txt  

test.awk

{
    if($0 ~ /^\|/)
    {
            array[i++] = $0
    }
    else
    {
            for(j=0;j<i;j++)
            {
                    line = line array[j];
            }
            i=0;
            print line
            line = $0;
    }
}
awk -f inp.awk input | sed '/^$/d'

inp.awk

{
    if($0 !~ /^\|/)
     { 
       print line;
       line = $0;
      }
    else
      {
        line = line $0;
      }
 }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM