[Из песочницы] Обходим ошибки утилит из пакета GNU Core Utilities
Пакет coreutils предустановлен во многих дистрибутивах Linux. Он содержит в себе стандартные и такие привычные утилиты, как cat, chmod, date, echo, ls и многие другие. Но даже в таком каноническом пакете встречаются ошибки, которые могут помешать работе пользователя. С одной из них я столкнулся на собственном опыте и хочу рассказать о том, как смог её обойти.Задача была следующая — преобразовать текстовый файл с длинными строками так, чтобы ни одна строка не была длиннее 80 символов. Длинные строки должны разбиваться на несколько строк по 80 или менее символов. Файл закодирован в UTF-8. Немного погуглив можно узнать, что в Unix-подобных ОС с этой задачей справляется утилита fold. Отлично, значит, будем её использовать. Для начала выполним в терминале пару тестовых команд, чтобы научиться управляться с ней. Я приведу ниже вывод команд, выполненных в системе Debian 7.5 с пакетом coreutils 8.13. Такой же вывод будет и в системе Arch Linux с coreutils 8.22.
При выполнении всех тестовых команд настройки локали следующие:
$ locale LANG=ru_RU.UTF-8 LC_CTYPE=«ru_RU.UTF-8» LC_NUMERIC=«ru_RU.UTF-8» LC_TIME=«ru_RU.UTF-8» LC_COLLATE=«ru_RU.UTF-8» LC_MONETARY=«ru_RU.UTF-8» LC_MESSAGES=«ru_RU.UTF-8» LC_PAPER=«ru_RU.UTF-8» LC_NAME=«ru_RU.UTF-8» LC_ADDRESS=«ru_RU.UTF-8» LC_TELEPHONE=«ru_RU.UTF-8» LC_MEASUREMENT=«ru_RU.UTF-8» LC_IDENTIFICATION=«ru_RU.UTF-8» LC_ALL=ru_RU.UTF-8 Если у вас не так, то выполните: $ export LC_ALL=«ru_RU.UTF-8» Пусть тестовая команда разобьёт строку «abcdefghij» на строки по 4 символа: $ echo «abcdefghij» | fold -w 4 abcd efgh ij Здорово! Теперь строку «абвгдеёжзи»: $ echo «абвгдеёжзи» | fold -w 4 аб вг де ёж зи И тут-то нас ждет сюрприз. Видим, что строка «абвгдеёжзи» разбилась на строки по два символа. Дело тут в том, что кириллический символ в кодировке UTF-8 занимает два байта, а символ латиницы один. Утилита fold, считая все символы однобайтовыми, просто разбила данную строку (массив байт) на куски по 4 байта. Как видно, такой алгоритм разбиения верен в кодировке UTF-8 только для латинских символов. В то же время утилита wc верно подсчитает количество символов в строке «абвгдеёжзи»: $ echo -n «абвгдеёжзи» | wc -m 10 Это говорит о том, что поддержка юникода в пакете coreutils реализована частично, и результат работы с юникодом различных утилит может быть непредсказуемым.На самом деле, об этой ошибке было известно несколько лет назад. Она описана тут и тут, и даже дан ответ от разработчиков, но, к сожалению, она по-прежнему находится в состоянии «это не баг, это фича».
Описанное выше не относится к BSD системам, у них собственная реализация стандартных утилит. Тест в системе FreeBSD 10 показал, что там с юникодом всё в порядке.
Теперь поговорим о том, как обойти эту ошибку. Мне известны две замены coreutils: BusyBox и Heirloom. Первый вариант мне показался более актуальным и простым, поэтому покажу как с его помощью соорудить костыль, который позволит нормально пользоваться утилитой fold в вашей системе. Аналогичным образом можно соорудить костыль и для любой другой стандартной утилиты.
Для начала установим пакет busybox. В системе Debian команда:
# apt-get install busybox В системе Arch Linux, соответственно, такая команда: # pacman -S busybox Согласно документации, использовать BusyBox можно так: $ busybox ls -l $ busybox ps $ busybox seq 1 5 Т.е. просто передавать имя утилиты как параметр исполняемому файлу busybox. Можно также переименовать исполняемый файл в одну из поддерживаемых им команд, и он будет автоматически действовать так, как будто это и есть эта команда. Переименовывать мы его не будем, но вот символьную ссылку с именем fold на него создадим: # cd $(dirname $(which fold)) # mv fold fold.orig # ln -s $(which busybox) fold После этого fold можно использовать самым привычным образом: вызывать из терминала или скрипта. Такая заплатка в системе является для меня приемлемой. Буду рад, если кому-то она тоже сможет помочь. А пока остаётся надеяться, что когда-нибудь coreutils будет полностью поддерживать юникод.