如何解析多行记录(用awk?)

How to parse multi line records (with awk?)

我想弄清楚如何从 \n\n 分隔的多行记录中提取特定字段。

在这种情况下,它恰好是从 apt-cache 输出的,类似于 DEBIAN 控制文件。查看 apt-cache show "$package"

的输出
Package: caffeine
Priority: optional
Section: misc
Installed-Size: 641
Maintainer: Reuben Thomas <rrt@sc3d.org>
Architecture: all
Version: 2.8.3
Depends: python3:any (>= 3.3.2-2~), python3, gir1.2-gtk-3.0, gir1.2-appindicator3-0.1, python3-xlib, python3-pkg-resources, libnet-dbus-perl
Filename: pool/main/c/caffeine/caffeine_2.8.3_all.deb
Size: 58774
MD5sum: 4438db3f6d1cf43a4f4b49cc7f24cda0
SHA1: e748370ac5ccd7de6fc9466ce0451d2e90d179d4
SHA256: ae303b4e32949cc1e1af80df7217e3406291679e3f18fa8f78a5bbb97504c4f6
Description-en: Prevent the desktop becoming idle in full-screen mode
 Caffeine stops the desktop becoming idle when an application
 is running full-screen. A desktop indicator ‘caffeine-indicator’
 supplies a manual toggle, and the command ‘caffeinate’ can be used
 to prevent idleness for the duration of any command.
Description-md5: 7c14f8adc007b10f6ecafed36260bedb

Package: caffeine
Priority: optional
Section: misc
Installed-Size: 655
Maintainer: Reuben Thomas <rrt@sc3d.org>
Architecture: all
Version: 2.6+555~ubuntu14.04.1
Depends: python:any (<< 2.8), python:any (>= 2.7.5-5~), python, gir1.2-gtk-2.0, gir1.2-appindicator3-0.1, x11-utils, python-dbus
Filename: pool/main/c/caffeine/caffeine_2.6+555~ubuntu14.04.1_all.deb
Size: 58604
MD5sum: 1051c3f7d40d344f986bb632d7436849
SHA1: 5e5f622595e8cbba8fb7468b3cffe2914b0ba110
SHA256: 11c5bbf2d28dcda6a7b82872195f740f1f79521b60d3c9acea3037bf0ab3a60e
Description: Prevent the desktop becoming idle
 Caffeine allows the user to prevent the desktop becoming idle,
 either manually or when certain applications are run. This
 prevents screen-blanking, locking, suspending, and so on.
Description-md5: 738866350e5086e77408d7a9c7ffa59b

Package: caffeine
Status: install ok installed
Priority: optional
Section: misc
Installed-Size: 794
Maintainer: Isaiah Heyer <freshapplepy@gmail.com>
Architecture: all
Version: 2.4.1+478~raring1
Depends: dconf-gsettings-backend | gsettings-backend, python (>= 2.6), python-central (>= 0.6.11), python-xlib, python-appindicator, python-xdg, python-notify, python-kaa-metadata
Description: Caffeine
 A status bar application able to temporarily prevent the activation
 of both the screensaver and the "sleep" powersaving mode.
Description-md5: 1c29acf1ab0f2e6636db29fbde1d14a3
Homepage: https://launchpad.net/caffeine
Python-Version: >= 2.6

我想要的输出是格式为 apt-get download $pkg=$ver -a=$arch 的每条记录一行。基本上是可用软件包的安装命令列表...

到目前为止我得到的是 apt-cache show "$package" | awk '/^Package: / { print } /^Version: / { print } /^Architecture: / { print }' | xargs -n3 | awk '{printf "apt-get download %s=%s -a=%s\n", , , }'

这是实际输出:

apt-get download caffeine=2.8.3 -a=all
apt-get download caffeine=2.6+555~ubuntu14.04.1 -a=all
apt-get download caffeine=2.4.1+478~raring1 -a=all

这是所希望的,但它似乎只是侥幸,因为字段的顺序在这个例子中是一致的。如果字段顺序不同,它会中断。

我可以在 Python 中使用面向对象进行这样的解析,但我很难用一个 awk 命令完成这项工作。我能看到正确执行此操作的唯一方法是将每条记录拆分为单独的 tmp 文件(使用 split 或类似的东西),然后单独解析每个文件(这很简单)。显然,我真的很想避免不必要的 I/O,因为这似乎是 awk 的最佳选择。任何 awk 专业人士都知道如何解决这个问题?我什至愿意接受 Perl 单行代码或使用 bash,但我真的很想学习如何更好地利用 awk。

$ package=sed
$ apt-cache show "$package" | awk '/^Package: /{p=} /^Version: /{v=} /^Architecture: /{a=} /^$/{print "apt-get download "p"="v" -a="a}' 
apt-get download sed=4.2.1-10 -a=amd64

工作原理

  • /^Package: /{p=}

    将包信息保存在变量p中。

  • /^Version: /{v=}

    将版本信息保存在变量v中。

  • /^Architecture: /{a=}

    将架构信息保存在变量a中。

  • /^$/{print "apt-get download "p"="v" -a="a}

    当我们到达空白行时,以所需的形式打印出信息。

    我的 apt-cache 版本总是在每个包后输出一个空行。您的示例输出缺少最后一个空行。如果您的 apt-cache 确实没有生成最后一个空行,那么我们将需要添加更多代码来进行补偿。

    就风格而言,有些人可能更喜欢 printf 而不是 print。在这种情况下,将上面的替换为:

    /^$/{printf "apt-get download %s=%s -a=%s\n",v,p,a}' 
    

我发现处理包含名称到值配对的数据的最佳方法是创建这些对的数组,然后仅通过名称访问这些值:

$ cat tst.awk
BEGIN { RS=""; FS="\n" }
{
    delete n2v
    for (i=1;i<=NF;i++) {
        if ($i !~ /^ /) {
            name = gensub(/:.*/,"","",$i)
            value = gensub(/[^:]+:\s+/,"","",$i)
            n2v[name] = value
        }
    }
    printf "apt-get download %s=%s -a=%s\n",
        n2v["Package"], n2v["Version"], n2v["Architecture"]
}

$ awk -f tst.awk file
apt-get download caffeine=2.8.3 -a=all
apt-get download caffeine=2.6+555~ubuntu14.04.1 -a=all
apt-get download caffeine=2.4.1+478~raring1 -a=all

以上使用了几个 gawk 扩展,但如有必要,很容易适应任何 awk。