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.

12 min de leitura Equipe Python Brasil

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"] gera KeyError se 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.

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:

  1. a pasta atual;
  2. a lista de subpastas;
  3. 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.walk controla quais subárvores serão visitadas;
  • Path simplifica 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?

NecessidadeMelhor ponto de partida
Representar e juntar caminhospathlib.Path
Ler ou escrever um arquivo inteiroPath.read_text / write_text
Buscar arquivos por padrãoPath.glob / rglob
Controlar travessia e podar subpastasos.walk
Ler variáveis do processoos.environ
Listar entradas com metadados eficientesos.scandir
Copiar ou mover entre volumesshutil.copy2 / shutil.move
Remover árvore inteirashutil.rmtree, com validação rigorosa
Criar arquivo temporáriotempfile
Executar comandos externossubprocess

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

  1. Não monte comandos do shell com strings vindas do usuário. Use subprocess.run com uma lista de argumentos.
  2. Não registre segredos de os.environ. Logs, exceções e ferramentas de observabilidade podem persistir o conteúdo.
  3. Evite os.chdir em bibliotecas. Receba caminhos como argumentos e reduza estado global.
  4. Não apague uma árvore sem validar o destino. Resolva o caminho, bloqueie raiz e diretórios críticos e ofereça dry run.
  5. Use scandir quando precisa de tipo e metadados. listdir devolve apenas nomes.
  6. Modifique pastas[:] para podar o walk. Reatribuir a variável não altera a travessia.
  7. Não confunda st_ctime com criação em todas as plataformas. O significado varia.
  8. Trate OSError específicos. PermissionError e FileNotFoundError permitem respostas melhores que um except Exception genérico.
  9. Teste com diretórios temporários. O fixture tmp_path do pytest evita mexer nos arquivos reais do desenvolvedor.
  10. 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.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português