awk有问题:仅将空格转换为一个字段的制表符

时间:2012-10-31 01:33:28

标签: awk

我收到了一份表格数据的PDF文件,我已将其转换为明文进行处理。

pdftotext -nopgbrk -layout file.pdf

这做了相当不错的工作但是使用空格来分隔/分隔列中的字段并且似乎主要对保留视觉布局而不是“结构”布局感兴趣,即没有一致或可靠的分隔符。所以现在我将2个或更多空格转换为标签:

sed -i 's/[[:space:]]\{2,\}/\t/g' file.txt

使用cat -vte我看到这样做在文件中放置标签非常好....但是,第二个字段存在一些不一致之处,我想请你帮忙。 / p>

请参阅以下比较以澄清:

正常/预期结果:

79879   5.6     0.5     MG      EN      SQ      TFK World Report 09-24-2004     Time for Kids Editors,  ORD1915643
79880   5.5     0.5     MG      EN      SQ      TFK World Report 10-01-2004     Time for Kids Editors,  ORD1915643
79881   6.0     0.5     MG      EN      SQ      TFK World Report 10-08-2004     Time for Kids Editors,  ORD1915643
79882   5.5     0.5     MG      EN      SQ      TFK World Report 10-22-2004     Time for Kids Editors,  ORD1915643
79883   5.9     0.5     MG      EN      SQ      TFK World Report 10-29-2004     Time for Kids Editors,  ORD1915643

一些奇怪和不一致的地方:

72      5.2 3.0 MG      EN      LS      Ramona and Her Father   Cleary, Beverly ORD2111460
491     4.8 4.0 MG      EN      LS      Ramona and Her Mother   Cleary, Beverly ORD1748201
134     5.6 3.0 MG      EN      LS      Ramona Quimby, Age 8    Cleary, Beverly ORD1748201
29      4.7     5.0 MG  EN      LS      From the Mixed-Up Files of Mrs. Basil E.        Konigsburg, E.L.        ORD1525579

请注意,“smushing”效果可能出现在字段2或字段3 ... AND中,字段数与'normal'结果相差1或2。

...所以,为了解决这个问题,我尝试了以下内容:

awk -F'\t' 'OFS="\t";$1 ~ /^[[:digit:]]/{print $1,gensub(/[[:space:]]/,"\t","g",$2),$3,$4,$5,$6,$7}' file.txt

这似乎会使每个或至少大多数行加倍并切断字段。

修改 这似乎工作......到目前为止,还在测试。

awk -F'\t' '{$2 = gensub( /[[:space:]]/, "\t", "g", $2 );
             $3 = gensub( /[[:space:]]/, "\t", "g", $3 )}
             {OFS="\t";print}' file.txt

有没有一种简单的方法可以使用awk来解决这个问题?

更新

有些人要求在我的空间标签转换之前代表状态的示例。以下表示前一个样本在文档中的位置附近的示例。看起来一样......除了一个[下面]间隔,另一个[上面]标签。请注意pdftotext在下面的不同示例中处理第2列的方式...有时会拆分,有时会生成一列。

样本1:

    72   5.2 3.0 MG       EN   RP     Ramona and Her Father                     Cleary, Beverly              ORD0630871
are orphans
   491   4.8 4.0 MG       EN   RP     Ramona and Her Mother                     Cleary, Beverly              ORD0785414
are also orphans
   186   4.8 4.0 MG       EN   RP     Ramona Forever                            Cleary, Beverly              ORD0630871
forever the orphan

样本2:

  79871    5.7   0.5   MG   EN    SQ        TFK World Report 03-18-2005         Time for Kids Editors,       ORD1915643
  79872    5.8   0.5   MG   EN    SQ        TFK World Report 04-01-2005         Time for Kids Editors,       ORD1915643
  79873    6.0   0.5   MG   EN    SQ        TFK World Report 04-08-2005         Time for Kids Editors,       ORD1915643

更新2

对Ed的提交进行了以下更改。认为它可以简化,但它的工作原理。它必须允许孤立的行。

$1 ~ /^[[:digit:]]+/{
   for (i=1;i<=6;i++)
      printf "%s\t", $i

   n = split($0,tmp,/  +/)

   for (i=2;i>=0;i--)
      printf "%s\t", tmp[n-i]

   print ""
}
$1 ~ /^[^[:digit:]]+/ {print $0}

也许这更漂亮:

{
        if ($1 ~ /^[[:digit:]]+/) {
                for (i=1;i<=6;i++)
                printf "%s\t", $i

                n = split($0,tmp,/  +/)

                for (i=2;i>=0;i--)
                printf "%s\t", tmp[n-i]

                print ""
        }
        else print $0;
}

5 个答案:

答案 0 :(得分:5)

您的原始awk脚本似乎会使每行加倍,因为OFS="\t"的计算结果为true,因此会打印当前行。将其放在BEGIN{}块中以避免重复:

gawk -F'\t' 'BEGIN{OFS=FS} $1 ~ /^[[:digit:]]/ {print $1,gensub(/[[:space:]]/,"\t","g",$2),$3,$4,$5,$6,$7}' file.txt

请注意,gensub()gawk的一部分,因此不可移植。你可以用这个来实现同样的东西:

awk -F'\t' 'BEGIN{OFS=FS} $1 ~ /^[[:digit:]]/ {gsub(/[[:space:]]/,"\t",$2); print $1,$2,$3,$4,$5,$6,$7}' file.txt

那说...随着你的更新,我可以看到原始数据格式足够好,我们可以按原样处理它。令人烦恼的是你在第2列和第4列之间只有一个空格,或者我们可以简单地使用多个空格作为字段分隔符。但它仍然是一种可预测的输入格式。

