Pular para o conteúdo principal
os e bufio

os e bufio

9 min de leitura

Arquivado emLinguagem de Programação Goem

Aprenda a ler e escrever arquivos, processar texto linha a linha, gerenciar variáveis de ambiente e acessar argumentos de linha de comando com os pacotes os e bufio do Go.

O pacote os é a principal interface do Go com o sistema operacional. Arquivos, diretórios, variáveis de ambiente, informações de processo e argumentos de linha de comando passam todos por ele. O pacote bufio se apoia no os (e no modelo mais amplo de io.Reader / io.Writer) adicionando buffering — transformando muitas leituras e escritas pequenas em menos chamadas de sistema, mais eficientes. Juntos, esses dois pacotes cobrem a maior parte do que os programas precisam para interagir com o ambiente em que rodam.

Abrindo e lendo arquivos

A forma mais direta de ler um arquivo é os.ReadFile. Ele abre o arquivo, lê todo o conteúdo em um slice de bytes e fecha o arquivo — tudo em uma única chamada:

os.ReadFile é a escolha certa para arquivos pequenos onde você precisa do conteúdo de uma vez só. Para arquivos grandes ou situações em que você quer processar o conteúdo conforme ele chega, é necessário mais controle.

os.Open retorna um *os.File que implementa io.Reader. Isso permite passá-lo diretamente para qualquer função que espere um reader, envolvê-lo em um buffered reader, ou processá-lo em pedaços:

Sempre chame defer f.Close() imediatamente após verificar o erro. Arquivos abertos consomem recursos do sistema operacional (file descriptors), e há um limite por processo de quantos podem estar abertos ao mesmo tempo. Esquecer de fechar um arquivo é um vazamento de recurso que eventualmente fará o programa falhar.

os.Open abre somente para leitura

os.Open é um atalho para os.OpenFile(name, os.O_RDONLY, 0). Tentar escrever em um arquivo aberto dessa forma retorna um erro. Para acesso de escrita, use os.Create ou os.OpenFile com as flags apropriadas.

Escrevendo arquivos

os.WriteFile cria ou trunca um arquivo e escreve um slice de bytes nele em uma única chamada:

O terceiro argumento são os bits de permissão do arquivo. 0644 significa que o dono pode ler e escrever; todos os outros podem apenas ler. Esse é o padrão convencional para arquivos de dados.

Quando você precisa de mais controle — criar um arquivo apenas se ele não existir, ou fazer append — use os.OpenFile com combinações explícitas de flags:

As flags se combinam com OR bit a bit. As mais comuns são:

FlagSignificado
os.O_RDONLYAbrir somente para leitura
os.O_WRONLYAbrir somente para escrita
os.O_RDWRAbrir para leitura e escrita
os.O_CREATECriar o arquivo se não existir
os.O_TRUNCTruncar o arquivo ao abrir
os.O_APPENDAdicionar ao final em vez de sobrescrever

I/O com buffer usando bufio

Cada chamada a Read ou Write em um *os.File é uma chamada de sistema. Chamadas de sistema são caras — exigem que a CPU alterne entre modo usuário e modo kernel. Quando seu código lê um byte por vez ou escreve muitas strings pequenas, esse overhead domina o tempo de execução.

O pacote bufio resolve isso envolvendo um reader ou writer com um buffer em memória. As leituras são atendidas pelo buffer até ele esvaziar; aí ele é reabastecido com uma única chamada de sistema maior. As escritas se acumulam no buffer até ele encher ou ser explicitamente descarregado.

Lendo linha a linha com bufio.Scanner

bufio.Scanner é a forma idiomática de iterar sobre texto estruturado. Sua função de split padrão quebra a entrada em newlines:

scanner.Scan() avança para o próximo token. Quando não há mais nada para ler, retorna false. scanner.Text() retorna o token atual como string, sem o delimitador. Sempre verifique scanner.Err() após o loop — se Scan retornou false por causa de um erro (e não por EOF), Err() o expõe.

Você pode mudar como o scanner divide a entrada chamando scanner.Split antes do loop. O pacote bufio fornece quatro funções de split integradas:

