awk sub命令用带有捕获组的模式替换文本中的字符串

4

我正在为此编写一个awk一行命令:

文件1:

1   apple
2   orange
4   pear

文件2:

1/4/2/1

期望输出:apple/pear/orange/apple

附加说明:为了避免信息的丢失,缺失的数字应该最好保持不变,即 1/4/2/3 = apple/pear/orange/3。

方法:

  1. 为file1构建一个关联数组 key[$1] = $2
  2. 捕获斜杠之间的所有字符,并通过匹配关联数组的键将其替换,例如key[4] = pear

尝试过的方法:

gawk 'NR==FNR { key[$1] = $2 }; NR>FNR { r = gensub(/(\w+)/, "key[\\1]" , "g"); print r}' file1.txt file2.txt

#gawk because need to use \w+ regex
#gensub used because need to use a capturing group

很不幸,结果是1/4/2/1

key[1]/key[4]/key[2]/key[1]

有什么建议吗?谢谢。


2
据我所知,awk不允许您将反向引用用作数组键。 - Sundeep
3
如果file2中有1/4/2/3,应该输出什么? - anubhava
5个回答

5

编辑:如果您在文件中没有与file2匹配的内容,且希望保留原始值不变,请尝试以下操作:

awk '
FNR==NR{
  arr[$1]=$2
  next
}
{
  val=""
  for(i=1;i<=NF;i++){
    val=(val=="" ? "" : val FS) (($i in arr)?arr[$i]:$i)
  }
  print val
}
' file1 FS="/" file2

请根据您提供的示例尝试以下操作。
awk '
FNR==NR{
  arr[$1]=$2
  next
}
{
  val=""
  for(i=1;i<=NF;i++){
    val = (val=="" ? "" : val FS) arr[$i]
  }
  print val
}
' file1 FS="/" file2

解释: 首先读取Input_file1,创建索引为第一字段,值为第二字段的数组arr,然后将字段分隔符设置为/,遍历file2中的每个字段并将其值保存在val中; 最后针对每行打印输出 val。


5
你可以使用这个 awk 命令:
awk -v OFS='/' 'NR==FNR {key[$1] = $2; next}
{for (i=1; i<=NF; ++i) if ($i in key) $i = key[$i]} 1' file1 FS='/' file2

apple/pear/orange/apple

请注意,如果file2中的数字不存在于key数组中,则它们将使这些字段为空。 file1 FS='/' file2将保持默认的字段分隔符用于file1,但在读取file2时将使用/作为字段分隔符。

你能否通过 if ($i in key) { $i = key[$i] } 来解决缺失键的问题? - Wiktor Stribiżew
可以轻松地完成,但不确定缺失数字的预期行为是什么。数字应该替换为空字符串还是保持不变。 - anubhava
感谢您的建议。缺失的数字最好保持不变,例如 1/4/2/3 = 苹果/梨子/橙子/3,以防止信息损失。 - Harry
1
@Harry请编辑您的问题,包括[该信息](https://dev59.com/vMHqa4cB1Zd3GeqPrAvV#TMkuoYgBc1ULPQZF2R0S)。不要在评论中随意散布要求,以免被轻易忽略。 - Ed Morton

4

正如评论中@Sundeep所说,您不能将回溯引用作为数组索引。您可以混合使用 match gensub (下面我使用 sub )。并不是这是任何建议的方法,只是作为一个例子:

$ awk '
NR==FNR {
    k[$1]=$2                                       # hash them
    next
}
{
    while(match($0,/[0-9]+/))                      # keep doing it while it lasts
        sub(/[0-9]+/,k[substr($0,RSTART,RLENGTH)]) # replace here
}1' file1 file2

输出:

apple/pear/orange/apple

当然,如果你有k[1]="word1",你最终会陷入无限循环。

3
如果k[1]="me&you",那么你会得到me1you,接着是meme1youyou,以此类推,无限循环下去。 - Ed Morton

3

另一种使用 awk 而不需要循环的方法:

awk 'FNR==NR{
        a[$1]=$2;
        next
     }
     $1 in a{
        printf("%s%s",FNR>1 ? RS: "",a[$1])
     }
     END{
      print ""
     }' f1 RS='/' f2

$ cat f1
1   apple
2   orange
4   pear

$ cat f2
1/4/2/1

$ awk 'FNR==NR{a[$1]=$2;next}$1 in a{printf("%s%s",FNR>1?RS:"",a[$1])}END{print ""}' f1 RS='/' f2
apple/pear/orange/apple


3

使用 perl(假设键总是被找到):

$ perl -lane 'if(!$#ARGV){ $h{$F[0]}=$F[1] }
              else{ s|[^/]+|$h{$&}|g; print }' f1 f2
apple/pear/orange/apple
  • 使用if(!$#ARGV) 判断第一个文件(假定只传入两个文件)
  • 使用$h{$F[0]}=$F[1] 创建以第一个字段作为键,第二个字段作为值的哈希表
  • [^/]+ 匹配非/字符
  • $h{$&} 根据匹配到的部分从哈希表中获取对应的数据值


如果找不到某些键,则保持原样。

$ cat f2
1/4/2/1/5
$ perl -lane 'if(!$#ARGV){ $h{$F[0]}=$F[1] }
              else{ s|[^/]+|exists $h{$&} ? $h{$&} : $&|ge; print }' f1 f2
apple/pear/orange/apple/5

exists $h{$&} 检查匹配部分是否存在作为键。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接