Как проверить, использует ли файл CRLF или LF, не изменяя его?

48

Мне нужно периодически запускать команду, которая гарантирует, что некоторые текстовые файлы хранятся в режиме Linux. К сожалению, dos2unixвсегда изменяет файл, который может испортить временные метки файла и папки и вызвать ненужные записи.

Сценарий, который я пишу, написан на Bash, поэтому я бы предпочел ответы, основанные на Bash.

Адам Рычковски
источник

Ответы:

41

Вы можете использовать dos2unixв качестве фильтра и сравнить его вывод с исходным файлом:

dos2unix < myfile.txt | cmp -s - myfile.txt
Сэмюэл Эдвин Уорд
источник
2
Очень умный и полезный, потому что он тестирует весь файл, а не только первую или несколько строк.
Halloleo
2
Может быть , вы могли бы заменить testна myfile.txtдва раза в вашем примере , чтобы избежать путаницы с /usr/bin/test.
Петерино
1
NB вам нужно будет удалить -sфлаг, чтобы увидеть вывод. С man-страниц: -s, --quiet, --silent suppress all normal output
Tobalr
24

Если цель состоит в том, чтобы просто не влиять на временную метку, dos2unixесть опция -kили, --keepdateкоторая будет сохранять временную метку на том же уровне. Он все равно должен будет сделать запись, чтобы создать временный файл и переименовать его, но ваши временные метки не будут затронуты.

Если любое изменение файла неприемлемо, вы можете использовать следующее решение из этого ответа .

find . -not -type d -exec file "{}" ";" | grep CRLF
j883376
источник
1
Вы имеете в виду, что вы буквально пишете CRLF как 4 символа C, R, L и F?
bodacydo
7
Вы также имеете в виду, что grep может принимать CR и LF просто так?
bodacydo
@bodacydo Это объясняется в ответе, на который он ссылается, а теперь также в редактировании Скоттом ответа BertS здесь unix.stackexchange.com/a/79708/59699 .
dave_thompson_085
@ dave_thompson_085 Я не вижу объяснения. Это только упоминает CRLF, но не объясняет, что это такое.
bodacydo
1
@bodacydo stackoverflow.com/questions/73833/… говорит, что find ... -exec file ... | grep CRLFдля файла с окончанием строки DOS (т. е. байты 0D 0A) "вы получите что-то вроде: ./1/dos1.txt: ASCII text, with CRLF line terminators Как вы можете видеть, он содержит фактическую строку CRLF и, следовательно, соответствует grepпоиску простая строка CRLF
dave_thompson_085
22

Вы можете попробовать grepдля кода CRLF, восьмеричное:

grep -U $'\015' myfile.txt

или гекс:

grep -U $'\x0D' myfile.txt
don_crissti
источник
Конечно, предполагается, что это текстовый файл.
mdpc
2
Мне нравится это grepиспользование, потому что оно позволяет мне легко перечислить все такие файлы в каталоге grep -lU $'\x0D' *и передать результат xargs.
Мелебиус
что означает $ перед шаблоном поиска? @don_crissti
fersarr
1
@fersarr - unix.stackexchange.com/a/401451/22142
don_crissti
21

Начиная с версией 7.1dos2unix имеет -i, --infoвозможность получить информацию о разрывах строк. Вы можете использовать сам dos2unix для проверки того, какие файлы нуждаются в преобразовании.

Пример:

dos2unix -ic *.txt | xargs dos2unix
Эрвин Уотерлендер
источник
Вот ссылка на сам журнал
Адам
13

Первый метод ( grep):

Подсчитайте строки, содержащие возврат каретки:

[[ $(grep -c $'\r' myfile.txt) -gt 0 ]] && echo dos

Подсчитайте строки, которые заканчиваются возвратом каретки:

[[ $(grep -c $'\r$' myfile.txt) -gt 0 ]] && echo dos

Как правило, они будут эквивалентны; возврат каретки во внутренней части строки (т.е. не в конце) встречается редко.

Более эффективным:

grep -q $'\r' myfile.txt && echo dos

Это более эффективно

  1. потому что ему не нужно преобразовывать счет в строку ASCII, а затем преобразовывать эту строку обратно в целое число, сравнивать его с нулем и
  2. потому что grep -cнеобходимо прочитать весь файл, чтобы подсчитать все вхождения шаблона, в то время как grep -qможет выйти, увидев первое вхождение шаблона.

Примечания:

  • На протяжении всего вышеизложенного вам может потребоваться добавить -Uпараметр (например, использовать -cUили -qU), потому что GNU grepопределяет, является ли файл текстовым файлом. Если он думает, что файл является текстом, он игнорирует возврат каретки на концах строк, пытаясь заставить $регулярные выражения работать «правильно» - даже если регулярное выражение есть \r$! Указание -U(или --binary) отменяет это предположение, заставляя grepобрабатывать файл (ы) как двоичные и дословно передавать данные в механизм сопоставления с неизменными CR-окончаниями.
  • Не делай grep … $'\r\n' myfile.txt, потому что grepтрактует \nкак шаблон разделителя. Так же, как grep -E 'foo|'ищет строки, содержащие fooили нулевую строку, grep $'\r\n'ищет строки, содержащие \rили нулевую строку, и каждая строка соответствует пустой строке.

