sed: заменить несколько шаблонов, но не одной строкой

Можно ли изменить шаблоны умножения на разные значения в одной команде? скажем, у меня есть

A B C D ABC

и я хочу изменить каждый А до 1, каждый В до 2 и каждый С до 3

поэтому вывод будет

1 2 3 D 123

так как у меня есть 3 шаблона для изменения, я бы хотел избежать их замены отдельно. Я думал, что будет что-то вроде

sed -r s/'(A|B|C)'/(1|2|3)/ 

но, конечно, это просто заменить A или B или C на (1|2|3). Я должен только упомянуть, что мои реальные образцы более сложны чем это...

благодарю вас!

5 ответов

Решение

Легко в Perl:

perl -pe '%h = (A => 1, B => 2, C => 3); s/(A|B|C)/$h{$1}/g'

Если вы используете более сложные шаблоны, поместите более конкретные шаблоны перед более общими в альтернативном списке. Сортировка по длине может быть достаточно:

perl -pe 'BEGIN { %h = (A => 1, AA => 2, AAA => 3);
              $re = join "|", sort { length $b <=> length $a } keys %h; }
          s/($re)/$h{$1}/g'

Чтобы добавить границы слов или строк, просто измените шаблон на

/\b($re)\b/
# or
/^($re)$/
# resp.

Легко в sed:

sed 's/WORD1/NEW_WORD1/g;s/WORD2/NEW_WORD2/g;s/WORD3/NEW_WORD3/g'

Вы можете разделить несколько команд в одной строке ;


Обновить

Вероятно, это было слишком легко. NeronLeVelu отметил, что приведенная выше команда может привести к нежелательным результатам, потому что вторая замена может даже касаться результатов первой замены (и так далее).

Если вы заботитесь об этом, вы можете избежать этого побочного эффекта с t команда. t команда переходит к метке (или к концу скрипта, если метка отсутствует), если успешная замена произошла раньше.

Мы не используем метку, так как мы не хотим, чтобы дальнейшие замены происходили в случае успеха:

sed 's/WORD1/NEW_WORD1/g;t;s/WORD2/NEW_WORD2/g;t;s/WORD3/NEW_WORD3/g'  

Это сработает, если ваши "слова" не содержат метасхем RE (. *? И т. Д.):

$ cat file
there is the problem when the foo is closed

$ cat tst.awk
BEGIN {
    split("the a foo bar",tmp)
    for (i=1;i in tmp;i+=2) {
        old = (i>1 ? old "|" : "\\<(") tmp[i]
        map[tmp[i]] = tmp[i+1]
    }
    old = old ")\\>"
}
{
    head = ""
    tail = $0
    while ( match(tail,old) ) {
        head = head substr(tail,1,RSTART-1) map[substr(tail,RSTART,RLENGTH)]
        tail = substr(tail,RSTART+RLENGTH)
    }
    print head tail
}

$ awk -f tst.awk file
there is a problem when a bar is closed

Очевидно, что приведенное выше отображает "the" в "a" и "foo" в "bar" и использует GNU awk для границ слов.

Если ваши "слова" содержат метасхемы RE и т. Д., Вам нужно решение на основе строк, использующее index() вместо основанного на RE, использующего match() (Обратите внимание, что sed ТОЛЬКО поддерживает RE, а не строки).

заменить функцией обратного вызова в javascript

похоже на решение perl от choroba

      var i = 'abcd'
var r = {ab: "cd", cd: "ab"}

var o = i.replace(/ab|cd/g, (...args) => r[args[0]])

o == 'cdab'

можно оптимизировать с помощью таких групп захвата, как /(ab)|(cd)/gи проверка args[i]за undefinedценности

Использование Raku (ранее известного как Perl_6)

Адаптация элегантного (первого) ответа @Choroba на Perl, представленного ниже в Raku:

      ~$ raku -pe 'my %h = (a => 1, b => 2, c => 3); s:g/ (a|b|c) /%h{"$0"}/ ;'  file

#OR

~$ raku -pe 'my %h = (a => 1, b => 2, c => 3); s:g[ (a|b|c) ] = "%h{$0}" ;'  file

Следует отметить, что в Раку сингл|Alternation-pipe обозначает чередование «самого длинного соответствия токенов». Если вам нужно поведение Perl(5) («первый список заменяется первым и т. д.») в Raku, вы используете двойной||чередование-труба.

Напротив, если вы предпочитаете последовательную замену с использованием Raku, примеры можно найти в разделе Конкатенация `s///` в raku .

Ссылки:
https://docs.raku.org/language/5to6-nutshell#Longest_token_matching_(LTM)_displaces_alternation
https://docs.raku.org/language/regexes#Longest_alternation:_|
https://raku.org

Другие вопросы по тегам