用参考值更新匹配行之后的第二行

Eve*_*ner 0 awk

我有一个主文件,其中只有一个匹配的字符串,我想更改匹配短语后面第二行中的一列的值,并将其输出到一个单独的文件。我有另一个参考文件,其中包含输出文件名(第一列)和替换值(第二列)。下面是示例和我尝试的代码,有错误且没有任何输出。我感谢您的支持。

(主文件)

some words here
This is the 'MATCH LINE'
# this is just a comment
This NUMBER to be updated
and other words here
Run Code Online (Sandbox Code Playgroud)

(参考文件)

Out1 ONE
Out2 TWO
Out3 THREE
Run Code Online (Sandbox Code Playgroud)

(预期输出文件:Out1)

some words here
This is the 'MATCH LINE'
# this is just a comment
This ONE to be updated
and other words here
Run Code Online (Sandbox Code Playgroud)

(预期输出文件:Out2)

some words here
This is the 'MATCH LINE'
# this is just a comment
This TWO to be updated
and other words here
Run Code Online (Sandbox Code Playgroud)

(预期输出文件:Out3)

some words here
This is the 'MATCH LINE'
# this is just a comment
This THREE to be updated
and other words here
Run Code Online (Sandbox Code Playgroud)

我尝试的脚本:

awk 'FNR==NR {fn[$2]=$1; vals[$2]=$2; next}
   {for (f in fn) {$0~/This is the '\''MATCH LINE'\''/{getline;getline;$2=vals[f]}}; print $0 > fn[f]}' ref_file  main_file
Run Code Online (Sandbox Code Playgroud)

错误消息:

