使用另一个文件中的数据替换txt文件中特定位置的数据

使用另一个文件中的数据替换txt文件中特定位置的数据

我有一个以下格式的文本文件:

$data This is the experimental data    
good data
This is good file
datafile
1 4324 3673 6.2e+11 7687 67576
2 3565 8768 8760 5780 8778          "This is line '2'"
3 7656 8793 -3e+11 7099 79909
4 8768 8965 8769 9879 0970
5 5878 9879 7.970e-1 9070 0709799
.
.
.
100000 3655 6868 97879 96879 69899
$.endfile

我想用其他两个文本文件中的数据替换从行“2”到“100000”的第三列和第四列的数据,每个文本文件有一列 99999 行。

我如何使用 或任何其他 UNIX 命令来执行此awk操作sed?请注意,列分隔符是空格。

另外两个文本文件各有 99999 行,格式如下:

12414
12421
36347
3.4e+3
-3.5e22
987983
.
.
.
87698

答案1

一个 awk 的方式:

awk '{if(FNR==NR){f2[FNR+1]=$1;} 
      else{
        if(FNR==1){k++;} 
        if(k==1){f3[FNR+1]=$1} 
        else{if($1~/^[0-9]+/ && $1>1){$3=f2[$1];$4=f3[$1];} 
         print}
  }}' file2 file3 file1 

为了清楚起见,这与作为注释脚本编写的内容相同:

#!/usr/local/bin/gawk -f

{
    ## NR is the current line number, irrespective of 
    ## which input file is being read. FNR is the line 
    ## number of the current file. It is reset to 1 each 
    ## time a new file is opened. Therefore, FNR will be 
    ## equal to NR only while the 1st file is being read.
    if(FNR==NR){
        ## If this is the 1st file, save its 1st field
        ## in the array f2. The key of the array is the
        ## line number of the current file plus one. This is
        ## because you want to start modifying from row '2' onwards.
        ## Therefore, presumably, you want the 1st row of file2 to
        ## be the value for row '2' of your data file..
        f2[FNR+1]=$1;
    } 
    ## If this is not the 1st file
    else{
        ## If this is the 1st line of the current file
        if(FNR==1){
            ## Increase the value of the variable k by 1.
            k++;
        } 
        ## If k is currently 1, this means that the above has only
        ## been run once so we are currently reading the 1nd file.
        if(k==1){
            ## Save the 1st field of this file (file3 in your example)
            ## in the array f3. The key of the array is the
            ## line number of the current file plus one. 
            f3[FNR+1]=$1
        }
        ## If k is not 1, we are reading the 3rd file. In this case, 
        ## your actual data.
        else{
            ## If the 1st field is a number and is greater than 1.
            ## In other words, if this is one of the lines you want
            ## to change. 
            if($1~/^[0-9]+/ && $1>1){
                ## Set the 3rd field to be the value saved in the array
                ## f2 for the value of $1.  
                $3=f2[$1];
                ## Set the 4th field to be the value saved in the array
                ## f3 for the value of $1. 
                $4=f3[$1];
            } 
            ## Print the current line. Since this is outside the
            ## previous if block, it will print all lines irrespective
            ## of whether they've been modified. 
            print;
        }
    }
}

Perl 方式:

perl -lane 'BEGIN{
    open(A,"file2"); while(<A>){chomp; $f2{$.+1}=$_;} 
    open(B,"file3"); while(<B>){chomp; $f3{$.+1}=$_;}} 
    if($F[0]=~/^\d+$/ && $F[0]>1){$F[2]=$f2{$F[0]}; $F[3]=$f3{$F[0]}}
     print "@F"' file1

