Unix/bash:for 循环与 grep 折叠行
Unix/bash: For loops with grep to collapse rows
我有一个制表符分隔的文件 A,如下所示:
nameA GO:0005737 细胞质
nameB GO:0005875 微管相关复合体
nameB GO:0005884 肌动蛋白丝
nameC GO:0005737 细胞质
nameC GO:0005856 细胞骨架
nameC GO:0005524 ATP 绑定
..
第一列是基因名称,第二列是 GO id,第三列是对该 id 的描述。第一列中的每个标识符可以有一行或几行。
我想创建一个新文件,其中每个基因名称只有一行,所有相关的 GO 术语都在第二列,描述在第三列:
nameA GO:0005737 细胞质
nameB GO:0005875,GO:0005884, 微管相关复合体, 肌动蛋白丝
nameC GO:0005737, GO:0005856, GO:0005524 细胞质,细胞骨架,ATP 结合
...
...并且 GO id 的顺序遵循描述术语的顺序,即每行中的第一个 GO id 对应于第一个描述术语。
我尝试获取所有基因名称的唯一列表,然后 运行 对每个基因名称进行 for 循环 grepping,删除 GO 列并用逗号替换换行符,然后在结束。
cut -f1 文件A | uniq > 标识符
for name in `cat identifiers`
做
grep "$name" 文件A |切-f2 | tr '\n' ',' | sed 's/$/\n/' >> GOs_collapsed
完成
在此之后,我计划对第三列执行相同的操作,然后使用粘贴将两者与标识符文件放在一起。
但是,上面的 bash 脚本不起作用。 GOs_collapsed 中的输出只是 GO:s 的列表,就像之前一样。
GO:0005737
GO:0005875
GO:0005884
..
有什么想法吗?
假设输入在每行的第一个字段上排序,这应该可以满足您的要求。
$ cat group.awk
BEGIN {
FS=OFS="\t"
}
function printline(last, col, cols) {
printf last
for (i = 2; i <= cols; i++) {
printf OFS"%s", col[i]
}
printf ORS
}
!= last {
if (last) {
printline(last, col, cols)
}
# Reset last and our accumulated fields.
last=
split("", col)
}
== last {
cols = (cols > NF) ? cols : NF
for (i = 2; i <= NF; i++) {
col[i] = col[i] (col[i]?",":"") $i
}
next
}
END {
printline(last, col, cols)
}
$ awk -f group.awk fileA
您可以使用 awk one liner 来完成,如下所示:
awk 'BEGIN {
FS=OFS="\t"
}
{ if (a[] == "") {
a[]=; b[]=
} else {
a[]=a[] "," ; b[]=b[] "," ;
}
} END {
for (i in a)
print i "\t" a[i] "\t" b[i]
}' myfile.txt
读取一个密钥的所有条目,并在看到新密钥时打印收集的输出。这要求一个键的所有条目都相邻,这可以通过对输入进行排序来轻松实现。
IFS=$'\t'
sort fileA |
while read -r key go desc; do
if [ "$key" != "$prev" ] && [ "$prev" != "" ]; then
printf '%s\t%s\t%s\n' "$prev" "${gos#,}" "${descs#,}"
gos=""
descs=""
fi
gos="$gos,$go"
descs="$descs,$desc"
prev="$key"
done
printf '%s\t%s\t%s\n' "$key" "${gos#,}" "${descs#,}"
构造 ${var#prefix}
returns var
的值并删除任何 prefix
。允许并期望一个前导逗号简化了主要流程,因此我们不必在第一轮中为新密钥特例。
还要注意进入 while
循环的管道,它避免了临时文件和讨厌的 for
循环。
根据您目前的情况:
cut -f1 -d' ' fileA | uniq | while read name; do
awk -v name="$name" ' == name {print }' fileA | paste -s -d',' > GOs
echo "$name $(awk -v name="$name" ' == name {print }' fileA | paste -s -d',' | paste GOs -)"
done
如果字段由制表符而不是空格分隔,请将 cut -d1 -d' '
更改为 cut -f1
。
您可以通过编程方式这样做。
for name in `cut -d' ' -f 1 file.txt | uniq`
do
line="$name\t"
grepVal=`grep "$name" file.txt`
for val in `grep "$name" file.txt | cut -d' ' -f6`
do
line="$line$val, "
done
line="$line\t"
for desc in `grep "$name" file.txt | cut -d' ' -f 11-36`
do
line="$line$desc, "
done
echo $line >> GOs_collapsed
done
输出
nameA GO:0005737, cytoplasm,
nameB GO:0005875, GO:0005884, microtubule, associated, complex, actin, filament,
nameC GO:0005737, GO:0005856, GO:0005524, cytoplasm, cytoskeleton, ATP, binding,
我有一个制表符分隔的文件 A,如下所示:
nameA GO:0005737 细胞质 nameB GO:0005875 微管相关复合体 nameB GO:0005884 肌动蛋白丝 nameC GO:0005737 细胞质 nameC GO:0005856 细胞骨架 nameC GO:0005524 ATP 绑定 ..
第一列是基因名称,第二列是 GO id,第三列是对该 id 的描述。第一列中的每个标识符可以有一行或几行。
我想创建一个新文件,其中每个基因名称只有一行,所有相关的 GO 术语都在第二列,描述在第三列:
nameA GO:0005737 细胞质 nameB GO:0005875,GO:0005884, 微管相关复合体, 肌动蛋白丝 nameC GO:0005737, GO:0005856, GO:0005524 细胞质,细胞骨架,ATP 结合 ...
...并且 GO id 的顺序遵循描述术语的顺序,即每行中的第一个 GO id 对应于第一个描述术语。
我尝试获取所有基因名称的唯一列表,然后 运行 对每个基因名称进行 for 循环 grepping,删除 GO 列并用逗号替换换行符,然后在结束。
cut -f1 文件A | uniq > 标识符 for name in `cat identifiers` 做 grep "$name" 文件A |切-f2 | tr '\n' ',' | sed 's/$/\n/' >> GOs_collapsed 完成
在此之后,我计划对第三列执行相同的操作,然后使用粘贴将两者与标识符文件放在一起。
但是,上面的 bash 脚本不起作用。 GOs_collapsed 中的输出只是 GO:s 的列表,就像之前一样。
GO:0005737 GO:0005875 GO:0005884 ..
有什么想法吗?
假设输入在每行的第一个字段上排序,这应该可以满足您的要求。
$ cat group.awk
BEGIN {
FS=OFS="\t"
}
function printline(last, col, cols) {
printf last
for (i = 2; i <= cols; i++) {
printf OFS"%s", col[i]
}
printf ORS
}
!= last {
if (last) {
printline(last, col, cols)
}
# Reset last and our accumulated fields.
last=
split("", col)
}
== last {
cols = (cols > NF) ? cols : NF
for (i = 2; i <= NF; i++) {
col[i] = col[i] (col[i]?",":"") $i
}
next
}
END {
printline(last, col, cols)
}
$ awk -f group.awk fileA
您可以使用 awk one liner 来完成,如下所示:
awk 'BEGIN {
FS=OFS="\t"
}
{ if (a[] == "") {
a[]=; b[]=
} else {
a[]=a[] "," ; b[]=b[] "," ;
}
} END {
for (i in a)
print i "\t" a[i] "\t" b[i]
}' myfile.txt
读取一个密钥的所有条目,并在看到新密钥时打印收集的输出。这要求一个键的所有条目都相邻,这可以通过对输入进行排序来轻松实现。
IFS=$'\t'
sort fileA |
while read -r key go desc; do
if [ "$key" != "$prev" ] && [ "$prev" != "" ]; then
printf '%s\t%s\t%s\n' "$prev" "${gos#,}" "${descs#,}"
gos=""
descs=""
fi
gos="$gos,$go"
descs="$descs,$desc"
prev="$key"
done
printf '%s\t%s\t%s\n' "$key" "${gos#,}" "${descs#,}"
构造 ${var#prefix}
returns var
的值并删除任何 prefix
。允许并期望一个前导逗号简化了主要流程,因此我们不必在第一轮中为新密钥特例。
还要注意进入 while
循环的管道,它避免了临时文件和讨厌的 for
循环。
根据您目前的情况:
cut -f1 -d' ' fileA | uniq | while read name; do
awk -v name="$name" ' == name {print }' fileA | paste -s -d',' > GOs
echo "$name $(awk -v name="$name" ' == name {print }' fileA | paste -s -d',' | paste GOs -)"
done
如果字段由制表符而不是空格分隔,请将 cut -d1 -d' '
更改为 cut -f1
。
您可以通过编程方式这样做。
for name in `cut -d' ' -f 1 file.txt | uniq`
do
line="$name\t"
grepVal=`grep "$name" file.txt`
for val in `grep "$name" file.txt | cut -d' ' -f6`
do
line="$line$val, "
done
line="$line\t"
for desc in `grep "$name" file.txt | cut -d' ' -f 11-36`
do
line="$line$desc, "
done
echo $line >> GOs_collapsed
done
输出
nameA GO:0005737, cytoplasm,
nameB GO:0005875, GO:0005884, microtubule, associated, complex, actin, filament,
nameC GO:0005737, GO:0005856, GO:0005524, cytoplasm, cytoskeleton, ATP, binding,