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

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

A B C D ABC

и я хочу изменить каждый A до 1 каждый B до 2 и каждый C до 3

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

1 2 3 D 123

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

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

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

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

Ответ 1

Легко в 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.

Ответ 2

Легко в 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'  

Ответ 3

Это будет работать, если ваши "слова" не содержат метакаров 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, а не строки).