通过 awk 或 sed 按第一列连接行

通过 awk 或 sed 按第一列连接行

awk遇到以下情况该如何使用呢?

我想连接以同一列开头的行。连接后仅保留第一列(在本例中为aaa, wwwhhh

该文件可以用空格或制表符分隔。

输入示例:

aaa bbb ccc ddd NULL NULL NULL
aaa NULL NULL NULL NULL NULL NULL
aaa bbb ccc NULL NULL NULL NULL
www yyy hhh NULL NULL NULL NULL
hhh 111 333 yyy ooo hyy uuuioooy
hhh 111 333 yyy ooo hyy NULL

期望的输出:

aaa bbb ccc ddd NULL NULL NULL NULL NULL NULL NULL NULL NULL bbb ccc NULL NULL NULL NULL
www yyy hhh NULL NULL NULL NULL
hhh 111 333 yyy ooo hyy uuuioooy 111 333 yyy ooo hyy NULL

其背景是我想建立一个非常简单的基于文件的数据库,其中第一列始终是实体的标识符。基于相同标识符列的所有行都被连接起来。

答案1

要使用 awk 获取每行的第一列,您可以执行以下操作:

< testfile awk '{print $1}'
aaa
aaa
aaa
www
hhh
hhh

这些是其余行的键。因此,您可以创建一个哈希表,使用该行的第一列作为键,使用该行的第二列作为值:

< testfile awk '{table[$1]=table[$1] $2;} END {for (key in table) print key " => " table[key];}'
www => yyy
aaa => bbbNULLbbb
hhh => 111111

要获取该行的其余部分(从第 2 列开始),您需要收集所有列:

< testfile awk '{line="";for (i = 2; i <= NF; i++) line = line $i " "; table[$1]=table[$1] line;} END {for (key in table) print key " => " table[key];}'
www => yyy hhh NULL NULL NULL NULL 
aaa => bbb ccc ddd NULL NULL NULL NULL NULL NULL NULL NULL NULL bbb ccc    NULL NULL NULL NULL 
hhh => 111 333 yyy ooo hyy uuuioooy 111 333 yyy ooo hyy NULL 

答案2

其他人可以用 awk 或 sed 回答,但 Python 版本很简单,可能对您有帮助。

#!/usr/bin/env python

input_file = 'input.dat'
in_fh      = open(input_file, 'r')

input_order = []
seen        = {}
for line in in_fh:    
    # Remove the newline character...
    line = line[:-1]

    # Separate the first column from the rest of the line...
    key_col, sep, rest_of_line = line.partition(" ")
    rest_of_line = sep + rest_of_line  

    # If we've seen this key already, concatenate the line...
    if key_col in seen:
        seen[key_col] += rest_of_line
    # ...otherwise, record the ordering, and store the new info
    else:
        input_order.append(key_col)
        seen[key_col] = rest_of_line

in_fh.close()

# Dump the ordered output to stdout
for unique_col in input_order:
    print unique_col + seen[unique_col]

答案3

这是 coreutils 的一个有趣的应用程序,我怀疑它对于大输入不是很有效,因为它为输入中的每一行调用 join。

touch outfile
while read; do
  join -a1 -a2 outfile <(echo $REPLY) > tmp
  mv tmp outfile
done < infile

为了提高效率,保存outfiletmp虚拟磁盘可能会有所帮助。

编辑

或者没有临时文件:

out=""
while read; do
  out=$(join -a1 -a2 <(echo -n "$out") <(echo -n "$REPLY"))
done < infile

echo "$out"

答案4

假设OP通缉保留输入顺序所需输出,那么此处不能使用关联(字符串索引)数组。使用 迭代其键时,输出顺序是不可预测的for (var in array),就像 Perl 的哈希值或 Python 字典一样3.6之前

这是一个注释良好的普通 AWK 解决方案,它保留了键的原始顺序,就像它们首次出现在输入文件中一样。删除重复项其余的列将是一个不同的挑战,但这似乎不是必需的。

这是在 macOS 上使用 BSD 版本的 AWK 进行测试的,并且应该用你拥有的一切工作awk。如果需要,您可以将其作为独立的 shell 脚本运行*(没有人知道它是用 AWK 编写的),前提是您首先chmod +x concat-by-first-col要将其标记为可执行程序。

#!/usr/bin/awk -f
##  concat-by-first-col
##    concatenate values from lines beginning with the same first column
##
##  usage:
##    $ chmod +x concat-by-first-col
##    $ ./concat-by-first-col inputfile > outputfile

BEGIN {
    # default output separator *is* a space, but if you wanted to change it…
    OFS = " "
}

{
    # assuming EVERY input record has AT LEAST the key…
    # append to `keys` if we haven’t seen this key before
    if (!($1 in values))
        keys[length(keys)+1] = $1

    # append second and subsequent columns to what we already have
    for (i = 2; i <= NF; i++)
        # insert an OFS *only* if there’s an existing value for this key
        values[$1] = (values[$1] ? values[$1] OFS : "") $i
}

END {
    # for all the keys, in the order they appeared in the input…
    for (i = 1; i <= length(keys); i++) {
        key = keys[i]
        # a comma stands in for OFS in AWK’s `print` statement
        print key, values[key]
    }
}

作为独立脚本的替代方案,您可以使用开关将程序文本提供给 AWK -f

awk -f concat-by-first-col inputfile > outputfile

如果您还省略了该BEGIN块,则可以OFS在命令行上指定:

awk -v OFS='\t' -f concat-by-first-col inputfile > outputfile

它与所需的输出匹配吗?查看:

echo "aaa bbb ccc ddd NULL NULL NULL
aaa NULL NULL NULL NULL NULL NULL
aaa bbb ccc NULL NULL NULL NULL
www yyy hhh NULL NULL NULL NULL
hhh 111 333 yyy ooo hyy uuuioooy
hhh 111 333 yyy ooo hyy NULL" > input

echo "aaa bbb ccc ddd NULL NULL NULL NULL NULL NULL NULL NULL NULL bbb ccc NULL NULL NULL NULL
www yyy hhh NULL NULL NULL NULL
hhh 111 333 yyy ooo hyy uuuioooy 111 333 yyy ooo hyy NULL" > expected

# uses Bash’s process substitution**; expect no output
diff expected <(./concat-by-first-col input)

*为了原因,如果您使用的是 Linux 和/或您的 AWK 版本不是/usr/bin/awk,您可能需要调整系统上的“shebang”行。

**https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html

相关内容