Como comparar dois textos (diff) no terminal e em código

Um diff mostra o que mudou entre dois textos: linhas que saíram, que entraram e que ficaram iguais. Este guia mostra como comparar arquivos pelo terminal, como ler o formato unificado usado pelo git, como gerar diferenças em JavaScript e Python, e por que quebras de linha do Windows fazem tudo parecer diferente.

Por Leonardo Gaertner · Publicado em

Pelo terminal

  • diff -u antigo.txt novo.txt: o comando clássico do Linux e do macOS, no formato unificado.
  • git diff --no-index antigo.txt novo.txt: usa o diff do git em quaisquer dois arquivos, mesmo fora de um repositório, e funciona também no Windows com o Git instalado.
  • git diff --word-diff: mostra as palavras que mudaram dentro da linha, bom para textos longos.
  • git diff -w: ignora diferenças só de espaços.

Para comparar sem instalar nada, cole os dois textos no diff de texto, que compara por linha, palavra ou caractere no navegador.

Como ler o formato unificado

  • --- a/arquivo e +++ b/arquivo: o texto antigo e o novo.
  • @@ -3,4 +3,5 @@: o trecho começa na linha 3 do antigo (4 linhas) e na linha 3 do novo (5 linhas).
  • Linhas com - saíram, com + entraram, e com espaço são contexto, iguais nos dois. O padrão mostra 3 linhas de contexto.
  • \ No newline at end of file: uma das versões não termina com quebra de linha.

Uma linha alterada aparece como uma remoção seguida de uma adição. O diff trabalha com linhas inteiras, a não ser que você peça comparação por palavra.

Como o diff encontra as mudanças

O diff procura a maior sequência de linhas que aparece, na mesma ordem, nos dois textos (a maior subsequência comum). Tudo que fica fora dela foi removido ou adicionado. O git usa por padrão o algoritmo de Myers, que chega ao mesmo tamanho mínimo de mudanças com menos memória.

A versão abaixo monta a tabela completa, o que usa memória proporcional às linhas de um texto vezes as do outro. Para arquivos com poucos milhares de linhas, basta. Para mais que isso, use o pacote diff do npm ou o próprio git.

JavaScript
// Diferença linha a linha pela maior subsequência comum (LCS).
// Exata e curta; para arquivos com milhares de linhas, use o pacote diff ou o git.
function diffLinhas(antes, depois) {
  // \r\n (Windows) e \n (Linux) viram a mesma coisa, senão toda linha parece alterada
  const a = antes.replace(/\r\n?/g, '\n').split('\n');
  const b = depois.replace(/\r\n?/g, '\n').split('\n');
  // lcs[i][j]: quantas linhas em comum há entre a[i..] e b[j..]
  const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
  for (let i = a.length - 1; i >= 0; i--) {
    for (let j = b.length - 1; j >= 0; j--) {
      lcs[i][j] = a[i] === b[j]
        ? lcs[i + 1][j + 1] + 1
        : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
    }
  }
  const saida = [];
  let i = 0;
  let j = 0;
  while (i < a.length || j < b.length) {
    if (i < a.length && j < b.length && a[i] === b[j]) {
      saida.push(`  ${a[i++]}`);
      j++;
    } else if (i < a.length && (j === b.length || lcs[i + 1][j] >= lcs[i][j + 1])) {
      saida.push(`- ${a[i++]}`);
    } else {
      saida.push(`+ ${b[j++]}`);
    }
  }
  return saida;
}

Devolve cada linha com " " (igual), "- " (saiu) ou "+ " (entrou).

Em Python, com difflib

O módulo difflib já vem com o Python. O unified_diff gera o mesmo formato do git, que o comando patch aplica, e o SequenceMatcher dá uma nota de semelhança entre 0 e 1.

Python
import difflib


def diff_unificado(antes: str, depois: str, nome: str = "arquivo") -> str:
    """O mesmo formato do git diff e do comando patch."""
    linhas = difflib.unified_diff(
        antes.splitlines(),
        depois.splitlines(),
        fromfile=f"a/{nome}",
        tofile=f"b/{nome}",
        lineterm="",
    )
    return "\n".join(linhas)


def semelhanca(antes: str, depois: str) -> float:
    # De 0 a 1 (1 = iguais). Serve para achar textos parecidos, não para gerar patch
    return difflib.SequenceMatcher(None, antes, depois).ratio()

semelhanca("casa", "cama") dá 0,75.

Cuidados

  • Quebras de linha: um arquivo salvo no Windows usa \r\n e um do Linux usa \n. Sem normalizar, todas as linhas aparecem como diferentes. O exemplo em JavaScript normaliza antes de comparar.
  • Espaços no fim da linha e tabulação trocada por espaços também contam como mudança. Use a opção de ignorar espaços quando só o conteúdo importa.
  • Para comparar JSON, compare os dados e não o texto: a ordem das chaves muda o texto sem mudar o conteúdo. Veja como comparar dois JSON.
  • Arquivos binários, como imagens e PDFs, não têm linhas: compare pelo hash, como no guia de SHA-256 de arquivo.

Achou um erro neste guia? Avise por e-mail.

Perguntas frequentes

Como comparar dois arquivos fora de um repositório git?

Use git diff --no-index arquivo1 arquivo2. Ele funciona com quaisquer dois arquivos, sem precisar de repositório, e mostra o resultado no formato unificado.

Por que o diff mostra todas as linhas como diferentes?

Quase sempre é a quebra de linha: um arquivo usa \r\n (Windows) e o outro \n (Linux e macOS). Converta os dois para o mesmo formato antes de comparar.

O que significa @@ no diff?

É o cabeçalho de cada trecho. @@ -3,4 +3,5 @@ quer dizer que o trecho começa na linha 3 do arquivo antigo, com 4 linhas, e na linha 3 do novo, com 5 linhas.

Como comparar textos em Python?

Use o módulo difflib, que vem com o Python. O unified_diff gera diferenças no formato do git, e o SequenceMatcher mede quanto dois textos se parecem.