awk sub with a capturing group into the replacement

Viewed 289

I am writing an awk oneliner for this purpose:

file1:

1   apple
2   orange
4   pear

file2:

1/4/2/1

desired output: apple/pear/orange/apple

addendum: Missing numbers should be best kept unchanged 1/4/2/3 = apple/pear/orange/3 to prevent loss of info.

Methodology:

  1. Build an associative array key[$1] = $2 for file1
  2. capture all characters between the slashes and replace them by matching to the key of associative array eg key[4] = pear

Tried:

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

Unfortunately, results are 1/4/2/1

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

Any suggestions? Thank you.

5 Answers

You may use this 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

Note that if numbers from file2 don't exist in key array then it will make those fields empty.

file1 FS='/' file2 will keep default field separators for file1 but will use / as field separator while reading file2.

EDIT: In case you don't have a match in file2 from file and you want to keep original value as it is then try following:

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


With your shown samples please try following.

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

Explanation: Reading Input_file1 first and creating array arr with index of 1st field and value of 2nd field then setting field separator as / and traversing through each field os file2 and saving its value in val; printing it at last for each line.

Like @Sundeep comments in the comments, you can't use backreference as an array index. You could mix match and gensub (well, I'm using sub below). Not that this would be anywhere suggested method but just as an example:

$ 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

Output:

apple/pear/orange/apple

And of course, if you have k[1]="word1", you'll end up with a neverending loop.

With perl (assuming key is always found):

$ perl -lane 'if(!$#ARGV){ $h{$F[0]}=$F[1] }
              else{ s|[^/]+|$h{$&}|g; print }' f1 f2
apple/pear/orange/apple
  • if(!$#ARGV) to determine first file (assuming exactly two files passed)
  • $h{$F[0]}=$F[1] create hash based on first field as key and second field as value
  • [^/]+ match non / characters
  • $h{$&} get the value based on matched portion from the hash


If some keys aren't found, leave it as is:

$ 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{$&} checks if the matched portion exists as key.

Another approach using awk without loop:

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

Related