использовать sed для замены текста в кавычки
у меня есть этот тестовый файл.
[root@localhost ~]# cat f.txt "a aa" MM "bbb b" MM MM MM"b b " [root@localhost ~]#
Я хочу, чтобы заменить все символы пробела в кавычках, обратите внимание, только в кавычках. Все символы из кавычек трогать не следует. То есть, то, что я хочу, это что-то похожее на:
"a_aa" MM "bbb__b" MM MM MM"b_b_"
может ли это быть реализовано с помощью sed?
спасибо,
4 ответов
это совершенно нетривиальный вопрос.
это работает, заменяя первое пространство внутри кавычек подчеркиванием:
$ sed 's/\("[^ "]*\) \([^"]*"\)/_/g' f.txt
"a_aa" MM "bbb_ b"
MM MM
MM"b_b "
$
в этом примере, где внутри любой кавычки не более двух пробелов, возникает соблазн просто повторить команду, но она дает неверный результат:
$ sed -e 's/\("[^ "]*\) \([^"]*"\)/_/g' \
> -e 's/\("[^ "]*\) \([^"]*"\)/_/g' f.txt
"a_aa"_ MM "bbb_ b"
MM MM
MM"b_b_"
$
если ваша версия sed
поддерживает расширенные регулярные выражения, то это работает для образца данные:
$ sed -E \
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/_/' \
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/_/' \
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/_/' \
> f.txt
"a_aa" MM "bbb__b"
MM MM
MM"b_b_"
$
вы должны повторить это ужасное регулярное выражение для каждого пространства в двойных кавычках-следовательно, три раза для первой строки данных.
регулярное выражение можно объяснить как:
- начиная с начала строки,
- ищите последовательности "ноль или более не кавычек, необязательно с последующей цитатой, без пробелов или кавычек и цитатой", вся сборка повторяется ноль или более раз,
- Далее следует цитата, ноль или больше не-кавычек, не-пробелов, пробелов и нуля или более не-кавычек, а также кавычки.
- замените соответствующий материал на ведущую часть, материал в начале текущего цитируемого отрывка, подчеркивание и конечный материал текущего цитируемого отрывка.
из - за стартового якоря это должно быть повторено один раз за пробел...но!--8--> есть циклы, поэтому мы можем сделать это с:
$ sed -E -e ':redo
> s/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/_/
> t redo' f.txt
"a_aa" MM "bbb__b"
MM MM
MM"b_b_"
$
на :redo
определяет ярлык;s///
команда по-прежнему;t redo
команда переходит к метке, если была какая-либо подстановка, сделанная с момента последнего чтения строки или перехода к метке.
учитывая обсуждение в комментариях, есть несколько моментов, заслуживающих упоминания:
на относится к
sed
на MacOS X (протестировано 10.7.2). Соответствующий вариант для GNU версииsed
и-r
(или--regex-extended
). The согласуется сgrep -E
(который также использует расширенные регулярные выражения). "Классические системы Unix" не поддерживают EREs сsed
(Solaris 10, AIX 6, HP-UX 11).-
можно заменить на
?
я использовал (который является единственным символом, который заставляет использовать ERE вместо BRE) с*
, а затем разберитесь с круглыми скобками (которые требуют обратных косых черт перед ними в BRE, чтобы сделать их скобками захвата), оставив сценарий:sed -e ':redo s/^\(\([^"]*\("[^ "]*"\)*\)*\)\("[^ "]*\) \([^"]*"\)/_/g t redo' f.txt
это производит тот же выход на том же входе - я попробовал несколько более сложных шаблонов на входе:
"a aa" MM "bbb b" MM MM MM"b b " "c c""d d""e e" X " f "" g " "C C" "D D" "E E" x " F " " G "
это дает выход:
"a_aa" MM "bbb__b" MM MM MM"b_b_" "c_c""d_d""e__e" X "_f_""_g_" "C_C" "D_D" "E__E" x "_F_" "_G_"
-
даже с обозначением BRE,
sed
поддерживает\{0,1\}
обозначение для указания 0 или 1 вхождений предыдущего термина RE, поэтому?
версия может быть переведена на BRE с помощью:sed -e ':redo s/^\(\([^"]*\("[^ "]*"\)\{0,1\}\)*\)\("[^ "]*\) \([^"]*"\)/_/g t redo' f.txt
это производит такой же выход как другое альтернативы.
как-то необычный ответ в XSLT 2.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"></xsl:output>
<xsl:template name="init">
<xsl:for-each select="tokenize(unparsed-text('f.txt'),' ')">
<xsl:for-each select="tokenize(.,'"')">
<xsl:value-of select="if (position() mod 2 = 0)
then concat('"',translate(.,' ','_'),'"') else ."></xsl:value-of>
</xsl:for-each>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
чтобы проверить, если, просто получить saxon.jar на sourceforge и используйте следующую командную строку:
java -jar saxon9.jar -it:init regexp.xsl
файл xslt включает ссылку на f.txt, текстовый файл должен находиться в том же каталоге, что и файл xslt. Это можно легко изменить, указав параметр в таблице стилей.
он работает за один проход.
Это было бы очень легко, если бы цитируемый текст был на отдельных строках. Поэтому один из подходов-разделить текст так, чтобы у вас было это, сделать легкое преобразование, а затем перестроить строки.
разделение текста легко, но нам нужно будет различать новые строки, которые были
- уже присутствует в файле
- добавлены нами
для этого мы можем закончить каждую строку символом, указывающим, к какому классу она принадлежит. Я просто использовать 1 и 2, соответствующие непосредственно выше. В sed мы имеем:
sed -e 's/$/1/' -e 's/"[^"]*"/2\n&2\n/g'
это производит:
2
"a aa"2
MM 2
"bbb b"2
1
MM MM1
MM2
"b b "2
1
это легко преобразовать, просто используйте
sed -e '/".*"/ s/ /_/g'
дав
2
"a_aa"2
MM 2
"bbb__b"2
1
MM MM1
MM2
"b_b_"2
1
наконец, нам нужно собрать его обратно. Это на самом деле довольно ужасно в sed, но возможно с использованием пространства удержания:
sed -e '/1$/ {s/1$//;H;s/.*//;x;s/\n//g}' -e '/2$/ {s/2$//;H;d}'
(Это было бы намного яснее, например, awk.)
соедините эти три шага вместе, и вы закончите.
это может сработать для вас:
sed 's/^/\n/;:a;s/\(\n[^"]*"[^ "]*\) \([^"]*"\)\n*/_\n/;ta;s/\n//;ta;s/\n//' file
объяснение:
добавить \n
в начале строки это будет использоваться для удара по заменам. Заменить один with a
_
внутри "
и пока это там место \n
готов к следующему раунду замен. Заменив все ' s, удалить
\n
и повторите. Когда все замены произошли, удалите \n
разделитель.
или это:
sed -r ':a;s/"/\n/;s/"/\n/;:b;s/(\n[^\n ]*) ([^\n]*\n)/_/g;tb;s/\n/%%%/g;ta;s/%%%/"/g' file
объяснение:
заменить первый сет ""
С \n
. Замените первое пространство между новыми строками на _
повторите. Заменить \n
'S с уникальным разделителем (%%%
), повторите с самого начала. Убирать в конце, заменив все %%%
С "
s.
третий вариант:
sed 's/"[^"]*"/\n&\n/g;$!s/$/@@@/' file |
sed '/"/y/ /_/;1{h;d};H;${x;s/\n//g;s/@@@/\n/g;p};d'
объяснение:
окружить все приведенные выражения ("..."
) С новой строки (\n
' s). Вставить разделитель конца строки @@@
на всех, кроме последней строки. Результат трубы на секунду . Перевести все С
_
'S для строк "
в них. Сохраните каждую строку в пространстве хранения (HS). В конце файла перейдите в HS и удалите все \n
и замените разделители конца строки на \n
' s
и наконец:
sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"''"'"')/g;s/^/echo /' file | sh
или GNU sed:
sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"''"'"')/g;s/^/echo /e' file
осталось для читателя, чтобы работать.