Это совершенно нетривиальный вопрос.
Это работает, заменяя первый пробел внутри кавычек подчеркиванием:
$ sed 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' f.txt
"a_aa" MM "bbb_ b"
MM MM
MM"b_b "
$
В этом примере, где внутри кавычек не более двух пробелов, заманчиво просто повторить команду, но это дает неверный результат:
$ sed -e 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' \
> -e 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' f.txt
"a_aa"_ MM "bbb_ b"
MM MM
MM"b_b_"
$
Если ваша версия sed
поддерживает «расширенные регулярные выражения», то это работает для примеров данных:
$ sed -E \
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \
> f.txt
"a_aa" MM "bbb__b"
MM MM
MM"b_b_"
$
Вы должны повторить это ужасное регулярное выражение для каждого пробела в двойных кавычках - следовательно, три раза для первой строки данных.
Регулярное выражение можно объяснить так:
- Начиная с начала строки,
- Ищите последовательности «ноль или более не кавычек, за которыми может следовать кавычка, без пробелов или кавычек и кавычки», вся сборка повторяется ноль или более раз,
- За ним следует кавычка, ноль или более не кавычек, пробелы, пробел и ноль или более не кавычек, а также кавычка.
- Замените соответствующий материал на ведущую часть, материал в начале текущего цитируемого фрагмента, подчеркивание и завершающий материал текущего цитируемого фрагмента.
Из-за начального якоря это нужно повторять один раз для пробела ... но sed
имеет циклическую конструкцию, поэтому мы можем сделать это с:
$ sed -E -e ':redo
> s/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/
> t redo' f.txt
"a_aa" MM "bbb__b"
MM MM
MM"b_b_"
$
:redo
определяет метку; команда s///
такая же, как и раньше; команда t redo
переходит на метку, если с момента последнего чтения строки или перехода к метке была произведена какая-либо подстановка.
Учитывая обсуждение в комментариях, стоит упомянуть пару моментов:
Опция -E
применяется к sed
в MacOS X (проверено 10.7.2). Соответствующая опция для версии GNU sed
- -r
(или --regex-extended
). Параметр -E
соответствует grep -E
(который также использует расширенные регулярные выражения). «Классические системы Unix» не поддерживают ERE с sed
(Solaris 10, AIX 6, HP-UX 11).
Вы можете заменить ?
, который я использовал (это единственный символ, который вынуждает использовать ERE вместо BRE), на *
, а затем иметь дело с круглыми скобками (которые требуют обратной косой черты перед из них в BRE, чтобы сделать их в скобках), оставив сценарий:
sed -e ':redo
s/^\(\([^"]*\("[^ "]*"\)*\)*\)\("[^ "]*\) \([^"]*"\)/\1\4_\5/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\}\)*\)\("[^ "]*\) \([^"]*"\)/\1\4_\5/g
t redo' f.txt
Это дает тот же результат, что и другие альтернативы.