我有成千上万个制表符分隔的数据文件,每个文件都像:
a0\ta1\ta2\ta3\ta4\ta5\ta6\ta7\ta8\ta9\n
b0\tb1\tb2\tb3\tb4\tb5\tb6\tb7\tb8\tb9\n
...
Run Code Online (Sandbox Code Playgroud)
但是,偶尔会有包含(随机)格式错误的行的文件,例如:
a0\ta1\ta2\ta3_0\n
a3_1\ta4\ta5\ta6\ta7\ta8\ta9\n
b0\tb1\tb2_0\n
b2_1\tb3\tb4\tb5\tb6\tb7\tb8\tb9\n
...
Run Code Online (Sandbox Code Playgroud)
其中a3_0,a3_1(b2_0,b2_1相应)是最初由空格分隔的a3(b2分别)部分.我想\n在一行的末尾用白色空格替换每个行,只有当行太短或太少时\t.目前5似乎是一个安全的门槛.
我经常sed用来做一些修改,比上面简单得多.我想知道是否sed或其他一些命令(比如awk?我还需要学习)可用于快速处理(因为我有很多文件).谢谢.
随着GNU AWK多字符RS和RT(后来-i infile和ENDFILE),并使用逗号,而不是标签可见性:
$ cat file
a0,a1,a2,a3,a4,a5,a6,a7,a8,a9
b0,b1,b2,b3,b4,b5,b6,b7,b8,b9
a0,a1,a2,a3_0
a3_1,a4,a5,a6,a7,a8,a9
b0,b1,b2_0
b2_1,b3,b4,b5,b6,b7,b8,b9
$ awk -v RS='([^,]*,){9}[^\n]*\n' '{$0=RT; sub(/\n$/,"") gsub(/\n/," ")} 1' file
a0,a1,a2,a3,a4,a5,a6,a7,a8,a9
b0,b1,b2,b3,b4,b5,b6,b7,b8,b9
a0,a1,a2,a3_0 a3_1,a4,a5,a6,a7,a8,a9
b0,b1,b2_0 b2_1,b3,b4,b5,b6,b7,b8,b9
Run Code Online (Sandbox Code Playgroud)
上面的[ab-]使用RS将每个记录(而不是记录分隔符)描述为一系列以换行符结尾的10个逗号分隔字段,然后在打印前在每个记录中根据需要替换换行符.
只要改变RS='([^,]*,){9}[^\n]*\n'到RS='([^\t]*\t){9}[^\n]*\n'它与制表符分隔,而不是逗号分隔的领域工作.
要对所有文件进行更改,请添加-i inplace:
awk -i inplace -v RS='...' '...' *
Run Code Online (Sandbox Code Playgroud)
要么:
find ... -exec awk -i inplace -v RS='...' '...' {} +
Run Code Online (Sandbox Code Playgroud)
你实际上甚至不需要硬编码RS,工具可以弄明白,假设每个输入文件中至少有一条完整的行:
$ awk -F',' '
BEGIN { ARGV[ARGC] = ARGV[ARGC-1]; ARGC++ }
NR==FNR { n=(NF>n?NF:n); next }
ENDFILE { RS="([^"FS"]*"FS"){"n-1"}[^\n]*\n" }
{ $0=RT; sub(/\n$/,"") gsub(/\n/," "); print }
' file
a0,a1,a2,a3,a4,a5,a6,a7,a8,a9
b0,b1,b2,b3,b4,b5,b6,b7,b8,b9
a0,a1,a2,a3_0 a3_1,a4,a5,a6,a7,a8,a9
b0,b1,b2_0 b2_1,b3,b4,b5,b6,b7,b8,b9
Run Code Online (Sandbox Code Playgroud)
只要改变-F','以-F'\t'用于制表符分隔.
与POSIX awks相比,上述两个gawk脚本中最接近的等价物是:
$ awk '
{ rec=rec $0 RS }
END{
while ( match(rec,/([^,]*,){9}[^\n]*\n/) ) {
tgt = substr(rec,RSTART,RLENGTH)
sub(/\n$/,"",tgt)
gsub(/\n/," ",tgt)
print tgt
rec = substr(rec,RSTART+RLENGTH)
}
}
' file
a0,a1,a2,a3,a4,a5,a6,a7,a8,a9
b0,b1,b2,b3,b4,b5,b6,b7,b8,b9
a0,a1,a2,a3_0 a3_1,a4,a5,a6,a7,a8,a9
b0,b1,b2_0 b2_1,b3,b4,b5,b6,b7,b8,b9
Run Code Online (Sandbox Code Playgroud)
和:
awk -F',' '
{ rec=rec $0 RS; n=(NF>n?NF:n) }
END{
while ( match(rec,"([^"FS"]*"FS"){"n-1"}[^\n]*\n") ) {
tgt = substr(rec,RSTART,RLENGTH)
sub(/\n$/,"",tgt)
gsub(/\n/," ",tgt)
print tgt
rec = substr(rec,RSTART+RLENGTH)
}
}
' file
a0,a1,a2,a3,a4,a5,a6,a7,a8,a9
b0,b1,b2,b3,b4,b5,b6,b7,b8,b9
a0,a1,a2,a3_0 a3_1,a4,a5,a6,a7,a8,a9
b0,b1,b2_0 b2_1,b3,b4,b5,b6,b7,b8,b9
Run Code Online (Sandbox Code Playgroud)
请注意,那些人在主要处理开始之前将整个文件读入一个字符串,所以如果你的文件太大而无法放入内存中它们就会失败但是你已经告诉我们每个文件都"非常小"所以不应该是一个问题.
要覆盖输入文件,最简单的方法始终是:
awk '{...}' file > tmp && mv tmp file
Run Code Online (Sandbox Code Playgroud)
但在这种情况下你可以做:
awk '{...} END{... print tgt > ARGV[1] ...}' file
Run Code Online (Sandbox Code Playgroud)
这适用于这种情况,因为awk在启动END部分之前已经完成了读取输入文件.不要在脚本的其他地方尝试它.