Непредсказуемое поведение интерпретаторов sed, выводимых из нескольких выражений

Почему 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 дает мне такие результаты.


person chriv    schedule 19.09.2012    source источник
comment
Итак, что вы в конечном итоге делаете, так это сортируете свой путь, указывая, какие шаблоны должны быть первыми?   -  person evil otto    schedule 19.09.2012
comment
Точно. Некоторые двоичные файлы существуют более чем в одном пути (например, git и lib), и мне нужны определенные для использования в MSYS.   -  person chriv    schedule 19.09.2012


Ответы (1)


sed -e "s#^\(/[a-z]/.*/bin\)$#A40-\1#ig"

не завершается, потому что оболочка пытается расширить "$#A". Поместите свои выражения в одинарные кавычки, чтобы избежать этого.

Выражение

-e "s#\n\(/[a-z]/.*/bin\)\n#\nA40-\1\n#ig"

терпит неудачу или не делает того, что вы ожидаете, потому что . соответствует новой строке в многострочном выражении. Проверьте весь вывод, A40- находится в самом начале. Измените его на

-e "s#\n\(/[a-z]/[^\n]*/bin\)\n#\nA40-\1\n#ig"

и это может быть больше, чем вы ожидаете. Это вполне может иметь место с большинством ваших проблем с многострочными модификациями.

Вы также можете поместить операторы, по одному на строку, в отдельный файл и вызвать sed с помощью sed -f editscript. Это может сделать обслуживание этого немного проще.

person evil otto    schedule 19.09.2012
comment
Спасибо. Меня беспокоило, что я не мог понять, почему вывод. Я совершенно забыл, что sed будет расширять переменные среды (особенно в Windows, где обычно (в cmd вместо bash) переменные среды выражаются как% Variable% вместо $Variable. Спасибо за указание, что .* может соответствовать нескольким строкам в этом случае (поскольку sed работает сразу со всей строкой). - person chriv; 19.09.2012
comment
В конце концов, все исполняемые файлы в этой логике будут заменены переменными среды, указывающими полный путь к двоичному файлу. Кроме того, все префиксы будут определены в верхней части (как $VARIABLES), где я могу изменить их значения без изменения выражений sed. Спасибо еще раз! - person chriv; 19.09.2012
comment
Не совсем $#A. Оболочка просто расширяет $#. - person William Pursell; 20.09.2012