Почему GNU sed иногда обрабатывает замену с конвейерным выводом в другой экземпляр sed иначе, чем когда несколько выражений используются с одним и тем же?
В частности, для сеансов msys/mingw в сценарии /etc/profile у меня есть ряд манипуляций, которые «перестраивают» порядок переменной среды PATH и удаляют повторяющиеся записи.
Обратите внимание, что хотя обычно sed обрабатывает каждую строку ввода отдельно (и, следовательно, не может легко заменить '\n' во входном потоке, этот оператор sed заменяет ':' на '\n', поэтому он по-прежнему обрабатывает весь входной поток как одна строка (с символами '\n') Это поведение остается верным для всех выражений sed в одном и том же экземпляре sed (в основном до тех пор, пока вы не перенаправите или не перенаправите вывод в другую программу).
Вот обязательные характеристики:
Windows 7 Professional Service Pack 1
HP Pavilion dv7-6b78us
16 GB DDR3 RAM
MinGW-w64 (x86_64-w64-mingw32-gcc-4.7.1.2-release-win64-rubenvb) mounted on /mingw/
MSYS (20111123) mounted on / and on /usr/
$ uname -a="MINGW32_NT-6.1 CHRIV-L09 1.0.17(0.48/3/2) 2011-04-24 23:39 i686 Msys"
$ which sed="/bin/sed.exe" (it's part of MSYS)
$ sed --version="GNU sed version 4.2.1"
Это содержимое PATH перед манипуляцией:
PATH='.:/usr/local/bin:/mingw/bin:/bin:/c/PHP:/c/Program Files (x86)/HP SimplePass 2011/x64:/c/Program Files (x86)/HP SimplePass 2011:/c/Windows/system32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0:/c/si:/c/android-sdk:/c/android-sdk/tools:/c/android-sdk/platform-tools:/c/Program Files (x86)/WinMerge:/c/ntp/bin:/c/GnuWin32/bin:/c/Program Files/MySQL/MySQL Server5.5/bin:/c/Program Files (x86)/WinSCP:/c/Program Files (x86)/Overlook Fing 2.1/bin:/c/Program Files/7-zip:.:/c/Program Files/TortoiseGit/bin:/c/Program Files (x86)/Git/bin:/c/VS10/VC/bin/x86_amd64:/c/VS10/VC/bin/amd64:/c/VS10/VC/bin'
Это выдержка из /etc/profile (где я начал манипулировать PATH):
set | grep --color=never ^PATH= | sed -e "s#^PATH=##" -e "s#'##g" \
-e "s/:/\n/g" -e "s#\n\(/[^\n]*tortoisegit[^\n]*\)#\nZ95-\1#ig" \
-e "s#\n\(/[a-z]/win\)#\nZ90-\1#ig" -e "s#\n\(/[a-z]/p\)#\nZ70-\1#ig" \
-e "s#\.\n#A10-.\n#g" -e "s#\n\(/usr/local/bin\)#\nA15-\1#ig" \
-e "s#\n\(/bin\)#\nA20-\1#ig" -e "s#\n\(/mingw/bin\)#\nA25-\1#ig" \
-e "s#\n\(/[a-z]/vs10/vc/bin\)#\nA40-\1#ig"
Последнее выражение sed в этой строке в основном ищет строки, начинающиеся с «/c/VS10/VC/bin», и добавляет к ним «A40-», например:
...
/c/si
A40-/c/VS10/VC/bin
A40-/c/VS10/VC/bin/amd64
A40-/c/VS10/VC/bin/x86_amd64
/c/GnuWin32/bin
...
Мне нравится, чтобы мои выражения sed были гибкими (изменялась структура пути), но я не хочу, чтобы они совпадали со строками, заканчивающимися на amd64 или x86_amd64 (к которым будет добавлена другая строка). Поэтому я изменяю последнее выражение на:
-e "s#\n\(/[a-z]/vs10/vc/bin\)\n#\nA40-\1\n#ig"
Это работает:
...
/c/si
A40-/c/VS10/VC/bin
/c/VS10/VC/bin/amd64
/c/VS10/VC/bin/x86_amd64
/c/GnuWin32/bin
...
Затем (для соответствия любой «строке», соответствующей псевдокоду «/x/.../bin») я изменяю последнее выражение на:
-e "s#\n\(/[a-z]/.*/bin\)\n#\nA40-\1\n#ig"
Что производит:
...
/c/si
/c/VS10/VC/bin
/c/VS10/VC/bin/amd64
/c/VS10/VC/bin/x86_amd64
/c/GnuWin32/bin
...
<сильный>??? - sed не соответствует ни одному символу ('.') любое количество раз ('*') в середине строки ???
Но если я передам вывод в другой экземпляр sed (и компенсирую sed, обрабатывающий каждую «строку» отдельно), вот так:
| sed -e "s#^\(/[a-z]/.*/bin\)$#A40-\1#ig"
Я получил:
sed: -e expression #1, char 30: unterminated `s' command
<сильный>??? Как это не завершается? В нем есть все три символа "#" после s, есть модификаторы "i" и "g" после третьего "#", и все выражение заключено в двойные кавычки (""" ). Кроме того, нет экранов ('\'), непосредственно предшествующих разделителям, и разделитель не является частью ни поиска, ни замены. Давайте попробуем другой разделитель, чем '#', например '~':
Я использую: | sed -e "s~^(/[az]/.*/bin)$~A40-\1~ig"
и я получаю:
...
/c/si
A40-/c/VS10/VC/bin
/c/VS10/VC/bin/amd64
/c/VS10/VC/bin/x86_amd64
A40-/c/GnuWin32/bin
...
И это правильно! Единственное, что я изменил, это разделитель с "#" на "~", и это сработало???
Это не первый (даже близкий к тому) случай, когда sed дает необъяснимые результаты для меня.
Почему, о, почему sed НЕ соответствует синтаксису выражения в том же экземпляре, но соответствует при передаче в другой экземпляр sed? И почему, о, почему я должен использовать другой разделитель, когда я это делаю (чтобы не получить «незавершенную команду 's'»?
И настоящая причина, по которой я спрашиваю: это ошибка в sed ИЛИ правильное поведение, которого я не понимаю (и если да, то может ли кто-нибудь объяснить, почему такое поведение правильное)? Я хочу знать, делаю ли я это неправильно, или мне нужен другой/лучший инструмент (или оба, они не должны быть взаимоисключающими).
Я отмечу ответ как ответ, если кто-то сможет доказать, почему это поведение является правильным, или если он сможет доказать, почему это ошибка. Я с радостью приму любые советы о других инструментах или других методы использования sed, но они не ответят на вопрос.
Мне придется улучшить работу с другими текстовыми процессорами (такими как awk, tr и т. д.), потому что sed отнимает у меня слишком много времени из-за необъяснимых результатов.
P.S. Это не полная логика моей манипуляции с PATH. Полная логика также завершает добавление всех строк со значениями от «A00-» до «Z99-», а затем направляет вывод в «sort -u -f» и обратно в sed, чтобы удалить те же самые префиксы в каждой строке и преобразовать строки ('\n') обратно в двоеточия (':'). Затем перед единственной строкой добавляется «export PATH='», а к ней добавляется «'». Затем этот вывод перенаправляется во временный файл. Затем создается этот временный файл. И, наконец, этот временный файл удаляется.
Скрипт /etc/profile также отображает содержимое PATH до и после сортировки (на случай, если он испортил путь).
П.П.С. Я уверен, что есть гораздо лучший способ сделать это. Все началось с очень простых манипуляций с sed и превратилось в монстра, которого вы видите здесь. Даже если есть лучший способ, мне все равно нужно знать, почему sed дает мне такие результаты.