Используя sed, как можно дополнительно преобразовать групповые обратные ссылки?


Могу ли я использовать sed для замены соответствия регулярному выражению преобразованием групповой обратной ссылки в регулярном выражении?

Задача

Предположим, я хочу заменить строки вида:

(@ -p <fqdn>)

Таких совпадений может быть несколько на строку.

С:

<fqdn with dots replaced by underscores>

Пример

com.xyz (@ -p com.abc.def) com.pqr.stu (@ -p com.ghi)

Станет:

com.xyz com_abc_def com.pqr.stu com_ghi

Идеи

Чтобы начать работу над решением, рассмотрим:

$ sed 's|(@ -p ([^)]*))|1|' <<<"com.xyz (@ -p com.abc) com.pqr (@ -p com.ghi)"
com.xyz com.abc com.pqr com.ghi

Это делает соответствующий выбор; однако теперь мне все еще нужно, чтобы часть 1 была преобразована с помощью s|.|_|g.

Может ли кто-нибудь показать, как это можно сделать с помощью sed? Мое окружение-bash 4.2.46 (1)-release, CentOS 7.3.1611.

Примечания:

  • я добавляю это к существующему сценарию sed, поэтому я очень предпочитаю решение sed, а не передачу результата моего текущего сценария sed другому строковому процессору, такому как awk. Если нет решения sed для этого проблема, то я рассмотрю awk решения в следующем.
  • мой вопрос специфичен для шаблона, показанного в приведенном выше примере.
3 2

3 ответа:

Вы можете использовать gnu awk:

s='com.xyz (@ -p com.abc.def) com.pqr.stu'
awk -v RS='\\(@ -p [^)]+\\)' '{
       ORS=gensub(/.* |\)/,"","g",gensub(/\./,"_","g",RT))} 1' <<< "$s"

com.xyz com_abc_def com.pqr.stu

Если целевая строка встречается только один раз (на строку ввода), вы можете использовать пространство удержания для выполнения двойной замены, например:

Единственная замена

#Copy input line to the hold space: A(@B)C -- A(@B)C
h

#Replace the target substring with (@) (a "marker" string): A(@)C -- A(@B)C 
s/(@ -p [^)]*)/(@)/

#Exchange the content of the pattern space and hold space: A(@B) -- A(@)C 
x

#Strip off anything except the target substring value: B -- A(@)C
s/.*(@ -p \([^)]*\)).*/\1/

#Modify the target substring as appropriate: B' -- A(@)C
y/./_/

#Append the content of the hold space back to the pattern space: B'\nA(@)C -- 
G

#Merge the lines, replacing the "marker" string with the processed value: AB'C
s/\(.*\)\n\(.*\)(@)/\2\1/

Пример вывода:

%echo "com.xyz (@ -p com.abc) com.pqr" | sed -f doublereplace.sed 
com.xyz com_abc com.pqr

Множественные замены

Зацикленная версия будет выглядеть следующим образом:

#Loop label 
:start /(@/ { 
    #Copy input line to the hold space: A(@B)C -- A(@B)C
    h

    #Replace the target substring with (@) (a "marker" string): A(@)C -- A(@B)C 
    s/(@ -p [^)]*)/(@)/

    #Exchange the content of the pattern space and hold space: A(@B) -- A(@)C 
    x

    #Strip off anything except the target substring value: B -- A(@)C
    s/[^(]*(@ -p \([^)]*\)).*/\1/

    #Modify the target substring as appropriate: B' -- A(@)C
    y/./_/

    #Append the content of the hold space back to the pattern space: B'\nA(@)C -- 
    G

    #Merge the lines, replacing marker string with the processed value: AB'C
    s/\(.*\)\n\(.*\)(@)/\2\1/

    #Loop
    b start
}

Пример вывода:

%echo "com.xyz (@ -p com.abc.def) com.pqr.stu (@ -p com.ghi)" |
sed -f doublereplace.sed

com.xyz com_abc_def com.pqr.stu com_ghi

Закаленный

Немного более надежная версия может использовать новые строки в качестве разделителей / маркерной строки:

#Loop label 
:start /(@ -p [^)]*)/ { 
    #Copy input line to the hold space: A(@B)C -- A(@B)C
    h

    #Replace the target substring with (@) (a "marker" string): A\nC -- A(@B)C 
    s/(@ -p [^)]*)/\n/

    #Exchange the content of the pattern space and hold space: A(@B)C -- A\nC 
    x

    #Isolate the first instance of a target substring to a separate line A\n(@B)\nC -- A\n\C 
    s/\((@ -p [^)]*)\)/\n\1\n/1

    #Strip off anything except the target substring value: B -- A\nC
    s/.*\n(@ -p \([^)]*\))\n.*/\1/

    #Modify the target substring as appropriate: B' -- A\nC
    y/./_/

    #Append the content of the hold space back to the pattern space: B'\nA\nC -- 
    G

    #Merge the lines, replacing marker string with the processed value: AB'C
    s/\(.*\)\n\(.*\)\n/\2\1/

    #Loop
    b start
}

Это позволит для любого неполного @() конструкции во входных данных, как (@ t.i.m.e.s):

%echo "com.xyz (@ -p com.abc.def) fails (@ t.i.m.e.s) com.pqr.stu (@ -p com.ghi)" |
sed -f doublereplace.sed

com.xyz com_abc_def fails (@ t.i.m.e.s) com.pqr.stu com_ghi

поглазеть решение:

str="com.xyz (@ -p com.abc.def) com.pqr.stu"
awk 'match($0, /\(@ -p ([^)]+)\)/, a){ "echo "a[1]" | tr \".\" \"_\"" | getline v; 
     sub(/\(@ -p ([^)]+)\)/,v, $0); print }' <<< $str

Вывод:

com.xyz com_abc_def com.pqr.stu