我有多个txt文件,其中3个如下:
文件1:
sample input filtered
5809378 1 2
5811151 3 4
5811237 5 6
文件2:
sample chi tri
5809378 7 8
5811151
5811237 9 10
文件3:
sample bra doe
5809378 11
5811151 12
5811237 13 14
我想根据第一列:样本 ID 将这 3 个文件合并为 1 个文件,因此输出如下所示:
sample input filters chi tri bra doe
5809378 1 2 7 8 11 0
5811151 3 4 0 0 0 12
5811237 5 6 9 10 13 14
请注意,如果没有相应的数据,则必须有一个零,或者在最坏的情况下,有一个空选项卡。
我尝试过 awk 和 join,但找不到最佳解决方案。有人有什么想法吗?
答案1
从我的角度来看, file3 并不完全正确,因为行
5811151 12
在第二列或第三列中可能有数字“12”,具体取决于我们读取文件的方式(未定义列分隔符并且在各处都不相同)。
反正。
a=$(cat file1|awk '{if($2==""){$2="0"};if($3==""){$3="0"}; print $1,$2,$3}'|sort);
for f in file2 file3; do
b=$(cat $f|awk '{if($2==""){$2="0"};if($3==""){$3="0"}; print $1,$2,$3}'|sort);
a=$(join -j 1 <(echo "${a}") <(echo "${b}"));
done;
echo "${a}"|sort -n
输出是
sample input filtered chi tri bra doe
5809378 1 2 7 8 11 0
5811151 3 4 0 0 12 0
5811237 5 6 9 10 13 14
所以,我们
1)每个拍摄的文件都经过转换
cat file|awk '{if($2==""){$2="0"};if($3==""){$3="0"}; print $1,$2,$3}'|sort
用“0”替换缺失的数字并对行进行排序。
2)在一个循环中,我们获取下一个文件并将其合并到上一个结果
join -j 1 file_current file_next
因此,“for f in file2 file3; do”行可以更改为包含更多文件,例如“for f in file2 file3 file4 file5 file6; do”。
3)打印结果,按照字符串数值排序(首先排序并打印列名称)。如果需要的话,我们也可以在这里格式化输出。
答案2
假设您的文件具有制表符分隔的列(因此您可以在类似第三行的情况下判断哪些列为空file3
),并且像您的示例一样在第一列上排序,bash 脚本如下所示:
#!/bin/bash
function fixup() { # Add 0's to blank columns
awk -v cols="$2" 'BEGIN { FS = OFS = "\t" }
{ for (i = 1; i <= cols; i++)
if ($i == "") $i = 0
} 1' "$1"
}
join --header -t$'\t' -j1 \
<(join --header -t$'\t' -j1 <(fixup "$1" 3) \
<(fixup "$2" 3)) \
<(fixup "$3" 3)
会做的:
$ ./combine file1 file2 file3
sample input filtered chi tri bra doe
5809378 1 2 7 8 11 0
5811151 3 4 0 0 0 12
5811237 5 6 9 10 13 14
(确实需要 GNU coreutils 版本join
)。
答案3
假设您的起始文件是制表符分隔的,并且空字段仍然是制表符分隔的,您可以使用 awk 对缺失的列进行零填充,例如:
awk -F' ' '!$2{$2=0}!$3{$3=0}{print}' file1 > file1-n
awk -F' ' '!$2{$2=0}!$3{$3=0}{print}' file2 > file3-n
awk -F' ' '!$2{$2=0}!$3{$3=0}{print}' file3 > file3-n
需要明确的是awk -F '<TAB>'
。然后你可以使用paste来合并它们,用另一个awk来过滤不需要的列:
bash-$ paste file1-n file2-n file3-n | awk {'print $1, $2, $3, $5, $6, $8, $9'}
sample input filtered chi tri bra doe
5809378 1 2 7 8 11 0
5811151 3 4 0 0 0 12
5811237 5 6 9 10 13 14
或者,如果人类可读性很重要,则将列隔开:
bash-$ paste file1-n file2-n file3-n | awk {'printf "%-7s %-5s %-8s %-3s %-3s %-3s %-3s\n", $1, $2, $3, $5, $6, $8, $9'}
sample input filtered chi tri bra doe
5809378 1 2 7 8 11 0
5811151 3 4 0 0 0 12
5811237 5 6 9 10 13 14
答案4
另一种选择,假设有一个tab
分隔符
0
首先通过在 double 之间插入 a 或在没有文本(即它是标题)或数字的tab
行结束处插入 a 来修复文件$
[^[:alnum:]]
TAB=$'\t'; sed -Ei "s/([^[:alnum:]]|${TAB})($|${TAB})/\10\2/g" file*
然后就只有join
他们了
join --header file2 file3 | join --header file1 - | column -t
输出
sample input filtered chi tri bra doe
5809378 1 2 7 8 11 0
5811151 3 4 0 0 0 12
5811237 5 6 9 10 13 14