Есть ли способ предотвратить интерпретацию замещающей строки в sed? [закрыто]

16

Если вы хотите заменить ключевое слово на строку с помощью sed, sed старается интерпретировать заменяемую строку. Если в строке замены есть символы, которые sed считает специальными, например, символ '/', то произойдет сбой, если, конечно, вы не предполагали, что в строке замены есть символы, которые говорят sed, как действовать.

Пример:

VAR="hi/"

sed "s/KEYWORD/$VAR/g" somefile

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

Tal
источник
Если вы хотите поместить специальные символы в sedних и не использовать их, просто удалите их с обратной косой черты. VAR='hi\/'не дает такой проблемы.
Wildcard
6
Почему все отрицательные? Мне кажется, это вполне разумный вопрос
roaima
sed(1)просто интерпретирует, что он получает. В вашем случае он получает это через интерполяцию оболочки. Я считаю, что вы не можете делать, как хотите, но проверьте руководство. Я знаю, что в Perl (который делает проходимую sedзамену с гораздо более богатыми регулярными выражениями), вы можете указать строку, которую нужно воспринимать буквально, опять же, проверьте руководство.
vonbrand
Связанный stackoverflow.com/questions/407523/…
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功

Ответы:

4

Есть только 4 специальных символов в сменной части: \, &, перевод строки и разделитель ( ссылка )

$ VAR='abc/def&ghi\foo
next line'

$ repl=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$VAR")

$ echo "$repl"
abc\/def\&ghi\\foo\
next line

$ echo ZYX | sed "s/Y/$repl/g"
Zabc/def&ghi\foo
next lineX
Гленн Джекман
источник
Это имеет ту же проблему, что и решение Antti - если строка замены превышает определенную длину, вы получаете ошибку «Список аргументов слишком длинный». Кроме того, что если в строке замены есть символы «[», «]», «*», «.» И другие подобные символы? Сед действительно не будет интерпретировать это?
Тал
Замена сторона s///является не регулярным выражением, это действительно просто строка (для обратных косых черт побегов и , кроме &). Если строка замены слишком длинная, однострочная оболочка не является вашим решением.
Гленн Джекман
Очень полезный список, если, например, вашей строкой замены является кодированный в base64 текст (например, замена заполнителя на ключ SHA256). Тогда это просто разделитель, о котором нужно беспокоиться.
Хит
4

Вы можете использовать Perl вместо sed с помощью -p(предположите, что цикл за вводом) и -e(укажите программу в командной строке). С Perl вы можете обращаться к переменным окружения, не интерполируя их в оболочке. Обратите внимание, что переменная должна быть экспортирована :

export VAR='hi/'
perl -p -e 's/KEYWORD/$ENV{VAR}/g' somefile

Если вы не хотите экспортировать переменную везде, просто предоставьте ее только для этого процесса:

PATTERN="$VAR" perl -p -e 's/KEYWORD/$ENV{PATTERN}/g' somefile

Обратите внимание, что синтаксис регулярного выражения в Perl по умолчанию немного отличается от синтаксиса sed.

Антти Хаапала
источник
Это казалось очень многообещающим, но при тестировании я получаю ошибку «Список аргументов слишком длинный», потому что моя замещающая строка слишком длинная, что имеет смысл - используя этот метод, мы используем всю замещающую строку как часть аргументов, которые мы даем для Perl, так что есть предел того, как долго это может быть.
Тал
1
Нет, это будет PATTERN переменная окружения , а не аргументы. В любом случае, эта ошибка будет E2BIG, которую вы бы в равной степени получили, если бы использовали sed.
Антти Хаапала
2

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

В viвы можете избежать любого символа управления, набрав Ctrl-V (чаще записываются в виде ^V). Поэтому, если вы используете какой-либо управляющий символ (я часто использую ^Aв качестве разделителя в этих случаях), ваша sedкоманда будет прерываться, только если этот непечатаемый символ присутствует в переменной, в которую вы добавляете.

Таким образом, вы напечатаете, "s^V^AKEYWORD^V^A$VAR^V^Ag"и то, что вы получите (в vi), будет выглядеть так:

sed "s^AKEYWORD^A$VAR^Ag" somefile

Это будет работать до тех пор, пока $VARне будет содержать ^Aнепечатный символ, что крайне маловероятно.


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


Однако на самом деле нужно знать больше, чем строка-разделитель. Например, &когда присутствует в строке замены, означает «весь текст, который был сопоставлен». Например, s/stu../my&/заменил бы «stuff» на «mystuff», «stung» на «mystung» и т. Д. Итак, если у вас есть какой-либо символ в переменной, который вы добавляете в качестве строки замены, но вы хотите использовать литерал только значение переменной, тогда у вас есть некоторая очистка данных, прежде чем вы сможете использовать переменную в качестве строки замены в sed. (Однако очистка данных может быть выполнена sedтакже.)

