将固定宽度文件转换为 CSV 并删除尾随空格

Tho*_*mas 8 text-processing csv

我的输入文件是:

$ cat -e myfile.txt 
999a bcd efgh555$
 8 z         7  $
1  xx xx xx  48 $
Run Code Online (Sandbox Code Playgroud)

我需要一个列中没有尾随空格的 CSV:

999,a bcd efgh,555
 8,z,7
1,xx xx xx,48
Run Code Online (Sandbox Code Playgroud)

到目前为止,我成功地在需要的地方添加了昏迷:

$ gawk '$1=$1' FIELDWIDTHS="3 10 3" OFS=, myfile.txt
999,a bcd efgh,555
 8 ,z         ,7  
1  ,xx xx xx  ,48 
Run Code Online (Sandbox Code Playgroud)

我怎样才能删除尾随空格?

编辑:数据中可能已经有逗号,所以我需要:(i)将字段用双引号括起来,(ii)使用\"(或""根据RFC 4180)转义字段中可能已经存在的双引号。例如,a,aab"bbccc-> "a,aa","b\"bb","ccc"

  • 我可以使用gawk(不仅awk
  • 我对任何其他解决方案持开放态度(例如perl)。
  • 我需要一个有效的解决方案(例如不是gawk ... | sed ...),因为我有很多大文件要处理。
  • 我知道字段宽度,因此不需要FIELDWIDTHS自动计算。

Sté*_*las 9

perl

\n
<your-file perl -C -lnse 'print map {s/\\s+$//r} unpack "a3a10a3"' -- -,=,\n
Run Code Online (Sandbox Code Playgroud)\n

unpack()进行相当于 gawk 的FIELDWIDTHS处理。

\n

$,,此处与 awk 的等效项OFS设置为,with -,=,where-s导致-var=value参数被理解为分配value$var。或者,您可以省略-s, 并BEGIN{$, = ","}在开头添加一条语句,就像BEGIN{OFS = ","}在 awk 中代替 一样-v OFS=,

\n

-C如果区域设置使用 UTF-8 作为其字符映射,则将输入视为 UTF-8 编码,忽略具有不同多字节字符映射的区域设置,因为这些天几乎从未使用过这些区域设置。

\n

如果要修剪的空格字符都是 ASCII 字符,正如您所发现的,可以通过使用A说明符来进一步简化,而不是a删除unpack()尾随 ASCII 空格(和 NUL):

\n
<your-file perl -C -lnse 'print unpack "A3A10A3"' -- -,=,\n
Run Code Online (Sandbox Code Playgroud)\n

这是根据字符数来考虑宽度的。

\n

对于字节数,请删除-C.

\n

对于字素簇的数量,您可以替换unpack "a3a10a3"/^(\\X{3})(\\X{10})(\\X{3})/

\n

对于显示宽度,考虑到每个字符的宽度(包括零宽、单宽和双宽,但不支持 TAB\xc2\xb9、CR...等控制字符),在 中,zsh可以做:

\n
widths=(3 10 3)\nwhile IFS= read -ru3 line; do\n  csv=()\n  for width in $widths; do\n    field=${(mr[width])line}\n    line=${line#$field}\n    csv+=("${(M)field##*[^[:space:]]}")\n  done\n  print -r -- ${(j[,])csv}\ndone 3< your-file\n
Run Code Online (Sandbox Code Playgroud)\n

r[width] right-pad 并将文本截断为给定宽度的情况下,m这是根据显示宽度而不是字符数来完成的,并${(M)field##*[^[:space:]]}扩展到与模式相关的前导部分,$fieldM就是直到最后一个非-whitespace (${field%%[[:space:]]#}与不需要相同set -o extendedglob)。

\n

这可能会比 慢很多perl

\n

如果您的文件仅包含 ASCII 文本(如示例中所示),则它们应该都是等效的。然后删除-Cfor或将perl区域设置设置为C/ POSIXfor sed//可能会提高性能。gawkperl

\n

在 UTF-8 语言环境中,输入重复 100000 次,这里我得到 1.1 秒perl(变体为 0.34 A,变体为 1.7 \\X),Paul 的为 1.3 秒gawk,zsh 为 31 秒,GNU sed 's/./&,/13;s/./&,/3;s/[[:space:]]*,/,/g;s/[[:space:]]*$//'(标准)为 2.1 秒,1.1 为sed -E 's/^(.{3})(.{10})/\\1,\\2,/;s/\\s+,/,/g;s/\\s+$//'(非标准)。

\n

在 C 语言环境中,分别变为 0.9 (0.27, 1.2)、0.7、31、1.3、0.5。

\n
\n

这些假设字段不包含,"字符。某些 CSV 格式还需要引用带有前导或尾随空格的字段。

\n

要创建正确的 CSV 输出,最简单的方法是使用以下Text::CSV模块perl

\n
<your-file perl -C -MText::CSV -lne '\n  BEGIN{$csv = Text::CSV->new({binary => 1})}\n  $csv->print(*STDOUT, [unpack "A3A10A3"])'\n
Run Code Online (Sandbox Code Playgroud)\n

默认情况下,

\n
    \n
  • 分隔符是,
  • \n
  • 引号是"..."
  • \n
  • """在引号内转义
  • \n
  • 仅引用需要引用的字段
  • \n
\n

但这些可以在new()调用中进行调整。perldoc Text::CSV详情请参阅。

\n
\n

\xc2\xb9 虽然对于 TAB 特别而言,您可以预处理输入以expand将这些 TAB 转换为空格序列;对于其他的,宽度的概念通常很难适用,并且取决于文本发送到的显示设备。

\n

  • @Thomas,请参阅编辑 (2认同)

Pau*_*ant 7

$ cat txx
9  a bcd     55 # <- 1 trailing space here
48 z         7  # <- 2 trailing spaces here
1  xx xx xx  489
aaabbb   bb bccchh

$ awk 'BEGIN { FIELDWIDTHS="3 10 3"; OFS=","; }
{ for (f = 1; f <= NF; ++f) sub (/[[:space:]]*$/, "", $f); print; }' txx
9,a bcd,55
48,z,7
1,xx xx xx,489
aaa,bbb   bb b,ccc
Run Code Online (Sandbox Code Playgroud)