有条件地用数字替换行

有条件地用数字替换行

我有一个包含近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.outFILENAME 指示当前输入文件名读取的格式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 "";
    }
}'

解释

  1. tr '\n' '\t' < input.txt- 将两条线连接在一起。
  2. awk
    • 同时检查第一行中的一个元素和第二行中的相邻元素,例如:1316,2第317章,3318, 很快。
    • 如果两个元素都是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

相关内容