似乎对于前6个字段,输入由“任何空格”分隔,对于最后3个字段,它由“两个或多个空格”分隔。考虑到这一点,我们可以使用以下awk来解析您的输入数据:

#!/usr/bin/awk -f

BEGIN {
  FS="  +";
  fmt="----\n1=%s\n2=%s\n3=%s\n4=%s\n5=%s\n6=%s\n7=%s\n8=%s\n9=%s\n";
}

{
  # Grab the right-hand fields, separated by FS
  a[7]=$(NF-2); a[8]=$(NF-1); a[9]=$NF;

  # Then trim the line and grab initial fields, separated by whitespace
  sub(/^ +/, "");
  split($0, easy, /[[:space:]]+/);
  for(i=1;i<=6;i++) {
    a[i]=easy[i+1];
  }

  printf(fmt, a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]);
}

这假设您的第二个最后一个字段和最后一个字段之间的间隔总是超过1个空格(如您在问题中提供的输入数据中所示)。如果这不是一个安全的假设,你/我们可以编写代码。

根据需要调整输出。

答案 1 :(得分:3)

而不是我们从可能破坏您的数据的sed命令的输出开始,在您运行该sed命令之前发布您的数据并让我们从那里开始。我怀疑,既然你说PDF转换工具保留了“视觉布局”,正确的解决方案可能就是简单地使用gawk的FIELDWIDTHS功能,这样你就可以根据字段的宽度解析PDF转换器输出,而不是试图弄清楚表示字段分隔符需要多少空格。

编辑:这是一个基于匹配()的解决方案进行比较,但我现在认为@ghoti是正确的,解决方案比这简单:

$ cat file
    72   5.2 3.0 MG       EN   RP     Ramona and Her Father     Cleary, Beverly    ORD0630871
   491   4.8 4.0 MG       EN   RP     Ramona and Her Mother     Cleary, Beverly    ORD0785414
  79872  5.8  0.5  MG  EN   SQ    TFK World Report 04-01-2005  Time for Kids Editors,  ORD1915643
  79873  6.0  0.5  MG  EN   SQ    TFK World Report 04-08-2005  Time for Kids Editors,  ORD1915643
$
$ cat tst.awk
BEGIN {
   whl = "([[:digit:]]+)"
   dec = "([[:digit:]]+[.][[:digit:]]+)"
   wrd = "([^ ]+)"
   rst = "(.*)"
   s   = "[ ]+"
   fmt = whl s dec s dec s wrd s wrd s wrd s rst
}
{
   match($0,fmt,arr)
   split(arr[7],tmp,/  +/)
   arr[7] = tmp[1]
   arr[8] = tmp[2]
   arr[9] = tmp[3]

   for (i=1;i<=9;i++)
      printf "<%s>", arr[i]
   print ""
}
$
$ awk -f tst.awk file
<72><5.2><3.0><MG><EN><RP><Ramona and Her Father><Cleary, Beverly><ORD0630871>
<491><4.8><4.0><MG><EN><RP><Ramona and Her Mother><Cleary, Beverly><ORD0785414>
<79872><5.8><0.5><MG><EN><SQ><TFK World Report 04-01-2005><Time for Kids Editors,><ORD1915643>
<79873><6.0><0.5><MG><EN><SQ><TFK World Report 04-08-2005><Time for Kids Editors,><ORD1915643>

编辑:是的,这是一个更简单的解决方案,只需打印前6个字段,然后将其余字段拆分为多空格分隔符:

$ cat tst2.awk
{
   for (i=1;i<=6;i++)
      printf "<%s>", $i

   n = split($0,tmp,/  +/)

   for (i=2;i>=0;i--)
      printf "<%s>", tmp[n-i]

   print ""
}
$
$ awk -f tst2.awk file
<72><5.2><3.0><MG><EN><RP><Ramona and Her Father><Cleary, Beverly><ORD0630871>
<491><4.8><4.0><MG><EN><RP><Ramona and Her Mother><Cleary, Beverly><ORD0785414>
<79872><5.8><0.5><MG><EN><SQ><TFK World Report 04-01-2005><Time for Kids Editors,><ORD1915643>
<79873><6.0><0.5><MG><EN><SQ><TFK World Report 04-08-2005><Time for Kids Editors,><ORD1915643>

答案 2 :(得分:2)

而不是{print $1,gensub(/[[:space:]]/,"\t","g",$2),$3,$4,$5,$6,$7},请尝试:

{ $2 = gensub( /[[:space:]]/, "\t", "g", $2 ); print }

答案 3 :(得分:1)

尝试这样做:

column -t file.txt > newfile.txt

答案 4 :(得分:1)

我认为只有末端的第2和第3列实际上可以有空格?

我会尝试像Python这样的东西:

import re
import sys

for line in sys.stdin:
    start = line.rstrip().split(None, 6)
    end = start.pop().rsplit(None, 1)
    mid = re.split('\s\s+', end.pop(0), maxsplit=1)
    print '\t'.join(start + mid + end)

编辑:好的,如果你想坚持使用coreutils / textutils工具,这里的sed脚本与上面的Python大致相同:

#!/bin/sed -f
s/^ *//
s/ \+/\t/
s/ \+/\t/
s/ \+/\t/
s/ \+/\t/
s/ \+/\t/
s/ \+/\t/
s/ \+\([^ ]\+\) *$/\t\1/
s/  \+/\t/

或者,作为一个单行:

sed -e 's/^ *//; s/ \+/\t/; s/ \+/\t/; s/ \+/\t/; s/ \+/\t/; s/ \+/\t/; s/ \+/\t/; s/ \+\([^ ]\+\) *$/\t\1/; s/  \+/\t/'