Bash:集合论

Bash: Set theory

我有以下制表符分隔的 table:

    A   B   C   D   E   F   G   H   I   J
ZO1     X1  X2  X3          X4      X5  X6
ZO2 X7  X8  X9  X10     X11 X12 X13 X14 X15
ZO3 X16 X17 X18 X19         X20     X21 X22
ZO4     X23 X24 X25         X26     X27 X28
ZO5     X29 X30                         
ZO6     X31 X32 X33 X34 X35 X36 X37 X38 X39
ZO7 X40 X41 X42 X43 X44 X45 X46 X47 X48 X49
ZO8     X50 X51 X52         X53     X54 X55

(X## 为随机字符串)

我想提取第 1 列中满足特定条件的值。示例性条件是:检索所有值(第 1 列),这些值在列 B、C、D、G、I、J 中具有非空值,而在其余列 A、E、F、H 中具有空值。

因此示例输出为:

Z01
Z04
Z08

编辑:抱歉输入不当。下面以分号分隔 table;实际输入是制表符分隔的

;A;B;C;D;E;F;G;H;I;J
ZO1;;X1;X2;X3;;;X4;;X5;X6
ZO2;X7;X8;X9;X10;;X11;X12;X13;X14;X15
ZO3;X16;X17;X18;X19;;;X20;;X21;X22
ZO4;;X23;X24;X25;;;X26;;X27;X28
ZO5;;X29;X30;;;;;;;
ZO6;;X31;X32;X33;X34;X35;X36;X37;X38;X39
ZO7;X40;X41;X42;X43;X44;X45;X46;X47;X48;X49
ZO8;;X50;X51;X52;;;X53;;X54;X55

几个简单的方法可以做到这一点。这是一个更像 C 的语法:

awk -F'\t' '{if( =="" && !="" && !="" && !="" && =="" && =="" && !="" && =="" && !="" && !="" ) print }' table_file

以及 awk 本机语法的另一个更压缩的版本,正如 karakfa 在下面的评论中所建议的:

awk -F'\t' '!="" && !="" && !="" && !="" && !="" && !="" &&  == "" {print }' table_file

你会想要这样的东西:

awk -v pres='B,C,D,G,I,J' '
    BEGIN { FS="\t" }
    FNR==1 {
        split(pres,tmp,/,/)
        for (i in tmp) {
            presNames[tmp[i]]
        }
        for (i=2; i<=NF; i++) {
            if ($i in presNames) {
                mustBePresent[i]
            }
        }
        next
    }
    {
        pass = 1
        for (i=1; i<=NF; i++) {
            if ( ($i == "") &&  (i in mustBePresent) ) { pass = 0 }
            if ( ($i != "") && !(i in mustBePresent) ) { pass = 0 }
        }
        if (pass) {
            print 
        }
    }
' file

未经测试,因为您没有提供示例输入,我们可以轻松地copy/paste进行测试。

我实际上喜欢 创建字段的位图,比上面的更好一点,所以这是你在 awk 中的做法:

awk -v pres='B,C,D,G,I,J' '
    BEGIN { FS="\t" }
    FNR==1 {
        split(pres,tmp,/,/)
        for (i in tmp) {
            presNames[tmp[i]]
        }
        req = 1
        for (i=2; i<=NF; i++) {
            req = req ($i in presNames ? 1 : 0)
        }
        next
    }
    {
        act = 1
        for (i=2; i<=NF; i++) {
            act = act ($i == "" ? 0 : 1)
        }
        if (act == req) {
            print 
        }
    }
' file

我喜欢这个,如果您将其整个复制并粘贴到 bash、评论和所有内容中,它将 运行。

tail -n +2 file              `# Grab the bit of the file you car about` \
|  sed 's/;/|;/'           `# Protect the first column`               \
|  sed 's/;[^;][^;]*/1/g' `# Change all the filled values to 1`      \
|  sed 's/;/0/g'            `# Change the empty values to 0`

该命令的输出如下所示:

 ZO1|0111001011
 ZO2|1111011111
 ZO3|1111001011
 ZO4|0111001011
 ZO5|0110000000
 ZO6|0111111111
 ZO7|1111111111
 ZO8|0111001011

所以现在我可以设置我正在寻找的模式。

tail -n +2 file              `# Grab the bit of the file you car about` \
|  sed 's/;/|;/'           `# Protect the first column`               \
|  sed 's/;[^;][^;]*/1/g' `# Change all the filled values to 1`      \
|  sed 's/;/0/g'            `# Change the empty values to 0`           \
|  grep "|0111001011"        `# Grab the match you want`                \
|  sed  's/|.*//'            `# Clear out the garbage`

然后用函数 Id g 泛化它

>> function table_match () {
    cat                          `# Grab the stdin`                     \
    |  sed 's/;/|;/'           `# Protect the first column`           \
    |  sed 's/;[^;][^;]*/1/g' `# Change all the filled values to 1`  \
    |  sed 's/;/0/g'            `# Change the empty values to 0`       \
    |  grep "|"              `# Grab the match you want`            \
    |  sed  's/|.*//'            `# Clear out the garbage`;
}


>> tail -n +2 file | table_match 0111001011
ZO1
ZO4
ZO8

我也可以做其他事情...点外卡...kleene 星...漂亮。

>> tail -n +2 file | table_match .......011
ZO1
ZO2
ZO3
ZO4
ZO5
ZO6
ZO7
ZO8

>> tail -n +2 file | table_match 01*
ZO1 
ZO4 
ZO5 
ZO6 
ZO8 

鉴于:

$ printf "\tA\tB\tC\tD\tE\tF\tG\tH\tI\tJ
ZO1\t\tX1\tX2\tX3\t\t\tX4\t\tX5\tX6
ZO2\tX7\tX8\tX9\tX10\t\tX11\tX12\tX13\tX14\tX15
ZO3\tX16\tX17\tX18\tX19\t\t\tX20\t\tX21\tX22
ZO4\t\tX23\tX24\tX25\t\t\tX26\t\tX27\tX28
ZO5\t\tX29\tX30\t\t\t\t\t\t\t
ZO6\t\tX31\tX32\tX33\tX34\tX35\tX36\tX37\tX38\tX39
ZO7\tX40\tX41\tX42\tX43\tX44\tX45\tX46\tX47\tX48\tX49
ZO8\t\tX50\tX51\tX52\t\t\tX53\t\tX54\tX55\n" > file

在Ruby中:

$ sed -E '1 s/^(.*)$/hdr/' /tmp/file | 
  ruby -e 'require "csv"
           options={:col_sep=>"\t", :headers=>true}
           CSV.parse($<, options){ |r| 
               puts r["hdr"] if ("B|C|D|G|I|J".split("|").map{ |e| r[e]!=nil }.all? \
                             && "A|E|F|H".split("|").map { |e| r[e]==nil }.all?) } '
ZO1
ZO4
ZO8

或者,不那么简洁:

$ sed -E '1 s/^(.*)$/hdr/' /tmp/file | 
ruby -e 'require "csv"
         options={:col_sep=>"\t", :headers=>true}
         CSV.parse($<, options)
            .select { |r| "B|C|D|G|I|J".split("|").map{ |e| r[e]!=nil }.all? }
            .select { |r| "A|E|F|H".split("|").map { |e| r[e]==nil }.all? }
            .map { |r| puts r["hdr"] } '

两种情况:

  1. 使用sed插入hdr字段,因为header行比下面的数据少一个字段;
  2. 使用CSV模块读取修改后的文件;
  3. 空白字段在 CSV 模块中分配 nil。将其用于 select 您描述的逻辑。

使用 gbtimmon used 的真相 table 方法,在 Ruby:

$ sed -E '1 s/^(.*)$/hdr/' file |
ruby -e 'require "csv"
        options={:col_sep=>"\t", :headers=>true}
        tt=CSV.parse($<, options)
            .map { |r| [r[0], r[1..-1].map { |e| e==nil ? "0" : "1" }.join ] }
            .group_by { |hdr, bits| bits }
            .map { |bits,lol| [bits, lol.map(&:first)] }.to_h 
        tt.map { |k, a| puts "#{k} => #{a.join(%q(, ))}" if k=~/^./ } '
0111001011 => ZO1, ZO4, ZO8
1111011111 => ZO2
1111001011 => ZO3
0110000000 => ZO5
0111111111 => ZO6
1111111111 => ZO7

您可以在正则表达式文字中添加任何正则表达式 k=~/^./ 以产生所需的结果。

awk:

$ awk 'BEGIN { FS="\t"; OFS=", " }
      NR==1 { next } 
            { ind=""
              for (i=2;i<=NF;i++)
                 ind=ind ($i=="" ? "0" : "1")
            map[ind]=map[ind] ? map[ind] OFS  : 
            }  
      END   { for( e in map) printf "%s => %s\n", e, map[e] }' file
0111111111 => ZO6
0111001011 => ZO1, ZO4, ZO8
0110000000 => ZO5
1111111111 => ZO7
1111011111 => ZO2
1111001011 => ZO3

table 条目将以无序结果出现,然后通过管道将其传送到 sedgrep 到 select 所需的行(或行的一部分)(或者就在最后的 awk 循环内)。

最佳

"straight" awk 解决方案的问题是 space 的解析以及 awk 看不到空字段的事实,因此我们必须将 sed 与 awk 一起使用。

sed -rn 's/([[:alpha:]]+)|([[:blank:]]{4})/,&/gp' filename | sed -rn 's/[[:blank:]]//gp' filename | awk -F , 'NR > 1 { if (  == "" &&  != "" &&  != "" &&  != "" &&  == "" &&  == "" &&  != "" &&  == "" &&  != "" &&  != "" ) { print  } }'



sed -rn 's/([[:alpha:]]+)|([[:blank:]]{4})/,&/gp' filename | sed -rn 's/[[:blank:]]//gp' filename

首先用sed,在任何字符前加逗号或4个空格space。然后 运行 第二个 sed 语句删除空白 spaces.

这让你

,,A,B,C,D,E,F,G,H,I,J
,ZO1,,X1,X2,X3,,,X4,,X5,X6
,ZO2,X7,X8,X9,X10,,X11,X12,X13,X14,X15
,ZO3,X16,X17,X18,X19,,,X20,,X21,X22
,ZO4,,X23,X24,X25,,,X26,,X27,X28
,ZO5,,X29,X30,,,,,,
,ZO6,,X31,X32,X33,X34,X35,X36,X37,X38,X39
,ZO7,X40,X41,X42,X43,X44,X45,X46,X47,X48,X49
,ZO8,,X50,X51,X52,,,X53,,X54,X55

然后用awk处理这个数据:

awk -F , 'NR > 1 { if (  == "" &&  != "" &&  != "" &&  != "" &&  == "" &&  == "" &&  != "" &&  == "" &&  != "" &&  != "" ) { print  } }'

使用 , 作为字段分隔符,然后根据特定条件检查分隔字段。

输出:

ZO1
ZO4
ZO8