如何仅保留特定于列的内容?

时间:2016-01-19 20:41:45

标签: bash text awk duplicates multiple-columns

我一直在四处寻找,但我无法发现任何接近我所寻找的东西。 我想一个例子将首先解释它: 输入:

------------------------------------|
|   List1   |   List2   |   List3   |
|     1     |     2     |     3     |
|     2     |     3     |     4     |
|     3     |     4     |     5     |
|     4     |     5     |     6     |
|     5     |     6     |     7     |
|     6     |     7     |     a     |
|     7     |     8     |     b     |
|     a     |     d     |     c     |

期望的输出:

------------------------------------|
|   List1   |   List2   |   List3   |
|     1     |     8     |     b     |
|           |     d     |     c     |

因此,正如您所看到的,目标是在每列中仅保留表中其他位置未找到的内容。理想情况下,这应适用于任意数量的列。

awk,bash甚至excel中的任何内容都没问题。 到目前为止,我已经玩过awk,但无济于事。

任何帮助将不胜感激。

谢谢大家。

编辑,澄清。 实际输入是我想要比较的不同列表。理想情况下,它们每个都在不同的文件中。现在,我可以轻松地将它们合并以匹配示例中给出的输入,以便答案中提供的代码适用。

7 个答案:

答案 0 :(得分:1)

请尝试Python

import re

with open("./input_file", "rt") as file:
  f = file.readlines()
  cols = [re.findall(r"[\w']+", x) for x in f[3:]]

col1 = set(x[0] for x in cols)
col2 = set(x[1] for x in cols)
col3 = set(x[2] for x in cols)

print col1.difference(col2.union(col3))
print col2.difference(col1.union(col3))
print col3.difference(col1.union(col2))

输出:

set(['1'])
set(['8', 'd'])
set(['c', 'b'])

<强> 修改

增强版本以匹配所需的格式。也应该使用任意数量的列/行。

from __future__ import print_function
import re

with open("./input_file", "rt") as file:
    f = file.readlines()
    for x in f[:2]:
        print(x,end='')
    rows = [re.findall(r"(?<=\|)(.*?)(?:\|)", x) for x in f[2:]]
    col_num, col_width = len(rows[0]), len(rows[0][0])
    cols = [ set(y[x] for y in rows) for x in range(len(rows[0]))]

uniq_cols = []
for col in cols:
    uniq_cols.append(list(col.difference(set().union(*[c for c in cols if c != col]))))

for x in range(max(len(x) for x in uniq_cols)):
    print('|', end='')
    for col in uniq_cols:
        try:
            print(col[x], end='')
            print('|', end='')
        except IndexError:
            print(' '*col_width, end='')
            print('|', end='')
    print('\n', end='')

输出:

------------------------------------|
|   List1   |   List2   |   List3   |
|     1     |     8     |     c     |
|           |     d     |     b     |

答案 1 :(得分:1)

$ cat tst.awk
BEGIN { FS="[[:space:]]*[|][[:space:]]*" }
NR<3 { print; next }
{
    for (i=2;i<NF;i++) {
        cnt[$i]++
        inCells[NR,i] = $i
    }
}
END {
    for (inRowNr=3; inRowNr<=FNR; inRowNr++) {
        for (colNr=2; colNr<NF; colNr++) {
            val = inCells[inRowNr,colNr]
            if ( cnt[val] == 1 ) {
                outRowNr = ++colOutRowNr[colNr]
                outCells[outRowNr,colNr] = val
                numOutRows = (outRowNr > numOutRows ? outRowNr : numOutRows)
            }
        }
    }

    for (outRowNr=1; outRowNr<=numOutRows; outRowNr++) {
        printf "|"
        for (colNr=2; colNr<NF; colNr++) {
            printf "     %s     |", ((outRowNr,colNr) in outCells ? outCells[outRowNr,colNr] : " ")
        }
        print ""
    }

}

$ awk -f tst.awk file
------------------------------------|
|   List1   |   List2   |   List3   |
|     1     |     8     |     b     |
|           |     d     |     c     |

答案 2 :(得分:0)

awk救援!

$ awk 'function abc(x,y,z) 
       {for(k in x) if(!(k in y || k in z)) printf "%s", FS k; 
        print ""
       } 

  NR==1{split($0,h); next} 
       {a[$1];b[$2];c[$3]}
    END{printf "%s", h[1]; abc(a,b,c); 
        printf "%s", h[2]; abc(b,a,c); 
        printf "%s", h[3]; abc(c,a,b) }' file

List1 1
List2 8 d
List3 b c

通常,由于记录的逻辑单元是行(行),因此列操作更难。您可以将其转换回列格式我不确定是否需要。

答案 3 :(得分:0)

您可以使用awk:

对此文件进行2次传递
awk 'FNR==NR{for(i=1; i<=NF; i++) dup[$i]++; next}
  {for(i=1; i<=NF; i++) printf "%s%s", ((dup[$i]>1)? ".":$i), OFS; print ""}' file file |
  column -t

<强>输出:

List1  List2  List3
1      .      .
.      .      .
.      .      .
.      .      .
.      .      .
.      .      .
.      8      b
.      d      c

使用DOT只是为了在输出中显示空白列。

要正确使用格式:

awk 'FNR==NR{
   for(i=1; i<=NF; i++)
      dup[$i]++
   fld=NF
   next
} {
  for(i=1; i<=NF; i++)
     if (dup[$i]<=1) {
        for(j=1; j<i; j++)
           if (dup[$j]>1)
              printf "\t"
        p++
        printf "%s%s", $i, (p && p%fld == 0) ? ORS : OFS
     }
}
END{
  print ""
}' file file

