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"] } '
两种情况:
- 使用
sed
插入hdr
字段,因为header行比下面的数据少一个字段;
- 使用
CSV
模块读取修改后的文件;
- 空白字段在 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 条目将以无序结果出现,然后通过管道将其传送到 sed
或 grep
到 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
我有以下制表符分隔的 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 -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"] } '
两种情况:
- 使用
sed
插入hdr
字段,因为header行比下面的数据少一个字段; - 使用
CSV
模块读取修改后的文件; - 空白字段在 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 条目将以无序结果出现,然后通过管道将其传送到 sed
或 grep
到 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