Wildcard
источник
Это моя точка зрения - замена строки другой строкой - очень простая операция. Неужели все должно быть так сложно, как выяснить, какие символы sed не нравятся, и использовать sed для очистки собственного ввода? Это звучит смешно и излишне запутанно. Я не профессиональный программист, но я уверен, что могу написать небольшую функцию, которая заменяет ключевое слово на строку практически на любом языке, с которым я когда-либо сталкивался, включая bash - я просто надеялся на простой Linux Решение с использованием существующих инструментов - я не могу поверить, что там нет ни одного.
Тал
1
@Tal, если ваша замещающая строка имеет длину «100 страниц», как вы упомянули в другом комментарии ... вы вряд ли сможете назвать это «простым» вариантом использования. Кстати, ответ здесь - Perl - я просто не изучил Perl. Сложность здесь заключается в том, что вы хотите разрешить ЛЮБОЙ произвольный ввод в качестве строки замены в регулярном выражении .
Wildcard
Вы можете использовать множество других решений, многие из которых очень просты. Например, если ваша строка замены на самом деле линия на основе и не должны быть вставлены в середине строки, используйте sed«ы iкомандной nsert. Но sedэто не хороший инструмент для обработки большого количества текста сложными способами. Я выложу другой ответ, показывающий, как это сделать awk.
Wildcard
1

Вместо этого вы можете использовать a ,или a, |и он будет восприниматься как разделитель, а технически вы можете использовать что угодно

со страницы руководства

\cregexpc
           Match lines matching the regular expression regexp.  The  c  may
      be any character.

Как вы можете видеть, вы должны начинать с \ перед вашим разделителем в начале, тогда вы можете использовать его как разделитель.

из документации http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command :

The / characters may be uniformly replaced by any other single character 
within any given s command.

The / character (or whatever other character is used in its stead) can appear in 
the regexp or replacement only if it is preceded by a \ character.

Пример:

sed -e 'somevar|s|foo|bar|'
echo "Hello all" | sed "s_all_user_"
echo "Hello all" | sed "s,all,user,"

echo "Hello/ World" | sed "s,Hello/,Neo,"

user3566929
источник
Вы говорите о том, чтобы разрешить использование одного конкретного символа в строке замены - в данном случае, "/". Я говорю о том, чтобы предотвратить попытки интерпретации замещающей строки в целом. Независимо от того, какой символ вы используете ("/", ",", "|" и т. Д.), Вы всегда рискуете, чтобы этот символ всплыл в строке замены. Кроме того, начальный символ - не единственный специальный символ, который заботится о sed, не так ли?
Тал
@Tal нет, он может взять что-нибудь вместо, /и он будет игнорировать /счастливо, как я только что указал .. на самом деле, вы даже можете найти его и заменить его в строке >>> я редактировал с примером >>> эти вещи не так безопасны, и вы всегда найдете умнее, чувак
user3566929
@Tal, почему ты хочешь предотвратить это? Я имею в виду, что, sedво-первых, для чего нужен ваш проект?
user3566929
Все, что мне нужно, это заменить ключевое слово на строку. Похоже, что sed - самый распространенный способ сделать это в Linux. Строка может быть длиной в 100 страниц. Я не хочу пытаться дезинфицировать строку, чтобы sed не волновался при ее чтении - я хочу, чтобы она могла обрабатывать любые символы в строке, и под словом «handle» я имею в виду не пытаться найти магический смысл внутри.
Тал
1
@Tal, bashэто НЕ для работы со строками. На всех, на всех, на всех. Это для манипулирования файлами и координации команд . Это имеет некоторую встроенную удобную функциональность для строк, но на самом деле ограниченную и не очень быструю, если это главное, что вы делаете. Смотрите "Почему использование цикла оболочки для обработки текста считается плохой практикой?" Некоторые инструменты, которые предназначены для обработки текста, в порядке от самых простых до самых мощных: sed, awkи Perl.
Wildcard
1

Если он основан на строках и заменяется только одной строкой, я рекомендую добавить сам файл с помощью строки замены printf, сохраняя эту первую строку в месте sedдля хранения, и добавляя ее при необходимости. Таким образом, вам не нужно беспокоиться о специальных символах. (Единственное допущение здесь состоит в том, что он $VARсодержит одну строку текста без каких-либо символов новой строки, о чем вы уже говорили в комментариях.) Кроме строк новой строки, VAR может содержать все что угодно, и это будет работать независимо.

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/KEYWORD/g'

printf '%s\n'будет печатать содержимое $VARв виде буквенной строки, независимо от ее содержимого, за которой следует новая строка. ( echoв некоторых случаях будет выполнять другие действия, например, если содержимое $VARначинается с дефиса - это будет интерпретироваться как передаваемый флаг опции echo.)

Скобки используются для добавления вывода printfк содержимому того, somefileкак оно передается sed. Здесь важны пробелы, разделяющие фигурные скобки, и точка с запятой перед закрывающей фигурной скобкой.

1{h;d;};как sedкоманда будет хранить первую строку текста в sed«S трюма , затем dдалить линию (а не печать).

/KEYWORD/применяет следующие действия ко всем строкам, которые содержат KEYWORD. Действие - это get, которое получает содержимое пространства удержания и удаляет его вместо пространства шаблона - другими словами, всей текущей строки. (Это не для замены только части строки.) Между прочим, пространство удержания не освобождается, а просто копируется в пространство шаблона, заменяя все, что там есть.

