如何编码任意长的管道链?

时间:2014-08-08 21:41:17

标签: linux bash awk

我对Linux环境有些新意。我全神贯过地寻找答案 - 如果之前有人问过道歉。

我写了一个awk脚本,它运行在一个大文本文件上(11个演出,40列,48M行)。脚本名为" cycle.awk。"它用新版本替换列。它要求数据首先由该特定列排序。为了在所有列上运行脚本,我写了一个像这样的bash命令:

cat input.csv |
    sort -k 22 -t "," | awk -v val=22 -f cycle.awk |
    sort -k 23 -t "," | awk -v val=23 -f cycle.awk |
    sort -k 24 -t "," | awk -v val=24 -f cycle.awk |
    sort -k 25 -t "," | awk -v val=25 -f cycle.awk |
    sort -k 26 -t "," | awk -v val=26 -f cycle.awk |
    sort -k 27 -t "," | awk -v val=27 -f cycle.awk |
    sort -k 28 -t "," | awk -v val=28 -f cycle.awk |
    sort -k 29 -t "," | awk -v val=29 -f cycle.awk |
    sort -k 30 -t "," | awk -v val=30 -f cycle.awk |
    sort -k 31 -t "," | awk -v val=31 -f cycle.awk |
    sort -k 32 -t "," | awk -v val=32 -f cycle.awk |
    sort -k 33 -t "," | awk -v val=33 -f cycle.awk |
    sort -k 34 -t "," | awk -v val=34 -f cycle.awk |
    sort -k 35 -t "," | awk -v val=35 -f cycle.awk |
    sort -k 36 -t "," | awk -v val=36 -f cycle.awk |
    sort -k 37 -t "," | awk -v val=37 -f cycle.awk |
    sort -k 38 -t "," | awk -v val=38 -f cycle.awk |
    sort -k 39 -t "," | awk -v val=39 -f cycle.awk |
    sort -k 40 -t "," | awk -v val=40 -f cycle.awk |
    sort -k 41 -t "," | awk -v val=41 -f cycle.awk > output.csv

我认为必须有一种更优雅的方式来做到这一点。如何编写一个bash脚本,允许我传递我想要应用我的awk脚本的列,然后运行这种管道程序而不需要生成任何临时数据文件?我正在避免临时文件,因为输入文件太大而且我对最佳性能感兴趣。

BTW,脚本如下。它基本上缩短了某些列的值,以便压缩文本文件。有关如何收紧它的任何指示?此过程大约需要10个小时才能运行。

BEGIN{ FS=","; OFS=","; count=1 }
NR == 1 { temp=$val }
{
    if ( temp != $val ) {
        temp=$val;
        count++;
    }
    $val=count
    print $0
}

输入通常看起来像这样:

id,c1
1,abcd
2,efgh
3,abcd
4,abcd
5,efgh

其中相应的输出为:

id,c1
1,1
2,2
3,1
4,1
5,2

从技术上讲,它将按c1排序,但这不是重点。

3 个答案:

答案 0 :(得分:7)

真正正确的答案是重写你的过程,不需要这种管道。但是,如果您想要设置这样的管道,请使用递归函数(管道自身):

process_column() {
  sort -k "$1" -t, | awk -v val="$1" -f cycle.awk
}

process_column_range() {
  local min_col=$1
  local max_col=$2
  if (( min_col < max_col )); then
    process_column "$min_col" \
     | process_column_range "$(( min_col + 1 ))" "$max_col"
  else
    process_column "$min_col"
  fi
}

...然后,调用(注意不需要cat):

process_column_range 22 41 <input.csv >output.csv

答案 1 :(得分:0)

这是使用@robmayoff建议的算法的两遍方法。 可能以awk或本机bash(后者以显着的性能损失)实现,但我会使用Python来提高可读性:

#!/usr/bin/env python

import sys, collections, itertools

input_file_name = sys.argv[1]
col_start = int(sys.argv[2])
col_end = int(sys.argv[3]) + 1

vals = collections.defaultdict(set)

# first pass: build up translation tables for each column
for line in open(input_file_name, 'r'):
  cols = line.split(',') # if this is real CSV, use the Python csv module instead
  for col_no in range(col_start, col_end):
    val = cols[col_no]
    if not val in vals[col_no]: # O(1) operation on sets, vs O(n) on lists
      vals[col_no].add(val)

# interim processing: make sets into dicts w/ values in ascending order
for col_no in vals.iterkeys():
  vals[col_no] = dict(itertools.izip(sorted(list(vals[col_no])),
                                     (str(n) for n in itertools.count())))

# second pass: apply translation tables and print output
for line in open(input_file_name, 'r'):
  cols = line.split(',')
  for col_no in range(col_start, col_end):
    val = cols[col_no]
    cols[col_no] = vals[col_no][val]
  print ','.join(cols)

我不建议将此作为一个可接受的答案,因为它实际上并没有回答提出的问题(构建管道链),但如果重新编号的列中的唯一值的数量很少,它可能对你有用。

调用为:

./process_column_range input.csv 22 41 >output.csv

答案 2 :(得分:0)

以下是基于@robmayoff评论的双程解决方案的建议。它使用gawk(提供内置排序功能)。只要存储所有不同列值所需的空间不能达到数千兆字节范围,它就应该运行良好,并且比进行20种排序和awk传递要快得多。

此示例对第2,3和4列进行排序。

s.awk:

# makemaps() replaces the values in the str-indexed arrays with integers,
# sorted by ascending index value
function makemaps() {
    PROCINFO["sorted_in"]="@ind_str_asc";
    n=1;
    for(i in A2) A2[i]=n++;
    n=1;
    for(i in A3) A3[i]=n++;
    n=1;
    for(i in A4) A4[i]=n++;
    mapsdone=1;
}
BEGIN { FS=","; OFS=","; mapsdone=0; }
{
    if (NR == FNR) {
        # first pass
        # allocate array elements by index. Don't need to assign values yet.
        A2[$2];A3[$3];A4[$4];
    } else {
        # second pass
        # if not yet done, set up arrays' values to be small sequential integers
        if (!mapsdone) makemaps();
        # replace fields with the corresponding small integers
        $2=A2[$2];
        $3=A3[$3];
        $4=A4[$4];
        print $0;
    }
}

输入文件:

1,abcd,red,mercury
2,efgh,orange,mercury
3,abcd,green,venus
4,abcd,blue,earth
5,efgh,red,earth

gawk -f s.awk input input的输出(您需要两次列出输入文件):

1,1,4,2
2,2,3,2
3,1,2,3
4,1,1,1
5,2,4,1

作为一个更大的测试,我使用这个脚本生成了一个4800万行输入文件,其中包含三个12个字符的列:

BEGIN {
    OFS=",";
    for(i=0; i<48000000; i++) {
        print i,
        "aaaaaaaaa" int(1000*rand()),
        "bbbbbbbbb" int(1000*rand()),
        "ccccccccc" int(1000*rand());
    }
}

正在运行/usr/bin/time -v awk -f s.awk input input > output导致

  

命令被定时:“awk -f s.awk输入输入”
  用户时间(秒):139.73
  系统时间(秒):6.12
  这项工作获得的CPU百分比:94%
  经过(挂钟)时间(h:mm:ss或m:ss):2:34.85
  最大驻留集大小(千字节):1896

这是在3.4GHz系统上的单核VMWare CPU上。

因此,对于20列,可能需要17分钟左右,并且耗尽不超过15兆字节的RAM。