【问题标题】:Join lines based on a starting value using UNIX commands使用 UNIX 命令根据起始值连接行
【发布时间】:2014-09-01 11:50:57
【问题描述】:

我又来了,还有另一个 UNIX 要求(因为我对 UNIX 的了解仅限于基本命令)。

我有一个看起来像这样的文件(大约有 3000 万行)

123456789012,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
123456789012,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
123456789012,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
234567890123,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
234567890123,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
345678901234,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
345678901234,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
345678901234,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
456789012345,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
567890123456,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
567890123456,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0

最终的输出应该是这样的(没有第一个值在连接部分重复)

123456789012,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
234567890123,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
345678901234,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
456789012345,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
567890123456,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0

但是,如果上面的输出有点复杂,像下面这样的输出也可以。因为我可以把文件加载到Oracle11g中,去掉多余的列。

123456789012,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,123456789012,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,123456789012,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
234567890123,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,234567890123,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
345678901234,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,345678901234,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,345678901234,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
456789012345,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
567890123456,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,567890123456,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0

【问题讨论】:

  • 输入文件是否按第一列排序?
  • 不,不是。但如果有重复的第一个值,它们会一个接一个地出现,而不是文件中的随机点。
  • python 会给你一个比 bash 命令更清晰和面向未来的脚本...
  • @beroe:Python 是一个bash 命令,或者至少是一个可从bash 运行的命令,例如awksedgrepperl 是可从以下位置运行的命令bash.
  • 我通过 Cygwin for Windows 运行这些 UNIX 命令,不认为它支持 Python。但我可以尝试安装 Python 包来执行 Python 代码。

标签: unix awk sed grep


【解决方案1】:

使用awk 就足够了;这是一种控制破坏报告。由于具有相同键的行被组合在一起 - 非常重要的一点 - 它相当简单。

awk -F, '{   if ($1 != saved)
             {
                 if (saved != 0) print saved "," list
                 saved = $1
                 list = ""
             }
             pad = ""
             for (i = 2; i <= NF; i++) { list = list pad $i; pad = "," }
         }
         END { if (saved != 0) print saved, list }'

您可以将数据作为标准输入提供,也可以在最后的单引号之后列出要处理的文件。

样本输出:

123456789012,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
234567890123,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
345678901234,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
456789012345,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
567890123456 PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0

代码使用saved 来跟踪它正在累积的键列值。当键列改变时,打印出保存的值(如果有的话)并为新的行集重新设置。最后,打印出保存的值(如果有的话)。因此,该代码优雅地处理了一个空文件。


Perl 选项

#!/usr/bin/env perl
use strict;
use warnings;
my $saved = "";
my $list;
while (<>)
{
    chomp;
    my($key,$value) = ($_ =~ m/^([^,]+)(,.*)/);
    if ($key ne $saved)
    {
        print "$saved$list\n" if $saved;
        $saved = $key;
        $list = "";
    }
    $list .= $value;
}
print "$saved$list\n" if $saved;

或者,如果你真的想,你可以省去编写循环(并使用严格和警告):

perl -n -e 'chomp;
($key,$value) = ($_ =~ m/^([^,]+)(,.*)/);
if ($key ne $saved)
{
    print "$saved$list\n" if $saved;
    $saved = $key;
    $list = "";
}
$list .= $value;
} END {
print "$saved$list\n" if $saved;'

这可以压缩成一条(相当长的)线。 } END { 是 Perl 的一个怪癖; -n 选项创建一个循环 while (&lt;&gt;) { … } 并将 -e 参数中的脚本插入其中,因此 } END { 中的 } 终止该循环,然后创建一个由 @ 结束的 END 块Perl 提供的 987654334@。是的,记录在案并得到支持;是的,非常奇怪(所以我不会这样做;我会使用首先显示的 Perl 脚本)。

【讨论】:

  • 感谢您的解释!这正是行的显示方式。现在试试这个,会及时通知您!
  • @Jonathan Leffler:你在某处丢失了“PDPCH=2-0”。
  • 啊——错误; for (i = 2; i &lt;= NF; i++)(用&lt;= 代替&lt;)。从 1(在本例中为 2)计数到 NF 而不是从 0 计数到 NF-1。
  • @jaypal 和 TomFrench 都提出了有效的观点。我会用 TMTOWTDI 为自己辩护(半心半意地)——有不止一种方法可以做到——这是 Perl 的座右铭。 我观察到,拒绝您在单个注释中的两个名称前写 @ 的权利(意味着引用不能是对称的)的 SO 规则是荒谬的。我知道只有第一个提到的会收到通知;没关系。我希望使用相同的符号来引用两位评论员的简洁性 - 以及 @ 符号来通知其中一个。该规则还使得在 cmets 中讨论 Perl 数组变得困难。
  • @jaypal 感谢您通知我,但看起来原始评论是针对 Tom French 的,不管是谁 :)
