2

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

pdftotext -nopgbrk -layout file.pdf

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

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

使用cat -vte我看到这在文件中放置选项卡做得非常好....但是,我想请你帮忙的第二个字段有一些不一致之处。

请参阅以下比较以进行说明:

正常/预期结果:

79879 5.6 0.5 MG EN SQ TFK 世界报告 09-24-2004 儿童编辑时间,ORD1915643
79880 5.5 0.5 MG EN SQ TFK 世界报告 10-01-2004 儿童编辑时间,ORD1915643
79881 6.0 0.5 MG EN SQ TFK 世界报告 10-08-2004 儿童编辑时间,ORD1915643
79882 5.5 0.5 MG EN SQ TFK 世界报告 2004 年 10 月 22 日儿童编辑时间,ORD1915643
79883 5.9 0.5 MG EN SQ TFK 世界报告 10-29-2004 儿童编辑时间,ORD1915643

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

72 5.2 3.0 MG EN LS Ramona 和她的父亲 Cleary,Beverly ORD2111460
491 4.8 4.0 MG EN LS Ramona 和她的母亲 Cleary,Beverly ORD1748201
134 5.6 3.0 MG EN LS Ramona Quimby,8 岁 Cleary,Beverly ORD1748201
29 4.7 5.0 MG EN LS 来自 Basil E. Konigsburg 夫人的混合文件,EL ORD1525579

请注意,“smushing”效应可能出现在字段 2 或字段 3 中......并且,字段数与“正常”结果相差 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 和她的父亲 Cleary,Beverly ORD0630871
是孤儿
   491 4.8 4.0 MG EN RP Ramona 和她的母亲 Cleary,Beverly ORD0785414
也是孤儿
   186 4.8 4.0 MG EN RP Ramona Forever Cleary,贝弗利 ORD0630871
永远的孤儿

样本 2:

  79871 5.7 0.5 MG EN SQ TFK 世界报告 03-18-2005 儿童编辑时间,ORD1915643
  79872 5.8 0.5 MG EN SQ TFK 世界报告 04-01-2005 儿童编辑时间,ORD1915643
  79873 6.0 0.5 MG EN SQ TFK 世界报告 04-08-2005 儿童编辑时间,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;
}
4

5 回答 5

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 个空格(如您在问题中提供的输入数据所示)。如果这不是一个安全的假设,您/我们可以围绕这个进行编码。

根据需要调整输出。

于 2012-10-31T18:23:19.913 回答
3

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

编辑:这是一个基于 match() 的比较解决方案,但实际上我现在认为@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>
于 2012-10-31T14:24:55.860 回答
2

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

{ $2 = gensub( /[[:space:]]/, "\t", "g", $2 ); print }
于 2012-10-31T13:33:35.470 回答
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/'
于 2012-10-31T01:49:18.500 回答
1

尝试这样做:

column -t file.txt > newfile.txt
于 2012-10-31T01:36:05.650 回答