Как я могу удалить спецификацию из файла UTF-8?

64

У меня есть файл в кодировке UTF-8 с BOM и я хочу удалить BOM. Существуют ли какие-либо инструменты командной строки Linux для удаления спецификации из файла?

$ file test.xml
test.xml:  XML 1.0 document, UTF-8 Unicode (with BOM) text, with very long lines
m13r
источник
1
Я сделал довольно простой инструмент, чтобы сделать это несколько месяцев назад: oskog97.com/read/?path=/small-scripts/killbom&referer=/… Возможно, стоит установить что-то подобное в / usr / local / bin, если у вас есть много файлов в кодировке UTF-8 с спецификациями.
Оскар Ског

Ответы:

76

Если вы не уверены, содержит ли файл спецификацию UTF-8, то это (при условии реализации GNU sed) удалит спецификацию, если она существует, или не внесет никаких изменений, если ее нет.

sed '1s/^\xEF\xBB\xBF//' < orig.txt > new.txt

Вы также можете перезаписать существующий файл с помощью -iопции:

sed -i '1s/^\xEF\xBB\xBF//' orig.txt
CSM
источник
4
это может не работать в локали utf8, но предопределение переопределения локали для c или posix всегда будет работать.
Хильдред
3
@hildred Я проверил это с языком, en_US.UTF-8и это сработало. Когда это не удастся?
m13r
2
@ m13r, зависит от версии sed и опций компиляции. В случае сбоя очень новая версия sed с классами символов Unicode принесет трехбайтовую последовательность как один символ, который не соответствует последовательности трех символов. Однако в таком случае вы можете сделать шестнадцати битное совпадение символов. Тем не менее, это новая функция, которая присутствует не всегда. Если вы хотите протестировать, я рекомендую собрать последнюю версию.
hildred
4
Чтобы исправить это для работы с sed с поддержкой Unicode, сделайте LC_ALL = C sed '1s / ^ \ xEF \ xBB \ xBF //'
Джошуа
1
@mazunki, 1s/означает поиск только по первой строке; другие линии не затрагиваются. Эти ^средства соответствуют только в начале (первой) линии. \xEF\xBB\xBFявляется спецификацией UTF-8 (экранированная шестнадцатеричная строка). //значит заменить ничем. Я мог бы добавить 1в конец (для 1s/^xEF\xBB\xBF//1), что будет означать только совпадение с первым появлением шаблона на линии. Но так как поиск привязан ^, это не будет иметь никакого значения. Если файл не имеет спецификации в начале первой строки, шаблон не будет соответствовать, и, следовательно, никаких изменений не произойдет.
CSM
64

Спецификация не имеет смысла в UTF-8. Как правило, они ошибочно добавляются фиктивным программным обеспечением на ОС Microsoft.

dos2unix удалит его, а также позаботится о других особенностях текстовых файлов Windows.

dos2unix test.xml
Стефан Шазелас
источник
17
Я согласен с тем, что спецификация в кодировке UTF-8 не имеет смысла, но, верьте или нет, есть много людей, которые считают, что это отличная идея, которая помогает отличить UTF-8 от других 8-битных кодировок. Так что это дело вкуса. Блокнот Windows специально добавляет спецификацию.
Йохан Мирен
17
Какое это имеет значение, имеет ли это смысл или нет, когда контекст - это просто вопрос о том, как его удалить? Согласно Википедии, Блокнот требует, чтобы спецификация распознала файл как UTF-8, и Документы Google также добавляют его при экспорте файла в виде текста. Я сомневаюсь, что все они делают это по ошибке .
ilkkachu
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Тердон
1
Есть ли способ не конвертировать окончания строк и просто удалить спецификацию dos2unix?
m13r
2
@ m13r Тогда воспользуйтесь сценарием sed в этом ответе . Это удалит только бомбу (если она существует), больше ничего не изменится.
Стрелка
27

Удалить спецификацию из файла можно tailкомандой:

tail -c +4 withBOM.txt > withoutBOM.txt
m13r
источник
2
Почему 4? Спецификация имеет 3 байта.
deviantfan
10
@deviantfan Вот почему вам нужно начинать с 4-го байта, если вы хотите пропустить его.
Стефан
9
tailиспользует 1 на основе индексации ?! WTF!
CodesInChaos
5
@CodesInChaos, tail -c -1или tail -c 1(для чего tailобычно используется) - это содержимое, начинающееся с последнего байта, tail -c +1начиная с первого байта. tail -c 0/ tail -c +0для этого было бы намного более неинтуитивным.
Стефан
2
@deviantfan: (dd bs=1 count=3 of=/dev/null; cat) <input >output. Или с GNU (head -c3 >/dev/null; cat)- даже в UTF8 или другой не однобайтовой локали; Глава GNU делает 'char' = байт.
dave_thompson_085
20

Использование VIM

  1. Открыть файл в VIM:

    vi text.xml
    
  2. Удалить кодировку спецификации:

    :set nobomb
    
  3. Сохранить и выйти:

    :wq
    
Джошуа Пинтер
источник
Как ни странно с vim 8 на Mac, у меня есть файл CSV UTF-8, созданный в Excel, и он начинается с <feff>, но :set nobombне изменяет и не удаляет его.
dlamblin
5

Вы можете использовать

LANG=C LC_ALL=C sed -e 's/\r$// ; 1 s/^\xef\xbb\xbf//' -i -- filename

удалить метку порядка следования байтов из начала файла, если он есть, а также преобразовать любые символы новой строки CR LF только в LF. Он LANG=C LC_ALL=Cсообщает оболочке, что вы хотите, чтобы команда выполнялась в локали C по умолчанию (также известной как локаль POSIX по умолчанию), где три байта, образующие метку порядка байтов, обрабатываются как байты. -iВариант СЭД означает на месте. Если вы используете -i.old, то sed сохраняет исходный файл как filename.old, а новый файл (с изменениями, если есть) как filename.


Мне лично нравится иметь это как ~/bin/fix-ms; например, как

#!/bin/dash
export LANG=C LC_ALL=C
if [ $# -gt 0 ]; then
    for FILE in "$@" ; do
        sed -e 's/\r$// ; 1 s/^\xef\xbb\xbf//' -i -- "$FILE" || exit 1
    done
else
    exec sed -e 's/\r$// ; 1 s/^\xef\xbb\xbf//'
fi

так что, если мне нужно применить это, чтобы сказать все исходные файлы и заголовки C (мой старый код из эры MS-DOS, например!), я просто запускаю

find . -name '*.[CHch]' -print0 | xargs -r0 ~/bin/ms-fix

или, если я просто хочу посмотреть на такой файл, не изменяя его, я могу запустить

~/bin/ms-fix < filename | less

и не вижу уродливого <U+FEFF>в моем терминале UTF-8.

Номинальное животное
источник
Почему не просто sed -e 's/\r$// ; 1 s/^\xef\xbb\xbf//' -i -- "$@"?
Стефан
@ StéphaneChazelas: потому что я хочу, чтобы скрипт немедленно завершился, если есть проблема с заменой, которая sed -e 's/\r$// ; 1 s/^\xef\xbb\xbf//' -i -- "$@"не помогает; он возвращает код завершения, но обрабатывает все файлы, перечисленные в списке аргументов, перед выходом.
Номинальное животное
@ StéphaneChazelas: --Перед именем файла (ов), конечно же, важны имена: без него имена файлов, начинающиеся с тире, могут считаться опциями sed. Я отредактировал их в своем ответе; Спасибо за напоминание!
Номинальное животное
0

Недавно я обнаружил этот крошечный инструмент командной строки, который добавляет или удаляет спецификацию для произвольных файлов в кодировке UTF-8: UTF BOM Utils ( новая ссылка на github)

Небольшой недостаток, вы можете скачать только простой исходный код C ++. Вы должны создать make-файл (например, с CMake ) и скомпилировать его самостоятельно, двоичные файлы на этой странице не предоставляются.

Вернфрид Домшайт
источник