<强>输出:

List1  List2  List3
1      8      b
       d      c

答案 4 :(得分:0)

awk -F '\|'

{   l1[$2]++; l2[$3]++; l3[$4]++ }
END{
    for i in l1 {if(l2[i] > 0 || l3[i] > 0 ) delete l1[i]}
    for i in l2 {if(l1[i] > 0 || l3[i] > 0 ) delete l2[i]}
    for i in l3 {if(l1[i] > 0 || l2[i] > 0 ) delete l3[i]}

    # formatting report is left as an exercise
}

答案 5 :(得分:0)

对于您的示例格式相当严格,这里是一个shell脚本,除了第一行之外,它还会生成您期望的输出。基本上,您将数据读入单独的列,并记录表中的所有数据。然后生成没有重复数据的数据列。然后打印去重复数据。如果您有包含空格的数据,这将会中断,但要解决此问题,您只需调整$ IFS以匹配您的分隔符。

这也是ksh93特有的。我不知道在打击中会有多少打破,但我不喜欢打击那么多。我认为如果你想用另一种语言重新实现它应该是相当可读的,但是在某个地方的Linux或UNIX系统上安装ksh93可能更容易(RHEL和Ubuntu都附带一个包,就像大多数常见系统一样)。

#!/usr/bin/ksh
IFS="${IFS}|" # also split on pipe

[[ -f "${1:-}" ]] || { print -- "'${1:-}' is not a file" >&2; exit 1; }
exec 3<$1

read firstline <&3
read -A headings <&3
# prune first element, due to weird line splitting
headings=("${headings[@]:1}")


# build data structure of all columns and aggregated values
typeset -a data
typeset -A counter
typeset -i row=0
while read -A line
do
  col=0
  #for v in ${line[@]}
  for v in "${line[@]:1}" # ignore false first element
  do
    data[$((col++))][$row]=$v
    (( counter[$v] += 1 ))
  done
  ((row++))
done <&3

# remove duplicated data from columns
row=0
typeset -i maxrows=0
typeset -a deduped
for col in {1..${#data[@]}}
do
  c=$(( col - 1 ))
  row=0
  for v in ${data[$c][@]}
  do
    [[ ${counter[$v]} -eq 1 ]] && deduped[$c][$((row++))]=${v}
  done
  [[ $row -gt $maxrows ]] && maxrows=$row
done

# pad short columns with empty elements and calculate widths
typeset -a widths
for col in {1..${#deduped[@]}}
do
  c=$((col-1))
  widths[$c]=${#headings[$c]} # default to heading width
  while [[ ${#deduped[$c][@]} -lt $maxrows ]]
  do
    deduped[$c][${#deduped[$c][@]}]=''
    [[ ${widths[$c]} -lt ${#v} ]] && widths[$c]=${#v}
  done
  (( widths[$c] += 2 )) # allow for a space on each side
done

# print the table out
# header first
format='|'
for w in "${widths[@]}"
do
  format="${format}%=${w}s|"
done
format="${format}\n"
printf "$format" "${headings[@]}"

# then table
row=0
while [[ $row -lt $maxrows ]]
do
  typeset -a r
  for col in {1..${#deduped[@]}}
  do
    c=$((col-1))
    r[$c]="${deduped[$c][$row]}"
  done
  printf "$format" "${r[@]}"
  ((row++))
done

处理您的测试文件:

$ ./test.ksh testfile
| List1 | List2 | List3 |
|   1   |   8   |   b   |
|       |   d   |   c   |

答案 6 :(得分:0)

此回复主要关注有n列的一般情况,对于任何n&gt; = 1.这使得程序比其他情况稍长。

由于OP引用了Excel,因此该响应还假定数据可用作具有分隔符分隔值的文件(例如TSV或管道分隔)。为了便于阅读,我将假设一个简单的CSV,如下所示:

List1,List2,List3
1,2,3
2,3,4
3,4,5
4,5,6
5,6,7
6,7,a
7,8,b
a,d,c

此响应还假设在输出中,每列中值的排序并不重要。 (另一种情况留给读者练习: - )

最后的假设是我们可以使用输入字段分隔符作为输出字段分隔符(OFS = FS)。

然后是awk程序,它不需要超过标准的awk:

awk -F, '
  # print an m by n matrix, a:
  function printm(a,m,n, i,j, row) {
    for(i=1;i<=m;i++) {
      row=a[i,1];
      for(j=2;j<=n;j++) {row = row OFS a[i,j]}
      print row;
    }
  }

  function maxfields(i)  { if (MAXFIELDS =="" || MAXFIELDS<i ) {MAXFIELDS=i; } }
  function maxcounter(i) { if (MAXCOUNTER=="" || MAXCOUNTER<i) {MAXCOUNTER=i;} }
  # process one line of input:
  function assemble(  i) {
     for(i=1;i<=NF;i++) {
       if ($i in seen) { delete col[$i] }
       else { seen[$i]; col[$i]=i; }
     }
  }
  function finish(  i,x) {
    for(i=1; i<=MAXFIELDS; i++) { counter[i]=1 }
    for (x in col) { a[ counter[col[x]], col[x] ] = x; 
                     maxcounter(counter[col[x]]++); }
    printm(a, MAXCOUNTER, MAXFIELDS)
  }
  BEGIN {OFS=FS}
  NR==1 {print; next;}
  {maxfields(NF); assemble();}
  END { finish(); } '

输出:

List1,List2,List3
1,d,b
,8,c