Как искать и заменять произвольные буквенные строки в sed и awk (и perl) - PullRequest
0 голосов
/ 06 января 2019

Скажем, у нас есть несколько произвольных литералов в файле, которые мы должны заменить другими литералами.

Обычно мы просто набираем sed (1) или awk (1) и кодируем что-то вроде:

sed "s/$target/$replacement/g" file.txt

Но что, если $ target и / или $ replace могут содержать символы, чувствительные к sed (1), такие как регулярные выражения. Вы могли бы избежать их, но предположим, что вы не знаете, что они - они произвольны, хорошо? Вам нужно что-то кодировать, чтобы избежать всех возможных чувствительных символов - включая разделитель '/'. например,

t=$( echo "$target" | sed 's/\./\\./g; s/\*/\\*/g; s/\[/\\[/g; ...' ) # arghhh!

Это довольно неловко для такой простой проблемы.

perl (1) имеет кавычки \ Q ... \ E, но даже это не может справиться с разделителем '/' в $target.

perl -pe "s/\Q$target\E/$replacement/g" file.txt

Я только что отправил ответ !! Поэтому мой реальный вопрос: «Есть ли лучший способ сделать буквальные замены в sed / awk / perl?»

Если нет, я оставлю это здесь на случай, если оно пригодится.

Ответы [ 3 ]

0 голосов
/ 06 января 2019

С помощью awk вы можете сделать это так:

awk -v t="$target" -v r="$replacement" '{gsub(t,r)}' file

Выше ожидается, что t будет регулярным выражением, чтобы использовать его как строку, которую вы можете использовать

awk -v t="$target" -v r="$replacement" '{while(i=index($0,t)){$0 = substr($0,1,i-1) r substr($0,i+length(t))} print}' file

Вдохновленный этим постом

Обратите внимание, что это не будет работать должным образом, если строка замены содержит цель. Приведенная выше ссылка также имеет решения для этого.

0 голосов
/ 06 января 2019

quotemeta , которая реализует \Q, абсолютно выполняет то, что вы просите

всем символам ASCII, не соответствующим /[A-Za-z_0-9]/, будет предшествовать обратный слеш

Поскольку это предположительно в сценарии оболочки, проблема действительно состоит в том, как и когда переменные оболочки интерполируются и что видит программа Perl.

Лучший способ - избежать этой интерполяционной путаницы и вместо этого правильно передать эти переменные оболочки в однострочник Perl. Это можно сделать несколькими способами; см. этот пост для деталей.

Либо передавайте переменные оболочки просто как аргументы

#!/bin/bash

# define $target

perl -pe"BEGIN { $patt = shift }; s{\Q$patt}{$replacement}g" "$target" file.txt

где необходимые аргументы удаляются из @ARGV и используются в блоке BEGIN, так до выполнения; тогда file.txt обрабатывается. Здесь нет необходимости в \E в регулярном выражении.

Или используйте переключатель -s , который включает переключатели командной строки для программы

# define $target, etc

perl -s -pe"s{\Q$patt}{$replacement}g" -- -patt="$target" file.txt

-- необходим для обозначения начала аргументов, а переключатели должны предшествовать именам файлов.

Наконец, вы также можете экспортировать переменные оболочки, которые затем можно использовать в скрипте Perl через %ENV; но в целом я бы предпочел любой из вышеперечисленных подходов.


Полный пример

#!/bin/bash
# Last modified: 2019 Jan 06 (22:15)

target="/{"
replacement="&"

echo "Replace $target with $replacement"

perl -wE'
    BEGIN { $p = shift; $r = shift }; 
    $_=q(ah/{yes); s/\Q$p/$r/; say
' "$target" "$replacement"

Это печатает

Replace /{ with &
ah&yes

где я использовал символы, упомянутые в комментарии.

Другой способ

#!/bin/bash
# Last modified: 2019 Jan 06 (22:05)

target="/{"
replacement="&"

echo "Replace $target with $replacement"

perl -s -wE'$_ = q(ah/{yes); s/\Q$patt/$repl/; say' \
    -- -patt="$target" -repl="$replacement"

, где код разбит на строки для удобства чтения (и, следовательно, требуется \). Та же распечатка.

0 голосов
/ 06 января 2019

Я снова!

Вот более простой способ использования xxd (1):

t=$( echo -n "$target" | xxd -p | tr -d '\n')
r=$( echo -n "$replacement" | xxd -p | tr -d '\n')
xxd -p file.txt | sed "s/$t/$r/g" | xxd -p -r

... поэтому мы кодируем исходный текст в шестнадцатеричном формате с помощью xxd (1) и выполняем поиск-замену с использованием шестнадцатеричных строк поиска. Наконец, мы шестнадцатерично декодируем результат.

РЕДАКТИРОВАТЬ: я забыл удалить \n из вывода xxd (| tr -d '\n'), чтобы шаблоны могли охватывать 60 столбцов вывода xxd. Конечно, это зависит от способности GNU sed работать на очень длинных линиях (ограниченных только памятью).

РЕДАКТИРОВАТЬ: это также работает на многострочных целях, например,

TARGET = $ 'Foo \ NBAR' замена = $ 'бар \ nfoo'

...