[英]How can I use multiple field separators or multiple awk to process columns
[英]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作为流。 (注意:下面当我说“缓冲区”时,技术上我的意思是“模式空间”): :a
只是一个标签; N
表示将下一行附加到缓冲区; 如果文件结尾( $
)未达到( !
),则分支( b
)返回标签:a
... s
),取代“的所有出现\\n|
”(换行后跟“ |
只用‘’) |
”,对整个( g
) 缓冲 N
)到缓冲区,处理它( s/\\n|/|/
); branches( t
)返回标签:a
仅在替换成功时; 否则打印( P
)并清除/删除( D
)当前缓冲区直到第一个嵌入的换行符......然后流继续。 在一个~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)
总之(带评论),
awk
或perl
(或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.