использовать 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 команда переходит к метке, если была какая-либо подстановка, сделанная с момента последнего чтения строки или перехода к метке.


учитывая обсуждение в комментариях, есть несколько моментов, заслуживающих упоминания:

  1. на относится к sed на MacOS X (протестировано 10.7.2). Соответствующий вариант для GNU версии sed и -r (или --regex-extended). The согласуется с grep -E (который также использует расширенные регулярные выражения). "Классические системы Unix" не поддерживают EREs с sed (Solaris 10, AIX 6, HP-UX 11).

  2. можно заменить на ? я использовал (который является единственным символом, который заставляет использовать 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_"
    
  3. даже с обозначением 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'),'&#10;')">
            <xsl:for-each select="tokenize(.,'&quot;')">
                <xsl:value-of select="if (position() mod 2 = 0) 
                  then concat('&quot;',translate(.,' ','_'),'&quot;') else ."></xsl:value-of>
            </xsl:for-each>
            <xsl:text>&#10;</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. добавлены нами

для этого мы можем закончить каждую строку символом, указывающим, к какому классу она принадлежит. Я просто использовать 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

осталось для читателя, чтобы работать.