Второй метод ( file):

[[ $(file myfile.txt) =~ CRLF ]] && echo dos

потому что fileсообщает что-то вроде:

myfile.txt: UTF-8 Unicode text, with CRLF line terminators

Более безопасный вариант:

[[ $(file -b - < myfile.txt) =~ CRLF ]] && echo dos

где

Помните, что проверка выходных данных file может не работать в неанглийской локали.

Berts
источник
1
Можно заменить "$(echo -e '\r')"на гораздо более простой $'\r', хотя лично я бы использовал, $'\r\n'чтобы уменьшить количество ложных срабатываний.
Ричи
@rici, grep $'\r\n'кажется, соответствует всем файлам в моей системе ...
depquid
@rici: хороший улов. Я отредактировал свой ответ согласно вашему предложению. - depquid: Может быть, вы на Windows? :-) Подсказка Ричи работает здесь.
BertS
@depquid (и BertS): На самом деле, я думаю, что правильный вызов заключается в том grep -U $'\r$', чтобы не grepпытаться угадать окончания строк.
Ричи
Также вы можете -qпросто установить код возврата, если найдено совпадение, вместо -cкоторого требуется дополнительная проверка. Лично мне нравится ваше второе решение, хотя оно сильно зависит от прихотей fileи может не работать в неанглийском языке.
Ричи
11

использование cat -A

$ cat file
hello
hello

Теперь, если этот файл был создан в системах * NIX, он будет отображать

$ cat -A file
hello$
hello$

Но если этот файл был сделан в Windows, он будет отображать

$ cat -A file
hello^M$
hello

^Mпредставляет CRи $представляет LF. Обратите внимание, что Windows не сохранила последнюю строку сCRLF

Это также не меняет содержимое файла.

GypsyCosmonaut
источник
Самое лучшее и простое решение! нужно больше голосов до.
user648026
1
+1 Безусловно лучший ответ. Никаких зависимостей, никаких сложных скриптов bash. Просто -Aчтобы кот. Один совет, однако, будет использовать, cat -A file | lessесли файл слишком большой. Я уверен, что нередко приходится проверять окончание файла для особенно длинного файла. (Нажмите, qчтобы оставить меньше)
Николас Пипитоне
4

функция bash для вас:

# return 0 (true) if first line ends in CR
isDosFile() {
    [[ $(head -1 "$1") == *$'\r' ]]  
}

Тогда вы можете делать такие вещи, как

streamFile () {
    if isDosFile /tmp/foo.txt; then
        sed 's/\r$//' "$1"
    else
        cat "$1"
    fi
}

streamFile /tmp/foo.txt | process_lines_without_CR
Гленн Джекман
источник
3
Вы не должны использовать isDosFile()в вашем примере: streamFile() { sed 's/\r$//' "$1" ; }.
1
Я думаю, что это самое элегантное решение; он не читает весь файл, только первую строку.
Адам Рычковски
4

Если файл имеет окончания строки CR-LF в стиле DOS / Windows, то если вы посмотрите на него с помощью инструмента на основе Unix, вы увидите символы CR ('\ r') в конце каждой строки.

Эта команда:

grep -l '^M$' filename

будет печатать, filenameесли файл содержит одну или несколько строк с окончаниями строк в стиле Windows, и ничего не печатать, если это не так. За исключением того, что это ^Mдолжен быть буквальный символ возврата каретки, обычно вводимый в терминале, набирая Ctrl+ с Vпоследующим Enter (или Ctrl+ Vи затем Ctrl+ M). Оболочка bash позволяет вам записать буквальный возврат каретки как $'\r'( документировано здесь ), так что вы можете написать:

grep -l $'\r$' filename

Другие оболочки могут предоставлять аналогичную функцию.

Вместо этого вы можете использовать другой инструмент:

awk '/\r$/ { exit(1) }' filename

Это завершится со статусом 1(установка $?на 1), если файл содержит какие-либо окончания строк в стиле Windows, и со статусом, 0если это не так, что делает его полезным в операторе оболочки if(обратите внимание на отсутствие [скобок ]):

if awk '/\r$/ { exit(1) }' filename ; then
    echo filename has Unix-style line endings
else
    echo filename has at least one Windows-style line ending
fi

Файл может содержать сочетание концов строк в стиле Unix и Windows. Я предполагаю , что здесь , что вы хотите , чтобы обнаружить файлы , которые имеют какие - либо окончания строк для Windows стиля.

Кит Томпсон
источник
1
Вы можете закодировать возврат каретки в командной строке в bash (и некоторых других оболочках), набрав $'\r', как упоминалось в других ответах на этот вопрос.
Скотт
2

Используйте file:

$ file README.md
README.md: ASCII text, with CRLF line terminators

$ dos2unix README.md
dos2unix: converting file README.md to Unix format...

$ file README.md
README.md: ASCII text
Дэн Сорак
источник
Эта идея обсуждалась гораздо более подробно в двух предыдущих ответах.
G-Man говорит: «Восстановите Монику»
1

Я использую

cat -v filename.txt | diff - filename.txt

который, кажется, работает. Я нахожу вывод немного легче, чем читать

dos2unix < filename.txt | diff - filename.txt

Это также полезно, если вы не можете установить dos2unixпо какой-либо причине.

Alex028502
источник