1

Что-то жужжит мне об этом. Я пытаюсь загрузить два разных файла хоста в один, если я делаю это серьезно, тогда все в порядке, но когда я добавляю первый ко второму, в каждой строке файла хоста появляется странный символ ^M

Чтобы привести реальный пример здесь, что я делаю

wget https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -O /etc/hosts && curl -s "https://raw.githubusercontent.com/CHEF-KOCH/CKs-FilterList/master/HOSTS/CK's-Spotify-HOSTS-FilterList.txt" >> /etc/hosts

теперь в /etc/hosts есть такие:

но когда я делаю это отдельно, так

curl -s "https://raw.githubusercontent.com/CHEF-KOCH/CKs-FilterList/master/HOSTS/CK's-Spotify-HOSTS-FilterList.txt" > /tmp/hosts

сейчас /tmp/hosts совершенно нормально

Почему это происходит? Почему, когда я загружаю файлы отдельно, я не получаю неправильный перевод строки, но когда я объединяю их, я получаю это. Это должно быть 0x0a, а не 0x0a0x0d, почему это происходит?

Если вам нужно взглянуть на загружаемые файлы, вы можете перейти по ссылкам в командах:

  1. https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
  2. https://raw.githubusercontent.com/CHEF-KOCH/CKs-FilterList/master/HOSTS/CK%27s-Spotify-HOSTS-FilterList.txt

РЕДАКТИРОВАТЬ: я попытался добавить только второй файл хоста в файл немой хостов, и то же самое произошло, поэтому мы можем не указывать, что первый файл является причиной проблемы

1 ответ1

3

Ни один инструмент не добавляет ничего. Это довольно путаница (но не твоя вина) по нескольким причинам.

Есть два общих окончания строки:

  • Unix-стиль, один символ обозначается LF (или \n или 0x0a),
  • Стиль Windows, два символа, CRLF (или \r\n или 0x0d 0x0a).

Вы скачиваете с двух разных URL. Кажется, сервер утверждает, что каждый файл является text/plain , поэтому они должны использовать CRLF. Второй (тот, который вы curl) действительно использует CRLF , но первый (тот, который вы wget) незаконно использует вместо одного LF .

Если вы загрузите только с первого URL (независимо от того, используете ли вы wget или curl) и сохраните результат в файле hosts1 , то file hosts1 выдаст:

hosts1: UTF-8 Unicode text

(Это означает, что окончания строки LF , в противном случае это будет UTF-8 Unicode text, with CRLF line terminators).

Если вы загружаете только со второго URL и сохраняете результат в файле hosts2 , то file hosts2 выдаст:

hosts2: ASCII text, with CRLF line terminators

Если вы загрузите оба файла в один и тот же файл (скажем, hosts12) таким же образом, вы получите LF качестве окончаний строк для строк, которые пришли из первого URL, и CRLF качестве окончаний строк для строк, которые пришли из второго URL.

На практике любой инструмент, который пытается определить, использует ли файл LF или CRLF проверяет не более нескольких начальных строк, но не все из них. Попробуйте file hosts12 и вы получите:

hosts12: UTF-8 Unicode text

Точно так же, как это было для hosts1 . То же самое происходит, когда вы используете vim hosts12: редактор определяет окончания строки как LF на основе начала файла. Затем вы переходите к концу и видите много ^M символов, которые обозначают символы CR . vim печатает их, потому что не считает CR частью правильной строки, заканчивающейся в этом случае.

Однако, когда вы используете vim hosts2 , редактор правильно определяет окончания строк как CRLF . Те же символы CR которые были напечатаны как ^M ранее, теперь скрыты от вас, потому что vim считает их частью правильных окончаний строк. Если вы добавите новую строку вручную, vim будет использовать конец строки в стиле Windows, даже если вы работаете в Unix. Вы можете подумать, что файл "совершенно нормальный", но это не обычный текстовый файл Unix.

Путаница заключается в том, что два файла на сервере используют разные окончания строк; тогда vim пытается быть умным

В Linux (Unix в целом) вы хотите, чтобы ваши /etc/hosts использовали LF качестве окончания строки. См. Определения строки и символа новой строки в POSIX. Явно указано, что символ \n:

3.243 Символ новой строки (<newline> новая строка > )
Символ, который в выходном потоке указывает, что печать должна начинаться в начале следующей строки. Это символ, обозначенный '\n' на языке Си.

Я не думаю, что инструменты обязаны поддерживать \r\n тогда. Простое решение - запустить wget … && curl … >> … точно так же, как вы это сделали, затем вызвать dos2unix /etc/hosts .

На вашем месте я бы работал с другим файлом, скажем /etc/hosts.tmp . Я бы использовал wget , curl , dos2unix , chmod --reference=/etc/hosts , chown --reference=/etc/hosts . Только когда файл будет mv , я бы заменил его на /etc/hosts . Эта функция rename(2) имеет отношение:

Если newpath уже существует, он будет атомарно заменен, так что не будет точки, в которой другой процесс, пытающийся получить доступ к newpath , обнаружит, что он отсутствует.

Таким образом, любой процесс найдет либо старый /etc/hosts (до mv), либо новый (после mv). Ваш текущий подход, напрямую работающий с /etc/hosts допускает сценарии, когда другой процесс находит файл неполным или с неправильным окончанием строки рядом с его концом.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .