在 sed 中重命名以允许在替换之前进一步匹配

在 sed 中重命名以允许在替换之前进一步匹配

我试图使用 .txt 文件重命名 fileA 中的文本sed。倒数第二列fileA是产品名称的完整描述。我想用产品 ID 替换产品名称。然而,描述中的某些文本具有相似的内容(示例如fileA)。第 2 行和第 4 行中出现了两次“Orange Juice”。

我正在生成renamefile将产品名称替换为sed.然而,sed无论“Orange Juice”匹配后是否出现“with Pull”,都用“3071”替换它找到的每个“Orange Juice”。

文件A:

AB12345    100    0    Apple juice 20/05   AB
CD67890    150    0    Orange juice with pulp 22/05   CS
EF25879    100    0    Watermelon juice 19/05   CG
GH96314    98    0    Orange juice 20/05   PU
IJ74123    95    0    Strawberry juice with lemon 17/05   ST

重新命名文件:

s/\<Apple juice\>/3071/g
s/\<Orange juice with pulp\>/3072/g
s/\<Orange juice\>/3073/g
s/\<Watermelon juice\>/3074/g
s/\<Apple juice with lemon\>/3075/g
s/\<Strawberry juice with lemon\>/3076/g

电流输出:

AB12345    100    0    3071 20/05   AB
CD67890    150    0    **3073** 22/05   CS
EF25879    100    0    3074 19/05   CG
GH96314    98    0    3073 20/05   PU
IJ74123    95    0    3076 17/05   ST

期望的输出:

AB12345    100    0    3071 20/05   AB
CD67890    150    0    3072 22/05   CS
EF25879    100    0    3074 19/05   CG
GH96314    98    0    3073 20/05   PU
IJ74123    95    0    3076 17/05   ST

我使用的是“<>”,正如在某处发现的那样,仅当存在完全匹配时它才有助于替换。然而,在这种情况下它似乎不起作用。 (错误在当前输出中以粗体显示。)

是否有更好或更有效的方法来确保字符串替换在前两个字符串匹配后考虑更多单词并将这些名称替换为其 ID?

如果我不够清楚,请告诉我。谢谢!

答案1

需要renamefile根据长度重新排序,首先替换较长的名称

awk '{ print length, $0 }' renamefile| sort -nr | cut -d" " -f2- > renamefile2

输出

s/\<Strawberry juice with lemon\>/3076/g
s/\<Orange juice with pulp\>/3072/g
s/\<Apple juice with lemon\>/3075/g
s/\<Watermelon juice\>/3074/g
s/\<Orange juice\>/3073/g
s/\<Apple juice\>/3071/g

然后就可以申请了,没有任何问题

sed -f renamefile2 fileA

描述:

awk循环行

  • length是 awk 的内置函数。当不带参数调用时,它将打印当前行的大小(更多信息位于awk 长度
  • $0当前行

以下命令将在该行本身旁边打印每行的长度

awk '{ print length, $0 }' renamefile

24 s/\<Apple juice\>/3071/g
35 s/\<Orange juice with pulp\>/3072/g
25 s/\<Orange juice\>/3073/g

sort将对输入文本进行排序

  • -n将按数字排序
  • -r反转结果,使其下降。

cut将选择文本的一部分(因为我们不需要最终脚本中的长度,并且只需要选择sed行的一部分)

  • -d" "指定此处的分隔符space
  • -f2-从字段 2 到行尾

答案2

/如果产品名称后面总是有两位数字,后面跟着一个,然后再有两位数字,您可以将它们包含在正则表达式中,并使用反向引用将它们替换为自身。

您还可以匹配前面的四个空格字符并将它们替换为自身。

重新命名文件:

s/( {4})Apple juice( [[:digit:]]{2}\/[[:digit:]]{2})/\13071\2/
s/( {4})Orange juice with pulp( [[:digit:]]{2}\/[[:digit:]]{2})/\13072\2/
s/( {4})Orange juice( [[:digit:]]{2}\/[[:digit:]]{2})/\13073\2/
s/( {4})Watermelon juice( [[:digit:]]{2}\/[[:digit:]]{2})/\13074\2/
s/( {4})Apple juice with lemon( [[:digit:]]{2}\/[[:digit:]]{2})/\13075\2/
s/( {4})Strawberry juice with lemon( [[:digit:]]{2}\/[[:digit:]]{2})/\13076\2/

输出:

$ sed -Ef renamefile fileA
AB12345    100    0    3071 20/05   AB
CD67890    150    0    3072 22/05   CS
EF25879    100    0    3074 19/05   CG
GH96314    98    0    3073 20/05   PU
IJ74123    95    0    3076 17/05   ST

答案3

使用 awk 更简单:

$ cat tst.awk
BEGIN {
    id = 3071
    map["Apple juice"]                  = id++
    map["Orange juice with pulp"]       = id++
    map["Orange juice"]                 = id++
    map["Watermelon juice"]             = id++
    map["Apple juice with lemon"]       = id++
    map["Strawberry juice with lemon"]  = id++
}
match($0,/^((\S+\s+){3})(.*\S)((\s+\S+){2})/,a) {
    $0 = a[1] map[a[3]] a[4]
    print
}

$ awk -f tst.awk file
AB12345    100    0    3071 20/05   AB
CD67890    150    0    3072 22/05   CS
EF25879    100    0    3074 19/05   CG
GH96314    98    0    3073 20/05   PU
IJ74123    95    0    3076 17/05   ST

上面使用了 GNU awk,因为您使用的是 GNU sed for\<\>单词边界。

答案4

使用 GNU sed,我们首先动态修改重命名文件(意味着您不必对其进行任何手动编辑),然后将其用作 sed 代码来执行文件中的编辑A

我们在重命名文件中所做的更改是寻找换行符作为 RHS 边界而不是 \>。但在此之前,我们在 fileA 的模式空间中插入一个换行符。

$ sed -re '
     1i\
s/(\\s+\\S+){2}\\s*$/\\n&/
     s/\\>/\\n/
' renamefile | sed -rf - fileA

输出:

AB12345    100    0    3071 20/05   AB
CD67890    150    0    3072 22/05   CS
EF25879    100    0    3074 19/05   CG
GH96314    98    0    3073 20/05   PU
IJ74123    95    0    3076 17/05   ST

相关内容