---
title: "Módulo os em Python: environ, walk, scandir e automação"
url: "https://python.dev.br/blog/python-modulo-os-environ-walk-scandir/"
markdown_url: "https://python.dev.br/blog/python-modulo-os-environ-walk-scandir.MD"
description: "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."
date: "2026-07-24"
author: "Equipe Python Brasil"
---

# 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`](/blog/python-pathlib-manipulacao-caminhos-arquivos/) 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.

```python
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.

```python
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:

```python
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](/blog/python-dotenv-env-vars-config/) detalha `.env.example`, validação e prevenção de vazamentos.

### Alterando o ambiente do processo

Também é possível definir uma variável:

```python
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:

```python
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](/blog/python-subprocess-comandos-externos/).

## 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`.

```python
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:

```python
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`.

```python
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:

```python
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:

```python
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`.

```python
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`:

```python
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.

```python
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`:

```python
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.

```python
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:

```python
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](/blog/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:

```python
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:

```python
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](/blog/tratamento-de-erros-python/).

## Renomeando com os.rename e os.replace

Para renomear ou mover dentro do mesmo sistema de arquivos:

```python
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:

```python
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](/blog/python-zipfile-shutil-comprimir-extrair/) 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.

```python
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](/blog/python-csv-leitura-escrita-arquivos/). É também um projeto de portfólio interessante para demonstrar automação, tratamento de erros e testes — competências comuns em [vagas de Python](/vagas/).

## 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

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](/blog/testes-unitarios-python/) 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`](/blog/python-pathlib-manipulacao-caminhos-arquivos/) para caminhos, `shutil` para cópia e movimentação e [`subprocess`](/blog/python-subprocess-comandos-externos/) 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.