【解决方案2】:

这个 awk 脚本做你想做的事:

BEGIN { FS = OFS = "," }
NR == 1 { a[++n] = $1 }
a[1] != $1 { for(i=1; i<=n; ++i) printf "%s%s", a[i], (i<n?OFS:ORS); n = 1 }
{ a[1] = $1; for(i=2;i<=NF;++i) a[++n] = $i }
END { for(i=1; i<=n; ++i) printf "%s%s", a[i], (i<n?OFS:ORS) }

它将所有具有相同第一列的字段存储在一个数组中。当第一列不同时,它会打印出数组的所有元素。像awk -f join.awk file 一样使用它。

输出:

123456789012,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
234567890123,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
345678901234,PID=1,AID=2,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
456789012345,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0
567890123456,PID=2,AID=1,EQOSID=1,PDPTY=IPV4,PDPCH=2-0,PID=3,AID=8,EQOSID=1,PDPTY=IPV4,PDPCH=2-0

【讨论】:

  • 如果输入一个空的输入(/dev/nullNUL:),它的效果如何?
  • 它打印一个空行,但你为什么要这样做?!
  • 我不会;如果在没有输入的情况下不打印任何内容会更整洁。空文件可能会意外生成。
  • 已编辑以实现您的功能请求:)
【解决方案3】:

这里有一些 Python 选项,如果你决定走那条路……首先适用于多个输入文件和非顺序相同的索引。第二个不会将整个文件读入内存。

(注意,我知道这不是约定俗成的,但我特意为变量使用大写,以明确什么是用户定义的变量,什么是特殊的 python 词。)

#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
concatenate comma-separated values based on first value

Usage: 
   catfile.py *.txt > output.dat

"""
import sys

if len(sys.argv)<2:
    sys.stderr.write(__doc__)
else:
    FileList = sys.argv[1:]
    IndexList = []
    OutDict = {}
    for FileName in FileList:
        with open(FileName,'rU') as FStream:
            for Line in FStream:
                if Line:
                    Ind,TheRest = Line.rstrip().split(",",1)
                    if Ind not in IndexList:
                        IndexList.append(Ind)
                    OutDict[Ind] = OutDict.get(Ind,"") + "," + TheRest

    for Ind in IndexList:
        print Ind + OutDict[Ind]

这是一个不同的版本,它不会将整个文件加载到内存中,但要求相同的索引都按顺序出现,并且只在一个文件上运行:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
concatenate comma-separated values based on first value

Usage: 
   catfile.py *.txt > output.dat

"""
import sys

if len(sys.argv)<2:
    sys.stderr.write(__doc__)
else:
    FileName = sys.argv[1]
    OutString = ''
    PrevInd = ''
    FirstLine = True
    with open(FileName,'rU') as FStream:
        for Line in FStream:
            if "," in Line:
                Ind,TheRest = Line.rstrip().split(",",1)
                if Ind != PrevInd:
                    if not FirstLine:
                        print PrevInd+OutString
                    PrevInd = Ind
                    OutString = TheRest
                    FirstLine = False
                else:
                    OutString += ","+TheRest
        print Ind + OutString

更一般地,您可以通过将它们保存为 catfile.py 然后执行 python catfile.py inputfile.txt &gt; outputfile.txt 来运行它们。或者对于长期解决方案,创建一个scripts 目录,将其添加到您的$PATH,使用chmod u+x catfile.py 使它们可执行,然后您可以从任何目录中键入脚本的名称。但这是您想要研究的另一个主题。

【讨论】:

  • 这会将整个文件读入内存,如果它只有 1 GiB 或更少,这可能无关紧要,但如果文件变得比这大得多,就会开始重要。
  • 文件大小为 3-6 GB...但系统有 32 GB 的 RAM。所以我认为它可以处理它。虽然我不知道如何执行上面的代码!!
  • 保存在catfile.py。以python catfile.py data-file 运行它
【解决方案4】:

一种没有数组的方式:

BEGIN { FS = OFS = "," ; ORS = "" }
{
    if (lid == $1) { $1 = "" ; print $0 }
    else { print sep $0 ; lid = $1 ; sep = "\n" }
}
END { if (NR) print }

注意:如果末尾不需要换行符,请删除 END 块。

【讨论】:

    【解决方案5】:

    这可能对你有用(GNU sed):

    sort file | sed -r ':a;$!N;s/^(([^,]*),.*)\n\2/\1/;ta;P;D'
    

    对文件进行排序(如果需要),然后删除出现重复的换行符和键。

    【讨论】:

      猜你喜欢
      • 2022-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-27
      • 2011-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多