Проблема в том, что механизм регулярного выражения sed не видит ни ваш входной файл, ни ваше […]
совпадение в виде списка символов Unicode; вместо этого он видит каждый из них как несколько независимых байтов. Например, он видит •
три байта \xe2 \x80 \xa2
и пытается сопоставить каждый из них по отдельности [ \xe2 \x80 \x98 \xe2 \x80 \x99 \x22 \xe2 \x80 ... ]
.
Таким образом, в примере, который вы показали в своем посте, регулярное выражение только сопоставляет и удаляет последний байт каждого символа пунктуации, но оставляет два других по-прежнему там. Вот что дает вам неверный (не UTF-8) выходной файл.
С помощью GNU sed (протестировано на 4.5) этого можно избежать, если убедиться, что языковой стандарт системы (переменные среды $ LANG или хотя бы $ LC_CTYPE) установлен в совместимый с UTF-8 языковой стандарт. Например:
$ export LANG = 'C' $ echo '' test '“test”' | sed 's / [“” •] / X / g' XX testXX XXXtestXXX $ echo '• _test' | sed 's / [• ‡] _ / X_ /' X_test $ export LANG = 'en_US.UTF-8' $ echo '' test '“test”' | sed 's / [“” •] / X / g' «тест» XtestX $ echo '• _test' | sed 's / [• ‡] _ / X_ /' X_test
(Язык локали не имеет значения. Подойдет любая локаль UTF-8.)
Если это не работает для вас, избегайте […]
полностью и используйте \(…\|…\|…\)
(или (…|…|…)
в sed -r), который является многосимвольной альтернативой и будет работать независимо от того, как эти символы будут интерпретироваться.
$ export LANG = 'C' $ echo '' test '“test”' | sed 's / \ (“\ |” \ | • \) / X / g' «тест» XtestX $ echo '• _test' | sed 's / \ (• \ | ‡ \) _ / X_ /' X_test