awk: file "[command line]": line 1: Syntax error  Context is:
>>>
>>>        {for (f in fn) {$0~/This is the 'MATCH LINE'/{       <<<
Run Code Online (Sandbox Code Playgroud)

Dav*_*ica 5

我看到了您的几个问题,您似乎对如何处理多个文件有点困惑。虽然您可以用来getline()读取当前文件记录之外的信息,但在处理 2 个文件以协调信息时,很少需要它。

相反,您将排序文件的处理,以便从第一个文件中捕获所需的内容,无论是在数组中(通常)还是通过连接字符串中的信息(在某些情况下提供好处),然后从第二个文件中读取记录并应用任何需要的更改。您正确地使用FNR==NR检查当前文件记录数是否等于从提供的第一个文件读取时要识别的记录总数。但随后你的脚本就偏离了你想要的。

在您的ref文件中,您真正关心的是第二个字段。只需将其读入数组,跳过其余规则,例如

awk 'FNR==NR {a[++n]=$2; next} ... '
Run Code Online (Sandbox Code Playgroud)

(注意:通过对数组索引使用预自增 (++n),可以使索引与记录号从 1 开始的处理保持一致,等等...)

现在您所需要的只是多一条规则(实际上是两条 - 考虑使用默认print规则),例如

' ... $1 && ++line==3 && i<=n {sub(/NUMBER/,a[++i]); line=0}1'
Run Code Online (Sandbox Code Playgroud)

现在让我们回顾一下处理文件的逻辑main。您需要的第一件事是一个简单的变量line来跟踪123(替换)、重置为0。因此,如果你看一下条件语句,$1 && ++line==3 && i<=n它会说:

  • 如果有第一个字段(例如不仅仅是空行);然后
  • 预增量行并将其与3; 最后
  • 确保您尚未用完已保存的替换号码。

注意:由于它是 AND 比较,因此根据第一个错误标准,永远不会检查其余部分,以防止++line在空行上执行)

如果满足所有三个条件,那么您只需替换为使用a[++i]时保存的数字即可。规则末尾的 仅仅是默认规则的简写。/NUMBER/sub()1print

使用/输出示例

您的ref文件位于dat/ref(不包含[ref]我认为作为您给出文件名的方式的文件)和更长的main时间dat/main,例如

$ cat dat/main
This is the 'MATCH LINE'
# this is just a comment
This NUMBER to be updated

This is the 'MATCH LINE'
# this is just a comment
This NUMBER to be updated
Run Code Online (Sandbox Code Playgroud)

然后您将使用完整awk表达式:

$ awk 'FNR==NR {a[++n]=$2; next} $1 && ++line==3 && i<=n {sub(/NUMBER/,a[++i]); line=0}1' dat/ref dat/main
This is the 'MATCH LINE'
# this is just a comment
This ONE to be updated

This is the 'MATCH LINE'
# this is just a comment
This TWO to be updated
Run Code Online (Sandbox Code Playgroud)

这是您指定的输出 -但我怀疑您实际上需要更多一点来处理文件中可能存在的其他行......

如果 [main] 文件中有其他行

如果您的[main]文件中可以包含各种其他行,那么您需要跟踪是否找到了匹配的行并且位于您的1, 2,3计数中。您可以使用line变量作为标志和计数器进行一些小的更改,例如:

awk 'FNR==NR {a[++n]=$2; next} line && ++line==3 && i<=n {sub(/NUMBER/,a[++i]); line=0} /MATCH LINE/ {line = 1}1' dat/ref dat/main
Run Code Online (Sandbox Code Playgroud)

在这里,我们使用line一个标志和一个计数器,如果您找到其中包含的行,则将其设置为1( )。当您进行更换时,您可以关闭该标志。这样,可能出现的任何其他行都将被简单地打印而不改变。例如,假设您的文件现在包含:true"MATCH LINE"line[main]

$ cat dat/main
@#$#% stuff
more stuff

###whatever
This is the 'MATCH LINE'
# this is just a comment
This NUMBER to be updated

@#$$%

This is the 'MATCH LINE'
# this is just a comment
This NUMBER to be updated
Run Code Online (Sandbox Code Playgroud)

现在您只需在第二行之后进行替换"MATCH LINE",例如

$ awk 'FNR==NR {a[++n]=$2; next} line && ++line==3 && i<=n {sub(/NUMBER/,a[++i]); line=0} /MATCH LINE/ {line = 1}1' dat/ref dat/main
@#$#% stuff
more stuff

###whatever
This is the 'MATCH LINE'
# this is just a comment
This ONE to be updated

@#$$%

This is the 'MATCH LINE'
# this is just a comment
This TWO to be updated
Run Code Online (Sandbox Code Playgroud)

这又是您指定的想要显示两个替换的输出,但在可以包含各种其他行的文件中完成(就像普通文件一样)。

如果您有疑问,或者您的输入文件中是否确实有这些行[ref]和,请告诉我。[main]


更新您的编辑以将单个替换行重定向到文件$1[ref]

好的,根据您的评论:

@DavidC.Rankin:我理解你的想法。您假设我在 dat/main 文件中有几个“匹配线”。我应该强调,dat/main 文件只有一个“匹配线”,我们使用锚点来修改以下第二行,并将数字替换为一/二和三,并将每个文件输出到单独的文件,根据第一列命名dat/ref(即 Out1、Out2 和 Out3)。

[ref]要重定向到由(my )中的第一个字段命名的文件,dat/ref您所需要做的就是将第一个字段保存在单独的数组中(或者您可以只保存完整的行并稍后将其拆分)。让我们使用与索引相同的文件名数组,例如(我们将 now 递增,因为我们将它放在第一位 - 您可以根据需要对其进行排序)。保存文件名数组中的第一个字段所需的唯一更改是:nfn[++n]fn[]dat/ref

... 'FNR==NR {fn[++n]=$1; a[n]=$2; next} ...
Run Code Online (Sandbox Code Playgroud)

保存第一个文件后,我们不再希望使用默认print命令输出1, 2, 3count 中的每条记录,我们只想将替换的行输出到新文件中。1因此,从末尾删除,现在只需在设置后重定向line=0;,例如

... line && ++line==3 && i<=n {sub(/NUMBER/,a[++i]); line=0; print > fn[i]} ...
Run Code Online (Sandbox Code Playgroud)

(对我来说,我喜欢在dat/目录中创建输出文件,所以你可以简单地让awk你连接目录,例如

... print > ("dat/" fn[i]) ...
Run Code Online (Sandbox Code Playgroud)

(感谢 Ed 指出优先级问题,使其适用于所有 awks)

将其放在一起并将新文件输出到dat/目录中,您将拥有:

awk 'FNR==NR {fn[++n]=$1; a[n]=$2; next} line && ++line==3 && i<=n {sub(/NUMBER/,a[++i]); line=0; print > ("dat/" fn[i])} /MATCH LINE/ {line = 1}' dat/ref dat/main
Run Code Online (Sandbox Code Playgroud)

已创建新文件

由于我只有 2 组语句dat/main,所以我只得到两个新文件,例如

$ ls -al dat/out*
-rw-r--r-- 1 david david 23 Jun 12 23:20 dat/out1
-rw-r--r-- 1 david david 23 Jun 12 23:20 dat/out2
Run Code Online (Sandbox Code Playgroud)

内容如您所指定,包含替换的单行,例如

$ cat dat/out1
This ONE to be updated
Run Code Online (Sandbox Code Playgroud)

$ cat dat/out2
This TWO to be updated
Run Code Online (Sandbox Code Playgroud)

如果我们最终达成了“共识”并沟通并理解了您想要实现的目标,请告诉我。

注意:正如Ed提到的,如果输出中的间距没有什么特别的,例如不需要保留特殊数量的空格,并且如果NUMBER始终是第二个字段,则可以简单地设置$2 = a[++i]而不是使用sub(/NUMBER/,a[++i])。)


附加编辑现在将所有行写入输出文件(MATCH通过替换)

如果您现在要将 MATCH 行、下一行和更改的行写入 中第一个字段指定的文件中ref,您可以添加一个变量(例如content)来累积每一行,然后打印将它们重定向到文件,例如

awk 'FNR==NR {fn[++n]=$1; a[n]=$2; next} line && ++line==3 && i<=n {sub(/NUMBER/,a[++i]); line=0; content = content $0; print content > ("dat/" fn[i]); content = ""} /MATCH LINE/ {line = 1} line > 0 {content = content $0 "\n"}' dat/ref dat/main
Run Code Online (Sandbox Code Playgroud)

文件已创建

相同dat/out1dat/out2,但现在内容:

$ cat dat/out1
This is the 'MATCH LINE'
# this is just a comment
This ONE to be updated
Run Code Online (Sandbox Code Playgroud)

$ cat dat/out2
This is the 'MATCH LINE'
# this is just a comment
This TWO to be updated
Run Code Online (Sandbox Code Playgroud)

当命令行变得这么长时,创建一个awk脚本matchline.awk并使其可以使用chmod +x matchline.awk. 现在您可以更轻松地阅读脚本,运行它所需要做的就是,例如使用 my dat/refanddat/main是:

$ ./matchline.awk dat/ref dat/main
Run Code Online (Sandbox Code Playgroud)

完整的awk脚本是:

#!/bin/awk -f

FNR == NR {         ## process ref file
  fn[++n] = $1      ## saving 1st field to array fn[]
  a[n] = $2         ## savind 2nd filed to array a[]
  next              ## skip to next record
}

line && ++line == 3 && i <= n {   ## if line set and 3 and array element reamin
  sub(/NUMBER/,a[++i])            ## substitute 2nd field for NUMBER
  content = content $0            ## append
  print content > ("dat/" fn[i])  ## output saved content to 1st field name
  line = 0                        ## reset line 0
  content = ""                    ## reset content empty
}

/MATCH LINE/ {      ## if line has 'MATCH LINE'
  line = 1          ## set line to 1
}

line > 0 {                        ## if line set
  content = content $0 "\n"       ## append to contents with newline
}
Run Code Online (Sandbox Code Playgroud)

(创建相同的文件)


根据您最后的评论:

@DavidC.Rankin:我尝试了最后一次编辑(长 awk 脚本),仅更改为从打印 print content > (fn[i]) 中删除“dat/”,但它只生成了第一个输出(Out1)

如果不为输入/输出文件使用单独的目录(例如"dat/"),则无需将文件名串联括在( ... ). 所有你需要的是print content > fn[i]。删除后的整个脚本"dat/"将是:

#!/bin/awk -f

FNR == NR {         ## process ref file
  fn[++n] = $1      ## saving 1st field to array fn[]
  a[n] = $2         ## savind 2nd filed to array a[]
  next              ## skip to next record
}

line && ++line == 3 && i <= n {   ## if line set and 3 and array element reamin
  sub (/NUMBER/, a[++i])          ## substitute 2nd filed for NUMBER
  content = content $0            ## append
  print content > fn[i]           ## output saved content
  line = 0                        ## reset line 0
  content = ""                    ## reset content empty
}

/MATCH LINE/ {      ## if line has 'MATCH LINE'
  line = 1          ## set line to 1
}

line > 0 {                        ## if line set
  content = content $0 "\n"       ## append to contents with newline
}
Run Code Online (Sandbox Code Playgroud)

在当前目录中创建的文件

main现在,使用上面显示的两组输入和您的ref,您可以以相同的方式运行:

$ ./matchline.awk ref main
Run Code Online (Sandbox Code Playgroud)

在运行脚本之前,目录中存在的唯一文件是:

$ ls -al
total 20
drwxr-xr-x  2 david david 4096 Jun 15 16:51 .
drwxr-xr-x 10 david david 4096 Jun 15 16:50 ..
-rw-r--r--  1 david david  153 Jun 15 16:50 main
-rwxr-xr-x  1 david david 1009 Jun 15 16:51 matchline.awk
-rw-r--r--  1 david david   29 Jun 15 16:50 ref
Run Code Online (Sandbox Code Playgroud)

之后(显示内容),你有

$ 16:51 wizard:~/tmp/awk/tst> ./matchline.awk ref main
16:51 wizard:~/tmp/awk/tst> l
total 28
drwxr-xr-x  2 david david 4096 Jun 15 16:51 .
drwxr-xr-x 10 david david 4096 Jun 15 16:50 ..
-rw-r--r--  1 david david  153 Jun 15 16:50 main
-rwxr-xr-x  1 david david 1009 Jun 15 16:51 matchline.awk
-rw-r--r--  1 david david   73 Jun 15 16:51 out1
-rw-r--r--  1 david david   73 Jun 15 16:51 out2
-rw-r--r--  1 david david   29 Jun 15 16:50 ref
16:51 wizard:~/tmp/awk/tst> cat out1
This is the 'MATCH LINE'
# this is just a comment
This ONE to be updated
16:51 wizard:~/tmp/awk/tst> cat out2
This is the 'MATCH LINE'
# this is just a comment
This TWO to be updated
Run Code Online (Sandbox Code Playgroud)

这正是您所描述的想要的。删除(...)重定向语句中的 并再试一次。awk --version如果您仍然遇到问题,还可以向我显示您系统上的确切输出。我遇到了一些奇怪的awk问题,OPawk在 Sun Sparkstation 上使用了 20 岁的设备——所以如果您仍然遇到问题,我们需要消除这个问题。


通用化第二次现场更换的变更main

要概括第二个字段的替换而main不是替换NUMBER,您可以简单地将sub()命令更改为对第二个字段的赋值。更改后的规则如下所示,脚本的其余部分保持不变。您只需用 Ed 在前面的评论中提到的sub()分配来替换该行,例如$2

line && ++line == 3 && i <= n {   ## if line set and 3 and array element reamin
  # sub (/NUMBER/, a[++i])          ## substitute 2nd field for NUMBER
  $2 = a[++i];                    ## assign save 2nd field to replace 2nd field
  content = content $0            ## append
  print content > fn[i]           ## output saved content
  line = 0                        ## reset line 0
  content = ""                    ## reset content empty
}
Run Code Online (Sandbox Code Playgroud)

(原始sub()命令已注释,新分配位于其下方)

结果与上面最后一个示例相同。

如果您有疑问,请告诉我。