Se os arquivos têm as mesmas colunas na mesma ordem, a forma mais rápida e correta de juntá-los é uma linha de shell que mantém o primeiro cabeçalho e descarta os demais:
head -n 1 first.csv > merged.csv
tail -q -n +2 *.csv >> merged.csv
Todo o resto desta página existe porque essa suposição, colunas idênticas na mesma ordem, é falsa com muito mais frequência do que as pessoas imaginam, e a falha é silenciosa. Uma junção que desalinha duas colunas gera um arquivo que abre normalmente, parece normal e está errado.
Primeiro, confira se os cabeçalhos coincidem
Faça isso antes de juntar, não depois. Todos os problemas seguintes são variações deste.
for f in *.csv; do echo "$f: $(head -n 1 "$f")"; done
Leia a saída procurando quatro coisas:
- Ordem de colunas diferente.
name,email,dateem um arquivo ename,date,emailem outro. A concatenação põe datas na coluna de e-mail, e nada reclama. - Nomes diferentes para o mesmo campo.
Email,email,E-mail,Email Address. Comum quando os arquivos vieram de exportações diferentes, ou da mesma exportação em anos diferentes. - Colunas a mais ou a menos. Uma exportação posterior ganhou um campo. Agora as linhas têm larguras diferentes.
- Uma marca de ordem de bytes (BOM) no primeiro arquivo.
namenão éname. Ela é invisível em qualquer editor e quebra junções por nome de coluna.
Se todos os cabeçalhos forem idênticos byte a byte, use a versão de duas linhas acima e pare de ler. Se não forem, a concatenação é a ferramenta errada e você precisa de algo que junte por nome de coluna.
Juntando por nome de coluna
DuckDB, se os arquivos são grandes ou os cabeçalhos diferem
Um comando resolve os dois problemas, ordens de colunas diferentes e colunas ausentes, porque alinha pelo nome e não pela posição:
duckdb -c "COPY (SELECT * FROM read_csv('*.csv', union_by_name=true, filename=true)) TO 'merged.csv' (HEADER)"
union_by_name=true é a parte que faz o trabalho: as colunas são casadas pelo cabeçalho, e um arquivo sem uma coluna recebe NULL nela, em vez de uma linha deslocada. filename=true adiciona uma coluna registrando de qual arquivo cada linha veio, algo de que você vai precisar mais do que imagina. É a única forma de responder "em qual exportação essa linha ruim chegou" depois do fato.
O DuckDB processa em fluxo, então o tamanho dos arquivos é limitado pelo disco e não pela RAM. Cem arquivos somando vários gigabytes é algo corriqueiro.
Python, se você precisa harmonizar os nomes antes
Quando o mesmo campo se chama Email em um arquivo e E-mail Address em outro, nenhuma ferramenta adivinha. Normalize de forma explícita:
import glob, pandas as pd
rename = {"E-mail Address": "email", "Email": "email"}
frames = []
for path in glob.glob("*.csv"):
df = pd.read_csv(path, dtype=str)
df.columns = [c.strip().lstrip("") for c in df.columns]
frames.append(df.rename(columns=rename).assign(source=path))
pd.concat(frames, ignore_index=True).to_csv("merged.csv", index=False)
Dois detalhes importam mais do que a junção em si. dtype=str impede o pandas de inferir tipos arquivo por arquivo. Sem isso, uma coluna de ID de cliente numérica em um arquivo e alfanumérica em outro sai como uma mistura de 1024 e "01024", e os zeros à esquerda se perdem para sempre. E .assign(source=path) é a mesma coluna de origem que o DuckDB entrega de graça.
Power Query, num computador sem terminal
No Excel, Dados → Obter Dados → De uma Pasta aponta para um diretório e combina todos os arquivos dentro dele, casando pelo cabeçalho. Faz isso bem, atualiza quando chegam novos arquivos à pasta e é a resposta certa para quem vai repetir essa junção todo mês.
O limite dele é o limite do Excel: o resultado ainda precisa caber em 1.048.576 linhas para entrar numa planilha. Você pode carregar para o modelo de dados e pular a planilha por completo, algo que a maioria não sabe e que eleva bastante o teto.
A diferença entre empilhar e cruzar
"Juntar" significa duas operações diferentes, e escolher a errada desperdiça uma tarde inteira.
| Você tem | Você quer | Operação |
|---|---|---|
| Doze exportações mensais, mesmas colunas | Um arquivo, mais linhas | Append (empilhar) |
| Clientes em um arquivo, pedidos em outro | Um arquivo, mais colunas | Join (cruzar por uma chave) |
Tudo o que vimos até aqui é append. Um join precisa de uma chave em comum e de uma decisão sobre o que acontece com as linhas sem correspondência, e é aí que nascem as duplicatas, porque uma chave que não é única dos dois lados multiplica linhas em silêncio. Se o número de linhas depois de um join é maior que o de qualquer entrada, foi isso que aconteceu.
Para um join, SQL é a ferramenta honesta:
duckdb -c "COPY (SELECT c.*, o.order_total FROM read_csv('customers.csv') c LEFT JOIN read_csv('orders.csv') o USING (customer_id)) TO 'merged.csv' (HEADER)"
No celular
O cenário realista no celular é um punhado de arquivos no Mail ou no app Arquivos, três exportações regionais ou alguns meses de extratos, e nenhum notebook até segunda-feira.
Duas coisas ajudam aqui. Empilhar arquivos com cabeçalhos idênticos é copiar e colar, desde que o app consiga colar um bloco de linhas em vez de uma célula por vez. E a verificação que importa, comparar cabeçalhos, é mais rápida no celular do que em qualquer outro lugar: abra cada arquivo e leia a primeira linha.
Abre cada exportação em segundos, cola um intervalo de linhas em outra planilha e grava todas as linhas ao salvar, inclusive na versão gratuita. Com inserção de colunas e linhas, ajustar um arquivo que ganhou um campo é uma edição de verdade, não um improviso.
O que ele fazPara mais do que um punhado de arquivos, espere um computador. Juntar trinta exportações à mão não é coisa para fazer numa tela de toque, e o motivo é a taxa de erros, não o tédio.
Depois de juntar, quatro verificações
Faça sempre. Levam um minuto e pegam praticamente todas as falhas de junção que importam.
- Contagem de linhas. O total deve ser a soma das linhas de todas as entradas, menos um cabeçalho por arquivo.
wc -l *.csvantes,wc -l merged.csvdepois. - Contagem de cabeçalhos.
grep -c "^name," merged.csv, ou o nome da primeira coluna. Mais de um resultado significa que um cabeçalho virou linha de dados. - Largura das colunas. Toda linha deve ter o mesmo número de campos. Larguras desiguais indicam uma vírgula sem aspas em algum lugar, não um bug da junção.
- Confira a fronteira. Olhe a última linha do arquivo um e a primeira do arquivo dois no resultado. O desalinhamento aparece ali primeiro.
A segunda verificação tem uma sutileza: se uma linha de cabeçalho for parar no meio dos dados, a ordenação vai espalhá-la pelo arquivo e ela fica muito difícil de achar depois. Pegue isso agora.
Executa SQL direto no arquivo resultante, então um GROUP BY na coluna de origem mostra se cada arquivo contribuiu com o número de linhas esperado, e toda a verificação cabe em uma consulta.
O que ele fazCodificação, a falha que sobrevive a todas as verificações
Arquivos de sistemas diferentes costumam estar em codificações diferentes, e a concatenação não converte. Um arquivo UTF-8 anexado a um Windows-1252 produz um arquivo que não é nenhum dos dois: os nomes aparecem certos na primeira metade e como é na segunda.
Converta antes de juntar, não depois:
iconv -f WINDOWS-1252 -t UTF-8 old.csv > old-utf8.csv
file -I *.csv tenta adivinhar o que você tem, mas erra bastante em arquivos curtos. O sinal confiável é abrir cada arquivo e olhar uma linha com um caractere fora do ASCII.