Função de splitDivide em
bufio.ScanLinesNewlines (padrão)
bufio.ScanWordsPalavras separadas por espaço
bufio.ScanBytesBytes individuais
bufio.ScanRunesRunes codificados em UTF-8

O scanner padrão tem um limite de tamanho de linha

Por padrão, bufio.Scanner rejeita tokens maiores que 64 KB e retorna bufio.ErrTooLong. Se você espera linhas muito longas — JSON minificado, dados binários — chame scanner.Buffer(buf, maxSize) com um buffer maior antes de iniciar o loop, ou use bufio.Reader.

Controle detalhado com bufio.Reader

Quando você precisa de mais do que leitura linha a linha — ler até um delimitador, fazer peek, ou desfazer a leitura de um byte — bufio.NewReader oferece essas capacidades:

ReadString retorna tudo até a primeira ocorrência do delimitador, inclusive ele. Se a fonte terminar antes de o delimitador ser encontrado, retorna o que tiver junto com io.EOF.

Outros métodos úteis em bufio.Reader:

MétodoDescrição
ReadBytes(delim byte)Como ReadString, retorna []byte
ReadRune()Lê um único rune UTF-8
ReadByte()Lê um único byte
Peek(n int)Retorna os próximos n bytes sem consumi-los
UnreadByte()Devolve o último byte lido ao buffer

Escrita com buffer usando bufio.Writer

bufio.NewWriter envolve um io.Writer e acumula escritas em memória, reduzindo o número de chamadas de sistema:

O passo crítico é Flush. Um bufio.Writer mantém os dados em seu buffer interno até o buffer encher ou você explicitamente descarregá-lo. Se esquecer de chamar Flush, o último bloco de dados pode nunca chegar ao writer subjacente, deixando seu arquivo incompleto.

Sempre faça Flush em um bufio.Writer

defer f.Close() não descarrega o bufio.Writer. Adicione defer bw.Flush() logo após criar o writer para que o flush aconteça mesmo se a função retornar cedo.

Variáveis de ambiente

Variáveis de ambiente são um mecanismo padrão para configurar programas sem hardcodar valores no binário. São especialmente comuns em ambientes containerizados e na nuvem. O pacote os fornece três funções para trabalhar com elas.

Lendo com os.Getenv

os.Getenv retorna o valor de uma variável pelo nome:

Se a variável não estiver definida, Getenv retorna uma string vazia. Isso cria uma ambiguidade: um resultado vazio pode significar que a variável está ausente ou que foi explicitamente definida como string vazia.

Distinguindo "não definida" de "vazia" com os.LookupEnv

os.LookupEnv resolve essa ambiguidade retornando um segundo valor booleano:

ok é true somente quando a variável existe no ambiente, independentemente do seu valor. Use LookupEnv sempre que precisar distinguir "não configurada" de "explicitamente vazia".

Definindo e removendo variáveis

os.Setenv define uma variável para o processo atual:

A mudança é visível para chamadas subsequentes a Getenv no mesmo processo e para qualquer processo filho iniciado depois da chamada. Não afeta o processo pai nem persiste após o término do programa.

os.Unsetenv remove uma variável:

Para ler todas as variáveis de uma vez, os.Environ retorna um snapshot do ambiente como um slice []string, onde cada elemento tem o formato "CHAVE=VALOR".

Argumentos de linha de comando

Todo programa Go pode acessar seus argumentos de linha de comando brutos por meio de os.Args:

os.Args é um []string. os.Args[0] é sempre o caminho para o executável. Os argumentos que o usuário digitou começam no índice 1. Executar ./myapp --port 8080 --debug produziria:

programa: ./myapp argumentos: [--port 8080 --debug]

Antes de indexar os.Args, sempre verifique seu comprimento para evitar um panic:

os.Exit(1) termina o processo imediatamente com um código de saída diferente de zero, sinalizando falha para o shell ou processo chamador. Note que os.Exit não executa funções com defer, por isso deve ser usado apenas no nível mais externo de main.

Para flags estruturadas, prefira o pacote flag

os.Args é o material bruto. Para programas com flags nomeadas (--port, --verbose, --output), o pacote flag da biblioteca padrão faz o parse de os.Args automaticamente, valida os tipos e gera instruções de uso. Use-o sempre que sua CLI crescer além de alguns argumentos posicionais.