Как определить, имеет ли файл спецификацию UTF-8 в Bash?

Я пытаюсь написать script, который автоматически удалит спецификации UTF-8 из файла. У меня возникли проблемы с обнаружением того, есть ли файл в первую очередь или нет. Вот мой код:

function has-bom {
    # Test if the file starts with 0xEF, 0xBB, and 0xBF
    head -c 3 "$1" | grep -P '\xef\xbb\xbf'
    return $?
}

По какой-то причине head, кажется, игнорирует спецификацию перед файлом. В качестве примера выполните этот

printf '\xef\xbb\xbf' > file
head -c 3 file

ничего не печатает.

Я попытался найти вариант в head --help, который позволил бы мне обойти это, но не повезло. Есть ли что-нибудь, что я могу сделать, чтобы сделать эту работу?

Ответы

Ответ 1

Сначала продемонстрируем, что head работает корректно:

$ printf '\xef\xbb\xbf' >file
$ head -c 3 file 
$ head -c 3 file | hexdump -C
00000000  ef bb bf                                          |...|
00000003

Теперь создайте рабочую функцию has_bom. Если ваш grep поддерживает -P, то один из вариантов:

$ has_bom() { head -c3 "$1" | LC_ALL=C grep -qP '\xef\xbb\xbf'; }
$ has_bom file && echo yes
yes

В настоящее время только GNU grep поддерживает -P.

Другой вариант - использовать bash $'...':

$ has_bom() { head -c3 "$1" | grep -q $'\xef\xbb\xbf'; }
$ has_bom file && echo yes
yes

ksh и zsh также поддерживают $'...', но эта конструкция не POSIX, а dash не поддерживает ее.

Примечания:

  • Использование явного return $? является необязательным. Функция по умолчанию вернется с кодом завершения последнего запуска команды.

  • Я использовал форму POSIX для определения функций. Это эквивалентно форме bash, но дает вам еще одну проблему, с которой вам придется столкнуться, если вам когда-либо понадобится запустить функцию под другой оболочкой.

  • bash принимает использование символа - в имени функции, но это противоречивая функция. Я заменил его на _, который более широко принят. (Подробнее об этой проблеме см. этот ответ.)

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

Ответ 2

Я применил следующие строки для первой прочитанной строки:

read c
if (( "$(printf "%d" "'${c:0:1}")" == 65279 ))  ; then c="${c:1}" ; fi

Это просто удаляет спецификацию из переменной.