Módulo os em Python: environ, walk, scandir e automação
Guia prático do módulo os em Python: variáveis de ambiente, os.walk, scandir, stat, rename, replace e automação de arquivos com exemplos seguros.
O módulo os é a ponte da biblioteca padrão entre o Python e o sistema operacional. Ele permite ler variáveis de ambiente, percorrer árvores de diretórios com os.walk, listar entradas eficientemente com os.scandir, consultar metadados com os.stat, criar e renomear pastas e executar outras tarefas comuns em scripts, CLIs, pipelines de dados e aplicações backend. Não é preciso instalar nada: import os já funciona em uma instalação normal do Python.
A recomendação prática para projetos modernos é simples: use pathlib para representar caminhos e fazer operações cotidianas; use os quando precisar conversar diretamente com o ambiente do processo, controlar uma travessia de diretórios ou acessar informações de baixo nível. Os dois módulos não são rivais. Em automações reais, eles aparecem juntos.
Este guia mostra como usar as partes mais úteis do os com exemplos seguros, incluindo um inventário de arquivos para organizar documentos por filial, um cenário comum em empresas brasileiras.
O que o módulo os resolve
O nome vem de operating system. A API reúne funções que abstraem diferenças entre Windows, Linux e macOS, embora alguns recursos continuem específicos de cada plataforma.
Os casos de uso mais frequentes são:
- ler configurações e segredos fornecidos ao processo;
- descobrir a pasta de trabalho atual;
- criar, listar, renomear e remover diretórios;
- percorrer milhares de arquivos recursivamente;
- consultar tamanho, data de modificação e permissões;
- trabalhar com descritores de arquivo e recursos mais próximos do sistema;
- obter informações básicas da plataforma.
import os
print(os.name) # "posix" em Linux/macOS; "nt" no Windows
print(os.getcwd()) # pasta de trabalho atual
print(os.getpid()) # identificador do processo atual
Evite usar os.name para espalhar condicionais por toda a aplicação. Quando uma API multiplataforma já existe — Path, tempfile, subprocess ou shutil — prefira essa abstração. Detectar o sistema é útil apenas quando o comportamento realmente precisa ser diferente.
Variáveis de ambiente com os.environ
os.environ é um objeto semelhante a um dicionário que representa as variáveis de ambiente visíveis pelo processo atual.
import os
ambiente = os.environ.get("APP_ENV", "development")
database_url = os.environ["DATABASE_URL"]
print(f"Executando em {ambiente}")
A diferença entre as duas formas importa:
os.environ["DATABASE_URL"]geraKeyErrorse a variável não existir;os.environ.get("APP_ENV", "development")aceita um padrão;os.getenv("APP_ENV", "development")é um atalho equivalente ao segundo caso.
Configurações obrigatórias devem falhar cedo e com uma mensagem clara:
import os
def variavel_obrigatoria(nome: str) -> str:
valor = os.environ.get(nome)
if not valor:
raise RuntimeError(f"Variável obrigatória ausente: {nome}")
return valor
api_token = variavel_obrigatoria("API_TOKEN")
Não coloque token, senha ou URL de banco diretamente no código. Em desenvolvimento local, uma ferramenta como python-dotenv pode carregar um arquivo ignorado pelo Git; em produção, forneça os valores pelo serviço de deploy, CI/CD ou gerenciador de segredos. O guia de variáveis de ambiente com python-dotenv detalha .env.example, validação e prevenção de vazamentos.
Alterando o ambiente do processo
Também é possível definir uma variável:
os.environ["RELATORIO_FORMATO"] = "csv"
A alteração vale para o processo atual e, normalmente, para processos filhos iniciados depois dela. Ela não muda permanentemente a configuração do terminal, do Windows ou do servidor. Para passar um ambiente controlado a um comando externo, monte um dicionário e envie-o ao subprocess, em vez de modificar estado global sem necessidade:
import os
import subprocess
ambiente = os.environ.copy()
ambiente["APP_ENV"] = "test"
subprocess.run(
["python", "-m", "pytest"],
env=ambiente,
check=True,
)
Para aprofundar a execução segura de comandos sem shell=True, consulte o tutorial de subprocess em Python.
Pasta atual e mudança de diretório
os.getcwd() devolve a pasta de trabalho atual. Caminhos relativos são interpretados a partir dela, não necessariamente a partir do arquivo .py.
import os
print(os.getcwd())
os.chdir("dados")
print(os.getcwd())
os.chdir() altera estado global do processo. Isso pode surpreender bibliotecas, testes e outras funções que esperavam a pasta anterior. Em aplicações, prefira construir caminhos absolutos com Path(__file__) ou receber a pasta como argumento:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
ARQUIVO_CONFIG = BASE_DIR / "config" / "app.toml"
Mudar de diretório é aceitável em um script pequeno e linear, mas raramente é a melhor base para uma aplicação maior.
Criando e removendo pastas
As operações fundamentais são os.mkdir, os.makedirs, os.rmdir e os.removedirs.
import os
os.makedirs("saida/2026/julho", exist_ok=True)
os.mkdir("saida/2026/julho") cria apenas o último diretório e falha se os pais não existirem. os.makedirs(..., exist_ok=True) cria a árvore necessária, de modo semelhante a mkdir -p no Linux.
Para remover uma pasta vazia:
os.rmdir("saida/2026/julho")
os.rmdir não apaga uma árvore com arquivos. Isso é uma proteção útil. Se a intenção for remover conteúdo recursivamente, use shutil.rmtree, mas valide cuidadosamente o caminho antes: uma variável vazia ou pasta errada pode causar uma exclusão grave. Scripts destrutivos devem ter modo de simulação (dry run), logs e testes.
Listando conteúdo: listdir ou scandir?
os.listdir devolve nomes de entradas:
import os
for nome in os.listdir("entrada"):
print(nome)
Quando você também precisa saber se cada entrada é arquivo, pasta ou link, os.scandir é mais conveniente e pode evitar chamadas adicionais ao sistema. Ele devolve objetos DirEntry com métodos como is_file, is_dir e stat.
import os
with os.scandir("entrada") as entradas:
for item in entradas:
if item.is_file() and item.name.endswith(".csv"):
tamanho = item.stat().st_size
print(item.name, tamanho)
Use o context manager (with) para garantir que recursos internos sejam liberados rapidamente. Em uma pasta com muitos itens, scandir costuma ser a escolha de baixo nível mais eficiente.
Evitando seguir links simbólicos
Os métodos de DirEntry aceitam follow_symlinks=False:
with os.scandir("entrada") as entradas:
for item in entradas:
if item.is_dir(follow_symlinks=False):
print(f"Pasta real: {item.path}")
Isso ajuda a evitar ciclos ou travessias inesperadas quando a árvore contém links simbólicos. Em automações que recebem uma pasta indicada pelo usuário, não presuma que toda entrada aponta para um arquivo comum.
Percorrendo subpastas com os.walk
os.walk percorre uma árvore e, a cada etapa, devolve uma tupla com:
- a pasta atual;
- a lista de subpastas;
- a lista de arquivos.
import os
for raiz, pastas, arquivos in os.walk("documentos"):
print(f"Pasta: {raiz}")
for nome in arquivos:
caminho = os.path.join(raiz, nome)
print(caminho)
Em código moderno, você pode combinar os.walk com Path:
import os
from pathlib import Path
for raiz, _, arquivos in os.walk(Path("documentos")):
pasta = Path(raiz)
for nome in arquivos:
caminho = pasta / nome
if caminho.suffix.lower() == ".pdf":
print(caminho)
Essa combinação usa a capacidade de travessia do walk e a API legível do Path para extensões e caminhos.
Ignorando pastas durante a travessia
Um diferencial do os.walk é permitir alterar a lista pastas quando a travessia ocorre de cima para baixo. Assim, você impede que o algoritmo entre em diretórios como .git, .venv, node_modules ou uma pasta de backup.
import os
from pathlib import Path
IGNORADAS = {".git", ".venv", "node_modules", "__pycache__"}
for raiz, pastas, arquivos in os.walk("projeto", topdown=True):
pastas[:] = [nome for nome in pastas if nome not in IGNORADAS]
for nome in arquivos:
caminho = Path(raiz) / nome
print(caminho)
A atribuição pastas[:] = ... modifica a lista que o walk usa internamente. Fazer apenas pastas = [...] cria outra referência e não impede a descida.
Tratando erros de acesso
Uma árvore pode conter uma pasta sem permissão. Use onerror para tornar a falha visível:
import os
def registrar_erro(erro: OSError) -> None:
print(f"Não foi possível acessar {erro.filename}: {erro.strerror}")
for raiz, pastas, arquivos in os.walk("/dados", onerror=registrar_erro):
print(raiz, len(arquivos))
Em produção, troque print pelo módulo de logging em Python e decida se a falha deve interromper o processo ou apenas marcar o relatório como incompleto.
Metadados com os.stat
os.stat consulta informações do arquivo sem abrir seu conteúdo:
import os
from datetime import datetime
info = os.stat("relatorio.csv")
print(info.st_size) # tamanho em bytes
print(datetime.fromtimestamp(info.st_mtime)) # última modificação
print(info.st_mode) # modo/permissões representados como inteiro
Para exibir tamanho de maneira amigável:
def tamanho_legivel(bytes_: int) -> str:
valor = float(bytes_)
for unidade in ("B", "KB", "MB", "GB", "TB"):
if valor < 1024 or unidade == "TB":
return f"{valor:.1f} {unidade}"
valor /= 1024
raise AssertionError("unidade não alcançada")
Datas de arquivo têm nuances. st_mtime representa modificação; st_ctime não significa criação em todos os sistemas — no Unix, costuma representar mudança de metadados. Se a automação depende de data de criação, verifique a plataforma e a disponibilidade de campos específicos.
Há ainda uma condição de corrida entre consultar e usar um arquivo: ele pode ser alterado, substituído ou removido depois do stat. Por isso, não use exists() ou stat() como garantia absoluta. Tente a operação real e trate FileNotFoundError, PermissionError e outros OSError, conforme explicado no guia de tratamento de erros em Python.
Renomeando com os.rename e os.replace
Para renomear ou mover dentro do mesmo sistema de arquivos:
import os
os.rename("entrada/relatorio.tmp", "saida/relatorio.csv")
Se a intenção explícita é substituir o destino, os.replace comunica melhor a operação e oferece substituição atômica quando origem e destino estão no mesmo sistema de arquivos:
import os
from pathlib import Path
conteudo = "id,total\n1,199.90\n"
temporario = Path("relatorio.csv.tmp")
destino = Path("relatorio.csv")
temporario.write_text(conteudo, encoding="utf-8")
os.replace(temporario, destino)
Esse padrão reduz o risco de deixar um arquivo final pela metade: primeiro você grava o temporário e, depois de concluir, substitui o destino. Ainda assim, trate erros e mantenha temporário e destino no mesmo volume para preservar as propriedades esperadas.
Para copiar arquivos, preservar metadados ou mover entre sistemas de arquivos diferentes, use shutil. O tutorial de zipfile e shutil cobre as operações de alto nível.
Exemplo prático: inventário de documentos por filial
Imagine uma pasta compartilhada com subpastas de filiais e documentos em PDF, XML e CSV. O objetivo é gerar um CSV com caminho, extensão, tamanho e última modificação, ignorando backups e ambientes virtuais.
import csv
import os
from datetime import datetime
from pathlib import Path
IGNORADAS = {"backup", ".git", ".venv", "__pycache__"}
EXTENSOES = {".pdf", ".xml", ".csv"}
def inventariar(origem: Path, destino: Path) -> int:
registros: list[dict[str, str | int]] = []
for raiz, pastas, arquivos in os.walk(origem, topdown=True):
pastas[:] = [p for p in pastas if p.lower() not in IGNORADAS]
pasta_atual = Path(raiz)
for nome in arquivos:
caminho = pasta_atual / nome
if caminho.suffix.lower() not in EXTENSOES:
continue
try:
info = caminho.stat()
except (FileNotFoundError, PermissionError) as erro:
print(f"Ignorado {caminho}: {erro}")
continue
registros.append(
{
"caminho": str(caminho),
"extensao": caminho.suffix.lower(),
"tamanho_bytes": info.st_size,
"modificado_em": datetime.fromtimestamp(
info.st_mtime
).isoformat(timespec="seconds"),
}
)
destino.parent.mkdir(parents=True, exist_ok=True)
with destino.open("w", newline="", encoding="utf-8") as arquivo_csv:
campos = ["caminho", "extensao", "tamanho_bytes", "modificado_em"]
escritor = csv.DictWriter(arquivo_csv, fieldnames=campos)
escritor.writeheader()
escritor.writerows(registros)
return len(registros)
if __name__ == "__main__":
total = inventariar(
origem=Path("documentos_filiais"),
destino=Path("saida/inventario.csv"),
)
print(f"Inventário concluído: {total} documentos")
O exemplo aplica várias decisões importantes:
os.walkcontrola quais subárvores serão visitadas;Pathsimplifica extensão, abertura e criação de pastas;- o código trata arquivo removido e falta de permissão;
- a data é gravada em formato ISO, fácil de ordenar;
- o CSV usa
newline=""e UTF-8; - nenhum caminho é apagado ou movido durante o inventário.
Você pode evoluir o projeto para calcular hashes, detectar duplicados, enviar o relatório a uma API ou processar os resultados com o tutorial de CSV em Python. É também um projeto de portfólio interessante para demonstrar automação, tratamento de erros e testes — competências comuns em vagas de Python.
os, pathlib ou shutil: qual escolher?
| Necessidade | Melhor ponto de partida |
|---|---|
| Representar e juntar caminhos | pathlib.Path |
| Ler ou escrever um arquivo inteiro | Path.read_text / write_text |
| Buscar arquivos por padrão | Path.glob / rglob |
| Controlar travessia e podar subpastas | os.walk |
| Ler variáveis do processo | os.environ |
| Listar entradas com metadados eficientes | os.scandir |
| Copiar ou mover entre volumes | shutil.copy2 / shutil.move |
| Remover árvore inteira | shutil.rmtree, com validação rigorosa |
| Criar arquivo temporário | tempfile |
| Executar comandos externos | subprocess |
A escolha não precisa ser exclusiva. Um código idiomático pode usar os.environ para configuração, os.walk para travessia, Path para manipular cada caminho e shutil para copiar o resultado.
Boas práticas e erros comuns
- Não monte comandos do shell com strings vindas do usuário. Use
subprocess.runcom uma lista de argumentos. - Não registre segredos de
os.environ. Logs, exceções e ferramentas de observabilidade podem persistir o conteúdo. - Evite
os.chdirem bibliotecas. Receba caminhos como argumentos e reduza estado global. - Não apague uma árvore sem validar o destino. Resolva o caminho, bloqueie raiz e diretórios críticos e ofereça dry run.
- Use
scandirquando precisa de tipo e metadados.listdirdevolve apenas nomes. - Modifique
pastas[:]para podar owalk. Reatribuir a variável não altera a travessia. - Não confunda
st_ctimecom criação em todas as plataformas. O significado varia. - Trate
OSErrorespecíficos.PermissionErroreFileNotFoundErrorpermitem respostas melhores que umexcept Exceptiongenérico. - Teste com diretórios temporários. O fixture
tmp_pathdo pytest evita mexer nos arquivos reais do desenvolvedor. - Defina encoding ao trabalhar com texto. UTF-8 reduz diferenças entre máquinas, especialmente em nomes e conteúdo com acentos do português.
Perguntas frequentes
O módulo os precisa ser instalado com pip?
Não. O os faz parte da biblioteca padrão do Python e acompanha a instalação da linguagem. Basta usar import os. Pacotes externos só são necessários quando a automação depende de recursos adicionais, como clientes HTTP, leitura de planilhas ou acesso a serviços de nuvem.
Quando usar os.walk em vez de pathlib.rglob?
Use pathlib.rglob quando você só precisa localizar caminhos por um padrão, como todos os arquivos CSV. Prefira os.walk quando precisa controlar a travessia, alterar a lista de subdiretórios, ignorar árvores inteiras ou processar separadamente a pasta atual, suas subpastas e seus arquivos.
Qual a diferença entre os.environ e os.getenv?
os.environ é um mapeamento semelhante a um dicionário: acessar os.environ["TOKEN"] gera KeyError se a variável não existir. os.getenv("TOKEN") devolve None ou um valor padrão. Para configuração obrigatória, o acesso por colchetes ou uma função de validação explícita ajuda a falhar cedo.
os.rename substitui um arquivo existente?
O comportamento de os.rename diante de um destino existente varia entre sistemas e tipos de caminho. Quando a intenção é substituir um arquivo de forma explícita no mesmo sistema de arquivos, os.replace comunica melhor a operação. Para mover entre discos ou fazer cópias, use shutil.
O módulo os funciona no Windows, Linux e macOS?
Sim, grande parte da API é multiplataforma, incluindo environ, walk, scandir, stat e funções básicas de diretório. Alguns recursos são específicos do sistema operacional. Consulte a documentação da função antes de depender de permissões, links, sinais ou descritores em código que precisa rodar em plataformas diferentes.
Conclusão
O módulo os continua essencial no Python moderno porque resolve a parte do trabalho que não termina no algoritmo: configuração do processo, navegação pelo sistema de arquivos, metadados e integração com o ambiente onde a aplicação roda. os.environ, os.scandir, os.walk, os.stat e os.replace cobrem grande parte das necessidades de automações profissionais sem dependências externas.
A arquitetura mais clara combina ferramentas: os para ambiente e travessia, pathlib para caminhos, shutil para cópia e movimentação e subprocess para comandos externos. Com validação de caminhos, tratamento de erros, logs e testes em pastas temporárias, um script que começou como tarefa manual pode virar uma automação confiável para rodar todos os dias.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português