![如何截断 tsv/csv 中大列之间的字符?](https://linux22.com/image/200841/%E5%A6%82%E4%BD%95%E6%88%AA%E6%96%AD%20tsv%2Fcsv%20%E4%B8%AD%E5%A4%A7%E5%88%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%AD%97%E7%AC%A6%EF%BC%9F.png)
我有一个 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