解释

  • -lanel会自动从每个输入行末尾删除尾随换行符(与 相同),并向每个语句chomp添加换行符。printa自动将空白处的每个输入行拆分到@F数组中,使 perl 像 awk 一样运行。意思是“在输入文件的每一行上n运行由 提供的脚本。-e
  • BEGIN{...}:这是在读取输入文件之前运行的。在本例中,我打开每个额外文件并将其内容保存在%f2%f3哈希中。这和awk我上面使用的数组基本相同。
  • if($F[0]=~/^\d+$/ && $F[0]>1){...}:同样,这与 awk 脚本中的逻辑相同。它将用每个文件的相应条目替换这些字段。
  • print "@F":这将打印所有字段。

答案2

由于您没有要求 100% 的awk解决方案,我将提供一个混合方案,它 (a) 可以说更容易理解,并且 (b) 不会强调awk的内存限制:

awk '
    $1 == 2 { secondpart = 1 }
       { if (!secondpart) {
                print > "top"
         } else {
                print $1, $2 > "left"
                print $5, $6, $7, $8, $9 > "right"
         }
       }' a
(cat top; paste -d" " left b c right) > new_a
rm top left right

或者我们可以删除其中一个临时文件并通过一条命令缩短脚本:

(awk '
    $1 == 2 { secondpart = 1 }
       { if (!secondpart) {
                print
         } else {
                print $1, $2 > "left"
                print $5, $6, $7, $8, $9 > "right"
         }
       }' a; paste -d" " left b c right) > new_a
rm left right

这将在输出的行末尾放置一些额外的空格, a如果任何行具有超过九个字段(列),则会丢失文件中的数据。如果这些是问题,它们可以很容易地解决。

答案3

{ { paste -d\  /dev/fd/[345] | 
    sed 's/ \( [^ ]*\)\(.*\)/\2\1/'
} 3<<FILE1 4<<FILE2 5<<FILE3
$(<file1 sed '1,/^1/w /dev/fd/2
      /^2/,$!d;s/ [^ ]*//4;s// /3')
FILE1
$(<file2 tr -s \\n)
FILE2
$(<file3 tr -s \\n)
FILE3
} 2>&1

在上面的命令序列中,我做了相当多的输入/输出杂耍。完成起来非常简单。file[23]实际上是相同的 - 它们都是第 3/4 行的 99,999 行的副本。剩下的就是file1——它是本质上与上面示例中的文件完全相同,但第 5 行被复制到第 6 行和第 7 行以匹配file[23].

基本上,每个文件都有自己的文件描述符和自己的准备工作。file[23]几乎不需要任何准备 -tr只是将所有重复的\newline 字符压缩为一个 - 这样空白行就消失了。

file1得到更多一点。首先,直到并包括以 a 开头的第一行的所有行都1将写入stderr。接下来它们会从输出中删除 - 所以它们只会输出到>&2.接下来sed选择第 3/4 列并用一个空格替换它们 - 这意味着它们原来的位置现在有两个连续的空格字符。

paste收集所有文件描述符并将它们全部粘在一起并用空格分隔。然后sed尝试将紧随两个空格字符之后的第一个非空格字符序列与其后的所有内容交换。

stderr最后和的文件描述符stdout被加入到 中stdout。结果如下:

输出

$data This is the experimental data

good data

This is good file

datafile

1 4324 3673 6.2e+11 7687 67576
2 3565 8768 12414 12414 8778
3 7656 8793 12421 12421 79909
4 8768 8965 36347 36347 0970
5 5878 9879 3.4e+3 3.4e+3 0709799
6 5878 9879 -3.5e22 -3.5e22 0709799
7 5878 9879 987983 987983 0709799
. . .
. . .
. . .
100000 3655 6868 87698 87698 69899
$.endfile 

答案4

另一种没有数组的 awk 方式,有点乱,所以我稍后会尝试清理它

awk 'function get(file,L) {x=1
        while ( (getline < file) > 0) {if(NR==x)y=$0;x++}
        close(file)
        return y
        }
     ARGV[1]==FILENAME{d=$0;a=get(ARGV[2],$0);b=get(ARGV[3],$0);$0=d;$2=a;$3=b;print
     }' file file1 file2

相关内容