我有一个包含近1100万个小文件的目录:像这样
wa_filtering_DP15_good_pops_snps_file_1
wa_filtering_DP15_good_pops_snps_file_2
.
.
.
wa_filtering_DP15_good_pops_snps_file_11232111
每个文件只有 2 行和 315 列,如下所示:
1 0 0 0 0 0 0 0 0 0 1 2 1
0 0 0 0 0 0 0 0 0 0 0 0 0
我想遍历每个文件,如果每列中两行都有 0 值,则将它们替换为 9 并得到如下所示的内容:
1 9 9 9 9 9 9 9 9 9 1 2 1
0 9 9 9 9 9 9 9 9 9 0 0 0
有人可以帮我弄清楚该怎么做吗?谢谢
答案1
这是awk
解决方案。
awk '{split($0,ary1,/[ ]+/); getline x; split(x,ary2,/[ ]+/);
for (i in ary1)if (!(ary1[i]+ary2[i])){ary1[i]=ary2[i]=9}}
END{for (r=1;r<=NF;r++) printf ("%d ", ary1[r]); printf"\n";
for (z=1;z<=NF;z++) printf ("%d ", ary2[z]); printf"\n"}' infile
说明:
split($0,ary1,/[ ]+/);
:读取第一行并将其拆分为一个数组,数组ary1
之间有一个或多个空格分隔符。getline x; split(x,ary2,/[ ]+/);
:将第二行读入变量x
并将其拆分为数组ary2
。for (i in ary1)if (!(ary1[i]+ary2[i])){ary1[i]=ary2[i]=9}}
:如果两个字段值的总和为,则在数组中循环ary1
每个索引i
零(将在 true 条件下!(0)
触发)然后将两个字段的值设置为。if(1)
9
for (r=1;r<=NF;r++) printf ("%d ", ary1[r]); printf"\n";
ary1
:现在打印每个数组和下一行的最终值ary2
。
要应用到所有约 1100 万个文件,只需将更改保存为FILENAME.out
FILENAME 指示当前输入文件名读取的格式awk
。
awk '{split($0,ary1,/[ ]+/); getline x; split(x,ary2,/[ ]+/);
for (i in ary1)if (!(ary1[i]+ary2[i])){ary1[i]=ary2[i]=9}}
END{for (r=1;r<=NF;r++) printf ("%d ", ary1[r])>FILENAME".out"; printf"\n">FILENAME".out";
for (z=1;z<=NF;z++) printf ("%d ", ary2[z])>FILENAME".out"
}' wa_filtering_DP15_good_pops_snps_file_{1..11232111}
答案2
为了好玩,这是 Ruby
ruby -e '
data = File.readlines(ARGV.shift)
.map {|line| line.split.map(&:to_i)}
.transpose
.map {|(a,b)| (a==0 && b==0) ? [9,9] : [a,b]}
.transpose
.each {|row| puts row.join(" ")}
' file
1 9 9 9 9 9 9 9 9 9 1 2 1
0 9 9 9 9 9 9 9 9 9 0 0 0
要替换所有文件:
ruby -e '
require "tempfile"
require "pathname"
Pathname.new("/path/to/your/files/").each_child do |pathname|
next unless pathname.file?
temp = Tempfile.new(pathname.basename.to_s)
filename = pathname.to_s
File.readlines(filename)
.map {|line| line.split.map(&:to_i)}
.transpose
.map {|(a,b)| (a==0 && b==0) ? [9,9] : [a,b]}
.transpose
.each {|row| temp.puts row.join(" ")}
temp.close
File.link filename, filename+".bak"
File.rename temp.path, filename
end
'
答案3
这是一种替代方法,与纯 awk 解决方案相比,对于数百万个文件来说,它可能会很慢。
使用类似的方法,您可以将行转置为列:
$ cat file1
1 0 0 0 0 0 0 0 0 0 1 2 1
0 0 0 0 0 0 0 0 0 0 0 0 0
$ paste -d'-' <(head -n1 file1 |tr -s ' ' '\n') <(tail -n1 file1 |tr -s ' ' '\n')
1-0
0-0
0-0
0-0
0-0
0-0
0-0
0-0
0-0
0-0
1-0
2-0
1-0
然后,您可以用简单的 sed 替换所有0-0
出现的情况9-9
,并且可以将输出存储到临时变量中:
$ f1=$(sed 's/0-0/9-9/g' <(paste -d'-' <(head -n1 file1|tr -s ' ' '\n') <(tail -n1 file1 |tr -s ' ' '\n')))
$ echo "$f1"
1-0
9-9
9-9
9-9
9-9
9-9
9-9
9-9
9-9
9-9
1-0
2-0
1-0
您现在可以从列恢复到行,例如:
$ awk -F'-' 'NR==FNR{printf "%s ",$1;p=1;next}p{printf "\n";p=0}{printf "%s ",$2}END{printf "\n"}' <(echo "$f1") <(echo "$f1")
1 9 9 9 9 9 9 9 9 9 1 2 1
0 9 9 9 9 9 9 9 9 9 0 0 0
您还可以>file1
在最后一个 awk 命令的末尾附加以file1
用新内容覆盖。
剩下的唯一一件事就是循环所有文件。可以通过一种 bash 循环来完成:
for f in ./wa_filtering_DP15_good_pops_snps_file_*;do
f1=$(sed 's/0-0/9-9/g' <(paste -d'-' <(head -n1 "$f"|tr -s ' ' '\n') <(tail -n1 "$f" |tr -s ' ' '\n')))
awk -F'-' 'NR==FNR{printf "%s ",$1;p=1;next}p{printf "\n";p=0}{printf "%s ",$2}END{printf "\n"}' <(echo "$f1") <(echo "$f1") #>"$f" #uncomment >"$f" to overwrite the files...
done
答案4
第一个变体:
对于单个文件:
datamash -W transpose < input.txt | sed 's/0\t0/9\t9/' | datamash transpose
对于许多文件,在循环中执行相同的操作:
for i in *; do datamash -W transpose < "$i" |
sed 's/0\t0/9\t9/' |
datamash transpose > "new_$i"; done
此循环将为每个文件创建新的、更改的文件,并添加前缀“new_”。然后您可以删除所有旧文件并从文件名中删除前缀“new_”。
第二种变体:
这是针对单个文件的解决方案,对于多个文件使用循环,如之前的变体所示。
tr '\n' '\t' < input.txt |
awk '{
num = NF / 2;
for(up = 1; up <= NF; up++) {
if(up <= num) {
low = num + up;
if(!$up && !$low) {
$up = 9;
$low = 9;
}
}
printf "%s\t", $up;
if(up % num == 0)
print "";
}
}'
解释
tr '\n' '\t' < input.txt
- 将两条线连接在一起。awk
- 同时检查第一行中的一个元素和第二行中的相邻元素,例如:1和316,2和第317章,3和318, 很快。
- 如果两个元素都是0,它将它们更改为9。
- 按顺序打印字段 -1, 2, 3, 4 ... 628, 629, 630。
- 每次元素数量是行中元素数量的倍数时,都会添加一个新行。
输入
1 0 0 0 0 0 0 0 0 0 1 2 1
0 0 0 0 0 0 0 0 0 0 0 0 0
输出
1 9 9 9 9 9 9 9 9 9 1 2 1
0 9 9 9 9 9 9 9 9 9 0 0 0