Como testar se um arquivo usa CRLF ou LF sem modificá-lo?

48

Preciso executar periodicamente um comando que garanta que alguns arquivos de texto sejam mantidos no modo Linux. Infelizmente, dos2unixsempre modifica o arquivo, o que atrapalha os carimbos de data e hora das pastas e arquivos e causa gravações desnecessárias.

O script que escrevo está no Bash, então prefiro respostas baseadas no Bash.

Adam Ryczkowski
fonte

Respostas:

41

Você pode usar dos2unixcomo um filtro e comparar sua saída com o arquivo original:

dos2unix < myfile.txt | cmp -s - myfile.txt
Samuel Edwin Ward
fonte
2
Muito inteligente e útil, porque testa o arquivo completo e não apenas a primeira ou algumas linhas.
Halloleo 19/05/2015
2
Talvez você poderia substituir testpor myfile.txtduas vezes no seu exemplo de confusão evitar com /usr/bin/test.
Peterino 30/05
11
NB, você precisará excluir o -ssinalizador para ver a saída. De páginas do manual: -s, --quiet, --silent suppress all normal output
tobalr
24

Se o objetivo é apenas evitar afetar o registro de data e hora, dos2unixtem uma opção -kou --keepdateque manterá o registro de data e hora igual. Ainda será necessário fazer uma gravação para criar o arquivo temporário e renomeá-lo, mas seus carimbos de data e hora não serão afetados.

Se qualquer modificação do arquivo for inaceitável, você pode usar a seguinte solução nesta resposta .

find . -not -type d -exec file "{}" ";" | grep CRLF
j883376
fonte
11
Você quer dizer que você literalmente escreve CRLF como 4 caracteres C, R, L e F?
bodacydo
7
Você também quer dizer que o grep pode receber CR e LF assim?
bodacydo
@bodacydo Isso está explicado na resposta a que ele vincula, e agora também na edição de Scott da resposta do BertS aqui, unix.stackexchange.com/a/79708/59699 .
David_thompson_085
@ dave_thompson_085 Não vejo explicação. Ele apenas menciona o CRLF, mas não explica o que é.
bodacydo
11
@bodacydo stackoverflow.com/questions/73833/... diz que find ... -exec file ... | grep CRLFpara um arquivo com terminações de linha DOS (ou seja bytes 0D 0A) "vai te algo como: ./1/dos1.txt: ASCII text, with CRLF line terminators Como você pode ver este contém o CRLF seqüência real e, portanto, é compensada pela grepprocura a cadeia de caracteres simples CRLF.
dave_thompson_085
22

Você pode tentar o grepcódigo CRLF, octal:

grep -U $'\015' myfile.txt

ou hex:

grep -U $'\x0D' myfile.txt
don_crissti
fonte
Obviamente, a suposição é que este é um arquivo de texto.
Mdpc
2
Eu gosto desse grepuso porque me permite listar facilmente todos esses arquivos no diretório grep -lU $'\x0D' *e passar a saída para xargs.
Melebius
qual é o significado do $ antes do padrão de pesquisa? @don_crissti
fersarr
11
@fersarr - unix.stackexchange.com/a/401451/22142
don_crissti
21

Como a versão 7.1dos2unix possui uma opção -i, --infopara obter informações sobre quebras de linha. Você pode usar o próprio dos2unix para testar quais arquivos precisam de conversão.

Exemplo:

dos2unix -ic *.txt | xargs dos2unix
Erwin Waterlander
fonte
Aqui está o link para o próprio changelog waterlan.home.xs4all.nl/dos2unix/NEWS.txt
Adam Ryczkowski 23/15
13

Primeiro método ( grep):

Conte as linhas que contêm um retorno de carro:

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

Conte as linhas que terminam com um retorno de carro:

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

Estes serão tipicamente equivalentes; um retorno de carro no interior de uma linha (ou seja, não no final) é raro.

Mais eficiente:

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

Isso é mais eficiente

  1. porque ele não precisa converter a contagem em uma sequência ASCII e, em seguida, converter essa sequência novamente em um número inteiro e compará-la com zero e
  2. porque grep -cprecisa ler o arquivo inteiro, contar todas as ocorrências do padrão, enquanto grep -qpode sair ao ver a primeira ocorrência do padrão.

Notas:

  • Nas -Uopções acima, você pode precisar adicionar a opção (por exemplo, use -cUor -qU), porque o GNU grepadivinha se o arquivo é um arquivo de texto. Se considerar que o arquivo é texto, ele ignora os retornos de carro no final das linhas, na tentativa de fazer $com que as expressões regulares funcionem "corretamente" - mesmo que a expressão regular seja \r$! A especificação -U(ou --binary) anula essas suposições, fazendo grepcom que os arquivos sejam tratados como binários e passem os dados para o mecanismo de correspondência literalmente, com as terminações de CR intactas.
  • Não faça grep … $'\r\n' myfile.txt, porque greptrata \ncomo um delimitador de padrão. Assim como grep -E 'foo|'procura linhas contendo fooou uma cadeia nula, grep $'\r\n'procura linhas contendo \rou uma cadeia nula, e cada linha corresponde a uma cadeia nula.