Если вы хотите привязать свое регулярное выражение, чтобы оно не совпадало со строкой, которая просто содержит KEYWORD, а содержит только строку, в которой нет ничего другого, кроме KEYWORD, добавьте начало строки anchor ( ^) и конец строки anchor ( $) в Ваше регулярное выражение:

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/^KEYWORD$/g'
Wildcard
источник
Кажется великолепным, если ваш VAR длиной в одну строку. Я на самом деле упомянул в комментариях, что VAR "может быть длиной в 100 страниц", а не в одну строку. Извините за путаницу.
Тал
0

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

$ var='a/b/c';var="${var//\//\\/}";echo 'this is a test' | sed "s/i/$var/g"

вывод

tha/b/cs a/b/cs a test

Вы можете поместить расширение параметра непосредственно в вашу команду sed:

$ var='a/b/c';echo 'this is a test' | sed "s/i/${var//\//\\/}/g"

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

Другим вариантом будет использование сценария, написанного на awk, perl или Python, или C-программы, для выполнения замен вместо использования sed.


Вот простой пример в Python, который работает, если заменяемое ключевое слово является полной строкой во входном файле (не считая перевода строки). Как видите, это по сути тот же алгоритм, что и в вашем примере с Bash, но он читает входной файл более эффективно.

import sys

#Get the keyword and replacement texts from the command line
keyword, replacement = sys.argv[1:]
for line in sys.stdin:
    #Strip any trailing whitespace
    line = line.rstrip()
    if line == keyword:
        line = replacement
    print(line)
PM 2Ring
источник
Это просто еще один способ дезинфекции ввода, и не очень хороший, поскольку он обрабатывает только один конкретный символ ('/'). Как отметил Уилкард, следует опасаться не только строки разделителя.
Тал
Честный звонок. Например, если текст замены содержит какие-либо последовательности с обратной косой чертой, они будут интерпретированы, что может быть нежелательно. Одним из способов решения этой проблемы было бы преобразование проблемных символов (или всего этого) в \xescape-последовательности в стиле. Или использовать программу, которая может обрабатывать произвольный ввод, как я упоминал в моем предыдущем абзаце.
PM 2Ring
@Tal: я добавлю простой пример Python в мой ответ.
PM 2Ring
Скрипт python отлично работает и, кажется, делает именно то, что делает моя функция, только намного эффективнее. К сожалению, если основным скриптом является bash (как в моем случае), это требует использования вторичного внешнего скрипта на python.
Тал
-1

Это путь, которым я пошел:

#Replaces a keyword with a long string
#
#This is normally done with sed, but sed
#tries to interpret the string you are
#replacing the keyword with too hard
#
#stdin - contents to look through
#Arg 1 - keyword to replace
#Arg 2 - what to replace keyword with
replace() {
        KEYWORD="$1"
        REPLACEMENT_STRING="$2"

        while IFS= read -r LINE
        do
                if [[ "$LINE" == "$KEYWORD" ]]
                then
                        printf "%s\n" "$REPLACEMENT_STRING"
                else
                        printf "%s\n" "$LINE"
                fi
        done < /dev/stdin
}

это прекрасно работает в моем случае, потому что мое ключевое слово в строке само по себе. Если ключевое слово находится в строке с другим текстом, это не будет работать.

Я все еще хотел бы знать, есть ли простой способ сделать это, не включающий в себя кодирование моего собственного решения.

Tal
источник
1
Если вы действительно беспокоитесь о специальных символах и надежности, вы не должны использовать echoвообще. Используйте printfвместо этого. И обработка текста в цикле оболочки - плохая идея.
Wildcard
1
Было бы полезно, если бы вы упомянули в вопросе, что ключевое слово всегда будет полной строкой. FWIW, Bash readдовольно медленно. Он предназначен для обработки интерактивного пользовательского ввода, а не для обработки текстовых файлов. Это медленно, потому что он читает stdin char за char, делая системный вызов для каждого char.
PM 2Ring
@PM 2Ring В моем вопросе не упоминалось, что ключевое слово находится в отдельной строке, потому что я не хочу, чтобы ответ работал просто в таком ограниченном количестве случаев - я хотел что-то, что могло бы легко работать независимо от того, где ключевое слово было. Я также никогда не говорил, что мой код эффективен - если бы это было так, я бы не искал альтернативы ...
Тал
@Wildcard Если только я что-то не упустил, printf абсолютно интерпретирует специальные символы, и гораздо больше, чем «echo» по умолчанию. printf "hi\n"заставит printf печатать новую строку, пока echo "hi\n"печатает как есть.
Тал
@Tal, «f» printfозначает «формат» - первый аргумент для printf- это спецификатор формата . Если спецификатор %s\n, что означает «строка с последующим переводом строки», ничего в следующем аргументе не будет интерпретироваться или переведены printf на все . (Разумеется, оболочка все еще может его интерпретировать; лучше всего указывать все в одинарных кавычках, если это буквальная строка, или в двойных кавычках, если вы хотите раскрыть переменную.) Более подробные сведения см. В моем ответеprintf .
Wildcard