如何截断 tsv/csv 中大列之间的字符?

如何截断 tsv/csv 中大列之间的字符?

我有一个 csv 文件:

1,abcde aa aaaa aaa aaaa abcde,4
2,efghi ooo oooo ooo oooo efghi,5

我需要它用开头和最后五个字符截断第二列并用三个点填充。如何实现这一目标?

1,abcde ... abcde,4
2,efghi ... efghi,5

答案1

解决办法sed

sed -E 's/(.*,.{5}).*(.{5},.*)/\1...\2/'

如果第二个“列”(字段)是九个或更少的字符,这将使输入保持不变,但即使它恰好是十个字符,也会注入“ ”(即使它不会取代任何字符):

     输入                       输出
9,abcdefghi,z 9,abcdefghi,z
(不用找了)
10,abcdefghij,z 10,abcde...fghij,z
(请注意,这是更长比输入。)

卡斯的回答建议明确检查第二个字段是否足够长以使得替换值得。因为我的答案是做问题的事(注入三个点)而不是它显示的内容(注入空格+三个点+空格,或者保留输入中的空格),如果前五个和后五个之间至少有四个其他字符,我们就会受益。我们可以用这个命令来处理这个问题:

sed -E 's/(.*,.{5}).{4,}(.{5},.*)/\1...\2/'
     输入                     输出
10,abcdefghij,z 10,abcdefghij,z
(不用找了)
13,abcdefghijklm,z 13,abcdefghijklm,z
(仍然没有变化)
14,abcdefghijklmn,z 14,abcde...jklmn,z
(这比输入的内容短一个字符。)
20,abcdefghijklmnopqrst,z 20,abcde...pqrst,z

.{4,}匹配 4 个或更多字符。当然,您可以将 更改4为任何非负整数。例如,要使用建议的 重复 cas 的答案min=20,请使用 .{11,}

答案2

除非 $2 比您要截断的长度(15 个字符:5 个字符 + 空格 + 3 个点 + 空格 + 5 个字符)长,否则这是不值得做的,所以:

$ awk -F, '
  BEGIN {OFS=FS; min=15};
  length($2) > min { $2 = substr($2,1,5)  " ... " substr($2, length($2)-4) }1' input.csv 
1,abcde ... abcde,4
2,efghi ... efghi,5
3,short field,5

$ cat input.csv 
1,abcde aa aaaa aaa aaaa abcde,4
2,efghi ooo oooo ooo oooo efghi,5
3,short field,5

或者,length($2)为每个输入行仅计算一次:(还显示设置 OFS 和最小值的替代方法)

awk -F, -v OFS=, -v min=15 '
  { L=length($2) };
  L > min { $2 = substr($2,1,5)  " ... " substr($2, L-4) }1' input.csv

1 并且可能不值得做,除非它比这个长得多,所以可能至少有大约 20 个字符。

答案3

对于您当前的示例,您所需要的只是:

$ sed 's/ .* / ... /' file
1,abcde ... abcde,4
2,efghi ... efghi,5

或者如果您确实需要只在第二个字段上进行操作,那么:

$ awk 'BEGIN{FS=OFS=","} {sub(/ .* /," ... ",$2)}1' file
1,abcde ... abcde,4
2,efghi ... efghi,5

如果这不是您所需要的全部,那么编辑您的问题以显示更真正具有代表性的示例输入/输出,包括不适用于的情况。

答案4

使用(以前称为 Perl_6)

raku -pe 's/ \, <( (<alnum>**5) .* (<alnum>**5) )> \, /$0 ... $1/;' 

或者

raku -pe 's/ \, <( $<head>=[<alnum>**5] .* $<tail>=[<alnum>**5] )> \, /$<head> ... $<tail>/;' 

输入示例:

1,abcde aa aaaa aaa aaaa abcde,4
2,efghi ooo oooo ooo oooo efghi,5

示例输出(上面的两个代码示例):

1,abcde ... abcde,4
2,efghi ... efghi,5

上述答案使用编程语言,Perl 编程语言家族的成员。上面的两个答案默认假设column_1逗号右侧的前5个字符是<alnum>(字母加下划线加<digits>)。有关如何处理更广泛的字符的信息,请参阅下面的代码。

Raku 使用新的正则表达式引擎,其设计更加强大且更具可读性。在第一个示例中,使用编号捕获 ( $0, $1),而在第二个示例中,使用命名捕获 ( $<head>, $<tail>)。上面代码的亮点包括 1) 对非 alnum 字符进行合理转义\,(这样你就不必猜测)、用方括号对[ … ]正则表达式“原子”进行分组、用( … )括号捕获、从 开始对捕获进行编号$0、用作**min..max通用量词(例如:**5),并使用捕获标记描绘匹配对象的外部文本<( … )>,因此(匹配对象外部)逗号不会无意中被删除。

请注意,上面的答案使用 Raku 的内置<alnum>字符类,它由<alpha>(字母、下划线) 加组成<digits>。但是,您可能希望截断更多种类的字符。您可以尝试<alnum>用定制(自定义和/或枚举)字符类替换内置字符类:<+[\S]-[,]>。自定义字符类<+[\S]-[,]>将接受+[\S]任何非空白字符(例如数字中的小数点)减去逗号,使用 减去逗号-[,]

下面给出了一个合理的结果,例如第1行和第2行被适当缩短,而line_3/column_2(只有4个非逗号字符长)太短而无法进一步截断。 (感谢@cas“短场”灵感):

raku -pe 's/ \, <(( <+[\S]-[,]>**5) .* ( <+[\S]-[,]>**5 ))> \, /$0 ... $1/;'  

输入示例:

1,$2.37 aa aaaa aaa aaaa abcde,1_end
2,##IN: ooo oooo ooo oooo efghi,2_end
3,#OUT, ooo oooo ooo oooo efghi,3_end
4,short field,4_end
5,thin ice,5_end

示例输出:

1,$2.37 ... abcde,1_end
2,##IN: ... efghi,2_end
3,#OUT, ooo oooo ooo oooo efghi,3_end
4,short ... field,4_end
5,thin ice,5_end

https://raku.org

相关内容