Segundo método ( file):

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

porque filerelata algo como:

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

Variante mais segura:

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

Onde

Lembre-se de que a verificação da saída file pode não funcionar em um código de idioma diferente do inglês.

BertS
fonte
11
Você pode substituir "$(echo -e '\r')"pelo muito mais simples $'\r', embora pessoalmente eu usaria $'\r\n'para reduzir o número de falsos positivos.
rici 17/06
@rici grep $'\r\n'parece corresponder a todos os arquivos no meu sistema ...
depquid
@rici: boa captura. Eu editei minha resposta de acordo com sua sugestão. - depquid: talvez você esteja no Windows? :-) A dica de rici funciona aqui.
berts
@depquid (e BertS): Na verdade, acho que a invocação correta é grep -U $'\r$', para evitar greptentar adivinhar o final da linha.
rici
Além disso, você pode -qdefinir apenas o código de retorno se uma correspondência for encontrada, em vez de -cexigir uma verificação adicional. Pessoalmente, gosto da sua segunda solução, embora seja altamente dependente dos caprichos filee possa não funcionar em um local diferente do inglês.
rici
11

Usar cat -A

$ cat file
hello
hello

Agora, se esse arquivo foi criado em sistemas * NIX, ele exibirá

$ cat -A file
hello$
hello$

Mas se esse arquivo foi criado no Windows, ele exibirá

$ cat -A file
hello^M$
hello

^Mrepresenta CRe $representa LF. Observe que o Windows não salvou a última linha comCRLF

Isso também não altera o conteúdo do arquivo.

CiganoCosmonauta
fonte
A melhor e mais simples solução! precisa de mais votos.
user648026
11
+1 De longe, a melhor resposta. Sem dependências, sem scripts bash complicados. Apenas -Apara gato. Uma dica seria usar cat -A file | lessse o arquivo for muito grande. Tenho certeza de que não é incomum ter que verificar as terminações de um arquivo particularmente longo. (Pressione qpara deixar menos)
Nicholas Pipitone
4

uma função bash para você:

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

Então você pode fazer coisas como

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

streamFile /tmp/foo.txt | process_lines_without_CR
Glenn Jackman
fonte
3
Você não tem que usar isDosFile()no seu exemplo: streamFile() { sed 's/\r$//' "$1" ; }.
11
Eu acho que essa é a solução mais elegante; ele não lê o arquivo inteiro, apenas a primeira linha.
Adam Ryczkowski
4

Se um arquivo tiver terminações de linha CR-LF no estilo DOS / Windows, se você o usar usando uma ferramenta baseada em Unix, verá caracteres CR ('\ r') no final de cada linha.

Este comando:

grep -l '^M$' filename

será impresso filenamese o arquivo contiver uma ou mais linhas com finais de linha no estilo Windows e não imprimirá nada se não contiver. Exceto que ^Mele deve ser um caractere literal de retorno de carro, geralmente inserido no terminal digitando Ctrl+ Vseguido de Enter (ou Ctrl+ Ve depois Ctrl+ M). O shell bash permite escrever um retorno de carro literal como $'\r'( documentado aqui ), para que você possa escrever:

grep -l $'\r$' filename

Outras conchas podem fornecer um recurso semelhante.

Você pode usar outra ferramenta:

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

Isso sairá com um status de 1(configurando $?como 1) se o arquivo contiver alguma final de linha no estilo do Windows e com um status de 0se não existir, tornando-o útil em uma ifdeclaração de shell (observe a falta de [colchetes ]):

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

Um arquivo pode conter uma mistura de terminações de linha no estilo Unix e no estilo Windows. Estou assumindo aqui que você deseja detectar arquivos que têm quaisquer fins de linha de estilo do Windows.

Keith Thompson
fonte
11
Você pode codificar um retorno de carro na linha de comando no bash (e em alguns outros shells) digitando $'\r', conforme mencionado em outras respostas a esta pergunta.
Scott
2

Use 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
Dan Sorak
fonte
Essa idéia foi discutida com mais detalhes em duas respostas anteriores.
G-Man diz 'Reinstate Monica'
1

Eu tenho usado

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

o que parece funcionar. Acho a saída um pouco mais fácil de ler do que

dos2unix < filename.txt | diff - filename.txt

Também é útil se você não puder instalar dos2unixpor algum motivo.

Alex028502
fonte