C Variáveis de Ambiente: getenv

Como um programa sabe qual é a pasta pessoal de quem o está usando, ou em que idioma falar, sem perguntar nada? Ele lê o ambiente: uma lista de pares NOME=valor que todo programa recebe ao começar, como HOME=/home/ana e LANG=pt_BR.UTF-8. Nesta aula você vai ler essas variáveis com getenv, tratar a que não existe, usar o ambiente para configurar a lojinha da Ana sem mudar o código, e ver o que um programa pode (e não pode) mudar nele.

Você vai aprender
  • O que são as variáveis de ambiente e como vê-las no terminal (env, echo $HOME).
  • Como ler uma com getenv e por que sempre conferir o NULL.
  • Como dar um valor padrão e como definir uma variável só para uma execução (NOME=valor ./prog) ou para todas (export).
  • Como converter e validar uma variável numérica.
  • Como montar um caminho a partir do HOME.
  • setenv, unsetenv e environ, do POSIX, e até onde vai uma mudança no ambiente.

Você vai precisar de Ponteiro Nulo, Texto ↔ Número e stdin, stdout e stderr. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com gcc -std=c23 -Wall -Wextra arquivo.c -o programa.


A ideia: o quadro de avisos da cozinha

Na parede da cozinha da lojinha há um quadro de avisos: "forno: 180 graus", "cliente do dia: dona Maria", "hoje fecha às 18h". Todo funcionário que chega dá uma olhada no quadro e trabalha de acordo, sem que a Ana precise repetir para cada um. E, se a Ana quiser que só o confeiteiro de hoje use 200 graus, ela entrega a ele uma cópia do quadro com essa linha trocada.

O ambiente funciona assim. O terminal tem o seu quadro de avisos e, quando roda um programa, entrega a ele uma cópia. O programa lê o que quiser da cópia, pode até rabiscá-la, mas o quadro da parede (o do terminal) continua igual.


1. O ambiente da Ana

No terminal, o comando env mostra o ambiente inteiro:

$ env
SHELL=/bin/bash
LANGUAGE=pt_BR:pt:en
EDITOR=nano
PWD=/home/ana
LOGNAME=ana
TZ=America/Sao_Paulo
HOME=/home/ana
LANG=pt_BR.UTF-8
TERM=xterm-256color
TERMINFO=/usr/share/terminfo
USER=ana
SHLVL=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env

Cada linha é uma variável: o nome (em maiúsculas, por costume), um = e o valor, que é sempre texto. As mais úteis:

VariávelNa máquina da AnaO que é
HOME/home/anaA pasta pessoal de quem está usando
USERanaO nome da usuária
PATH/usr/local/sbin:…:/binAs pastas onde o shell procura os comandos, separadas por :
LANGpt_BR.UTF-8Idioma e codificação (o setlocale(LC_ALL, "") da aula errno lê esta)
PWD/home/anaA pasta atual (quando o terminal abriu)
EDITORnanoO editor de texto preferido
TZAmerica/Sao_PauloO fuso horário

Para ver uma só, use echo $NOME (o $ pede ao shell o valor) ou printenv NOME:

$ echo $HOME
/home/ana
$ printenv USER
ana
$ printenv LOJA_MOEDA; echo $?
1                    « não existe: nada impresso, código 1

2. getenv: ler uma variável

getenv = "get environment", pegar do ambiente. Você passa o nome e recebe o valor, ou NULL se a variável não existe:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *casa = getenv("HOME");
    const char *usuaria = getenv("USER");
    const char *moeda = getenv("LOJA_MOEDA");

    printf("HOME = %s\n", casa != NULL ? casa : "(não existe)");
    printf("USER = %s\n", usuaria != NULL ? usuaria : "(não existe)");
    printf("LOJA_MOEDA = %s\n", moeda != NULL ? moeda : "(não existe)");
    return 0;
}
#include <stdlib.h>
Onde mora o getenv. Ele existe desde o primeiro padrão do C (C89).
getenv("HOME")
Procura HOME no ambiente. O nome é exato: "home", em minúsculas, é outra variável (que não existe).
const char *casa
O getenv devolve um ponteiro para o texto do valor, que mora no ambiente do programa. O const é uma promessa sua: "não vou mudar esse texto" (seção 5).
casa != NULL ? casa : "(não existe)"
O operador ternário: se a variável existe, mostra o valor; senão, um texto no lugar. Nunca passe um possível NULL direto para o printf (veja em Quando dá errado).
$ gcc -std=c23 -Wall -Wextra ambiente.c -o ambiente
$ ./ambiente
HOME = /home/ana
USER = ana
LOJA_MOEDA = (não existe)
$ LOJA_MOEDA=BRL ./ambiente
HOME = /home/ana
USER = ana
LOJA_MOEDA = BRL

Na segunda vez, o LOJA_MOEDA=BRL antes do nome do programa acrescentou uma linha à cópia do quadro, só para aquela execução. É assim que se configura um programa pelo ambiente (seção 3).

No editor do guia, o programa recebe um ambiente parecido com o da Ana: HOME=/home/ana, USER=ana, LANG=pt_BR.UTF-8, PATH etc. Não há como acrescentar variáveis antes de rodar (não existe o NOME=valor do terminal), então as caixas desta aula usam as que já estão lá, ou o setenv da seção 6.

Missão: o programa cumprimenta sempre um "visitante". Faça-o cumprimentar quem está usando o computador: leia a variável USER com getenv e, se ela não existir (NULL), continue usando "visitante". No editor, a saída deve ser Olá, ana!.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *nome = "visitante";
    // leia USER aqui

    printf("Olá, %s!\n", nome);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *nome = "visitante";
    const char *usuario = getenv("USER");
    if (usuario != NULL) {
        nome = usuario;
    }

    printf("Olá, %s!\n", nome);
    return 0;
}

3. Valor padrão e NOME=valor ./programa

O uso mais comum: uma configuração opcional. Se a variável existe, o programa a usa; se não, usa um valor padrão. Assim, a Ana pode mudar o nome da loja que aparece na tela sem recompilar nada:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *nome = getenv("LOJA_NOME");
    if (nome == NULL || nome[0] == '\0') {
        nome = "Lojinha";
    }
    printf("Bem-vindo à %s!\n", nome);
    return 0;
}
nome == NULL
A variável não existe.
|| nome[0] == '\0'
Ou existe, mas está vazia (LOJA_NOME= ./boasvindas). O || só olha o nome[0] se o nome não for NULL, então é seguro.
nome = "Lojinha";
O valor padrão. Ele é um texto fixo do programa, por isso o nome é const char *: tanto o texto do getenv quanto o padrão não devem ser alterados.

No terminal, há três jeitos de entregar a variável ao programa:

$ gcc -std=c23 -Wall -Wextra boasvindas.c -o boasvindas
$ ./boasvindas
Bem-vindo à Lojinha!
$ LOJA_NOME="Padaria da Ana" ./boasvindas
Bem-vindo à Padaria da Ana!
$ echo "[$LOJA_NOME]"
[]                                « valeu só para aquela execução
$ LOJA_NOME="Doceria"
$ ./boasvindas
Bem-vindo à Lojinha!              « variável do shell, sem export: não chega
$ export LOJA_NOME
$ ./boasvindas
Bem-vindo à Doceria!              « com export: chega a todo programa daqui em diante
$ unset LOJA_NOME
$ ./boasvindas
Bem-vindo à Lojinha!
No terminalEfeito
NOME=valor ./progSó esta execução do prog recebe a variável
NOME=valor (sozinho)Cria uma variável do shell, que os programas não recebem
export NOME ou export NOME=valorPõe a variável no ambiente: todos os programas rodados depois, neste terminal, a recebem
unset NOMEApaga a variável

Para a variável valer sempre, em todo terminal novo, a Ana escreveria o export no arquivo ~/.bashrc (o Guia Bash explica). E, quando você escolher nomes para as variáveis do seu programa, use um prefixo (LOJA_), para não se confundir com as do sistema.


4. Números no ambiente: converter e validar

O valor de uma variável é sempre texto, e quem o escreveu foi uma pessoa: pode estar errado. Trate-o como qualquer entrada (aula Validar a Entrada): converta com strtol, confira, e, se não servir, avise no stderr e use o padrão.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    long desconto = 0;
    const char *texto = getenv("LOJA_DESCONTO");
    if (texto != NULL) {
        char *fim;
        errno = 0;
        long n = strtol(texto, &fim, 10);
        if (fim == texto || *fim != '\0' || errno == ERANGE || n < 0 || n > 50) {
            fprintf(stderr, "aviso: LOJA_DESCONTO=\"%s\" não vale (use 0 a 50); sem desconto\n", texto);
        } else {
            desconto = n;
        }
    }
    double preco = 20.0;
    printf("Bolo: R$ %.2f\n", preco * (100 - desconto) / 100);
    return 0;
}
long desconto = 0;
Começa com o padrão. Só muda se a variável existir e for válida.
if (texto != NULL)
Variável ausente não é erro: é o caso normal, e o padrão vale.
fim == texto || *fim != '\0' || errno == ERANGE || n < 0 || n > 50
As conferências de sempre: nenhum dígito, sobra de texto, número grande demais, fora da faixa.
fprintf(stderr, "aviso: …")
Avisa, mas não para o programa: uma configuração errada não deve impedir a loja de abrir. Mostrar o valor recebido ("%s") ajuda a pessoa a achar o erro de digitação.
$ gcc -std=c23 -Wall -Wextra desconto.c -o desconto
$ ./desconto
Bolo: R$ 20.00
$ LOJA_DESCONTO=10 ./desconto
Bolo: R$ 18.00
$ LOJA_DESCONTO=dez ./desconto
aviso: LOJA_DESCONTO="dez" não vale (use 0 a 50); sem desconto
Bolo: R$ 20.00
$ LOJA_DESCONTO=80 ./desconto
aviso: LOJA_DESCONTO="80" não vale (use 0 a 50); sem desconto
Bolo: R$ 20.00

5. O texto devolvido: não mude, copie se for guardar

O ponteiro do getenv aponta para dentro do ambiente do programa. Duas regras:

  • Não escreva nele. Mudar o texto é comportamento indefinido. Por isso, guarde-o num const char *: se você tentar mudá-lo sem querer, o compilador reclama.
  • Copie, se for usar muito depois. O padrão C permite que uma nova chamada ao getenv (ou um setenv) mude o texto. Se você precisa montar algo com o valor, monte a sua própria string.

O caso típico é montar o caminho de um arquivo na pasta pessoal, como fazem os programas que guardam configurações em ~/.algumacoisa:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *casa = getenv("HOME");
    if (casa == NULL) {
        fprintf(stderr, "HOME não está definida\n");
        return 1;
    }
    char caminho[256];
    int tam = snprintf(caminho, sizeof caminho, "%s/vendas.txt", casa);
    if (tam < 0 || tam >= (int)sizeof caminho) {
        fprintf(stderr, "caminho comprido demais\n");
        return 1;
    }
    printf("Vou gravar em %s\n", caminho);
    FILE *f = fopen(caminho, "a");
    if (f == NULL) {
        perror(caminho);
        return 1;
    }
    fprintf(f, "3 pães\n");
    fclose(f);
    return 0;
}
snprintf(caminho, sizeof caminho, "%s/vendas.txt", casa)
Monta /home/ana/vendas.txt numa string sua, com limite de tamanho (aula Funções de String). Nunca use strcat direto no texto do getenv: ele não é seu e não tem espaço sobrando.
tam >= (int)sizeof caminho
O snprintf devolve o tamanho que o texto teria. Se não coube, ele foi cortado: melhor parar do que gravar no lugar errado. O HOME vem de fora e pode ser enorme.
fopen(caminho, "a")
"a" de append: acrescenta no fim do arquivo (aula Escrever Arquivos).
$ gcc -std=c23 -Wall -Wextra caminho.c -o caminho
$ ./caminho
Vou gravar em /home/ana/vendas.txt
$ cat ~/vendas.txt
3 pães
$ HOME=/tmp ./caminho
Vou gravar em /tmp/vendas.txt
$ env -u HOME ./caminho
HOME não está definida

O env -u HOME roda o programa com uma cópia do ambiente sem o HOME, para testar o caminho do erro. O HOME=/tmp mostra que o programa obedece ao ambiente: é exatamente o que se quer (e também o motivo para nunca confiar cegamente nele num programa que roda com permissões especiais).

Experimente: rode e veja o ambiente do editor. Depois troque os nomes: "PATH", "LANG", "SHELL", "home" (minúsculo) e um nome inventado.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *casa = getenv("HOME");
    const char *usuaria = getenv("USER");
    const char *moeda = getenv("LOJA_MOEDA");

    printf("HOME = %s\n", casa != NULL ? casa : "(não existe)");
    printf("USER = %s\n", usuaria != NULL ? usuaria : "(não existe)");
    printf("LOJA_MOEDA = %s\n", moeda != NULL ? moeda : "(não existe)");
    return 0;
}

6. setenv, unsetenv e environ (POSIX)

O C padrão só tem o getenv: ler. Para mudar o ambiente, o Linux e o macOS oferecem funções do POSIX (o padrão dos sistemas do tipo Unix): setenv e unsetenv. Como não são do C, com -std=c23 elas só aparecem se você pedir, com #define _POSIX_C_SOURCE 200809L antes dos #include (como o getline da aula Ler uma Linha):

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("antes: %s\n", getenv("LOJA_NOME") != NULL ? getenv("LOJA_NOME") : "(não existe)");
    if (setenv("LOJA_NOME", "Padaria da Ana", 1) != 0) {
        perror("setenv");
        return 1;
    }
    printf("depois do setenv: %s\n", getenv("LOJA_NOME"));
    unsetenv("LOJA_NOME");
    printf("depois do unsetenv: %s\n", getenv("LOJA_NOME") != NULL ? getenv("LOJA_NOME") : "(não existe)");
    return 0;
}
#define _POSIX_C_SOURCE 200809L
"Quero também as funções do POSIX de 2008." Tem de vir antes de qualquer #include.
setenv("LOJA_NOME", "Padaria da Ana", 1)
Cria ou troca a variável. O 1 final quer dizer "se já existir, substitua"; com 0, uma variável que já existe fica como está. Devolve 0 se deu certo.
unsetenv("LOJA_NOME")
Apaga a variável.
$ gcc -std=c23 -Wall -Wextra muda.c -o muda
$ ./muda
antes: (não existe)
depois do setenv: Padaria da Ana
depois do unsetenv: (não existe)
$ echo "[$LOJA_NOME]"
[]                                « o terminal não mudou

O setenv rabiscou a cópia do quadro: vale para o próprio programa e para os programas que ele iniciar depois, mas o terminal que o chamou não fica sabendo. Não existe jeito de um programa mudar o ambiente do shell que o rodou. (É por isso que o cd e o export são comandos do próprio shell, e não programas separados.)

Para ver o ambiente inteiro de dentro do programa, o POSIX oferece a variável global environ: um array de strings "NOME=valor" terminado em NULL, como o argv (aula Argumentos):

#include <stdio.h>

extern char **environ;

int main(void) {
    for (char **p = environ; *p != NULL; p++) {
        printf("%s\n", *p);
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra listar.c -o listar
$ env -i LOJA_NOME=Doceria LOJA_DESCONTO=10 ./listar
LOJA_NOME=Doceria
LOJA_DESCONTO=10
$ ./listar | wc -l
15
extern char **environ;
"Existe uma variável environ, definida em outro lugar" (na biblioteca C; aula static e extern). Ela não está em nenhum cabeçalho do C padrão, por isso a declaração.
env -i …
No terminal: roda o programa com um ambiente vazio (-i, "ignore"), mais as variáveis escritas depois. Útil para testar.

Você também pode encontrar int main(int argc, char *argv[], char *envp[]), com um terceiro parâmetro que é o mesmo array. Ele funciona no Linux, no macOS e no Windows, mas não faz parte do padrão C; prefira getenv para ler uma variável e environ para listar.


Formas diferentes, e qual usar

Para configurar…UseExemplo
Algo que muda a cada execuçãoArgumento./tabuada 7
Uma preferência que vale para vários programas ou várias execuçõesVariável de ambienteEDITOR=nano, LANG=pt_BR.UTF-8
Muitas opções, guardadas entre execuçõesUm arquivo de configuração (o caminho, muitas vezes, a partir do HOME)~/.lojinha.conf
Uma resposta da pessoa, na horaPerguntar (Validar a Entrada)Quantos pães?

Muitos programas combinam os três primeiros, nesta ordem de força: o argumento ganha da variável de ambiente, que ganha do arquivo, que ganha do padrão escrito no código.


Quando dá errado

Falha de segmentação (ou (null)) ao mostrar uma variável que não existe
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *moeda = getenv("LOJA_MOEDA");
    printf("%s\n", moeda);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra nulo2.c -o nulo2
$ ./nulo2
Falha de segmentação     (imagem do núcleo gravada) ./nulo2
$ ./nulo
Moeda: (null)
A moeda é (null).

O que significa: o getenv devolveu NULL, e o programa o passou para o printf sem conferir. Passar NULL para um %s é comportamento indefinido, e o resultado varia: o nulo.c, com printf("Moeda: %s\n", getenv("LOJA_MOEDA")), mostra (null); o nulo2.c morre, porque o gcc troca o printf("%s\n", moeda) por um puts(moeda), mais rápido, que não tolera o NULL. No editor do guia, o nulo2.c mostra (null) e termina com 0, o que esconde o erro.

Como consertar: confira o NULL antes de usar, e dê um valor padrão (seção 3).

Defini a variável no terminal, mas o programa não a vê
$ LOJA_NOME="Doceria"
$ ./boasvindas
Bem-vindo à Lojinha!

O que significa: NOME=valor sozinho numa linha cria uma variável do shell, que fica só no terminal. Os programas recebem só as variáveis exportadas.

Como consertar: export LOJA_NOME (ou export LOJA_NOME="Doceria"), ou LOJA_NOME="Doceria" ./boasvindas na mesma linha.

bash: LOJA_NOME: comando não encontrado
$ LOJA_NOME = Ana ./boasvindas
bash: LOJA_NOME: comando não encontrado

O que significa: com espaços em volta do =, o shell entendeu LOJA_NOME como o nome de um programa, com os argumentos =, Ana e ./boasvindas.

Como consertar: sem espaços: LOJA_NOME=Ana ./boasvindas. Para um valor com espaço, use aspas: LOJA_NOME="Padaria da Ana".

error: implicit declaration of function ‘setenv’; did you mean ‘getenv’?
$ gcc -std=c23 -Wall -Wextra semposix.c -o semposix
semposix.c: In function ‘main’:
semposix.c:5:5: error: implicit declaration of function ‘setenv’; did you mean ‘getenv’? [-Wimplicit-function-declaration]
    5 |     setenv("LOJA_NOME", "Padaria da Ana", 1);
      |     ^~~~~~
      |     getenv

O que significa: o setenv é do POSIX, e não do C. Com -std=c23, o stdlib.h só mostra o que é do C. No editor, o Clang diz use of undeclared identifier 'setenv'; did you mean 'getenv'?.

Como consertar: #define _POSIX_C_SOURCE 200809L na primeira linha, antes dos #include.

O programa mudou a variável com setenv, mas no terminal ela continua igual

O que significa: nada de errado: o programa tem a sua própria cópia do ambiente (seção 6). Mudanças valem para ele e para os programas que ele iniciar.

Como consertar: para mudar o ambiente do terminal, use export no próprio terminal. Um programa pode, no máximo, escrever um comando export … no stdout para o shell executar, mas isso é coisa do Guia Bash.


Referência rápida

EscritaOndeO que faz
const char *v = getenv("NOME");C (stdlib.h)O valor da variável, ou NULL se não existe
if (v == NULL) v = "padrão";CValor padrão
setenv("NOME", "valor", 1)POSIX (stdlib.h + _POSIX_C_SOURCE)Cria ou troca; 0 = deu certo
unsetenv("NOME")POSIXApaga
extern char **environ;POSIXA lista inteira, terminada em NULL
env, printenv NOME, echo $NOMETerminalMostram o ambiente / uma variável
NOME=valor ./progTerminalSó para esta execução
export NOME=valor, unset NOMETerminalPara todos os programas seguintes / apaga
env -i A=1 ./prog, env -u NOME ./progTerminalAmbiente vazio (mais A) / sem NOME

Exercícios

1. A pasta pessoal

Complete para ler a pasta pessoal de quem está usando:

const char *casa = getenv("");

2. Não existe

O que o getenv devolve quando a variável não existe?

3. Para todos os programas

Complete o comando do terminal que faz todos os programas seguintes receberem LOJA_NOME:

LOJA_NOME="Doceria"

4. O programa da Ana chama setenv("LOJA_NOME", "X", 1) e termina. Depois, no mesmo terminal, ela digita echo $LOJA_NOME. O que aparece?

5. Qual é o jeito certo de usar o valor de getenv("LOJA_MOEDA") num printf?

6. LOJA_DESCONTO=dez ./desconto. O que um programa bem-feito faz?

Missão final: a configuração da lojinha

Missão: complete a função ler_numero_env: ela lê a variável nome com getenv(nome) e devolve o número, se ele for válido e estiver entre min e max. Se a variável não existe, devolve padrao calada; se existe mas não vale, mostra no stderr aviso: LOJA_CAIXAS="muitos" não vale; usando 1 (com o nome, o valor e o padrão) e devolve padrao. Como o editor não deixa definir variáveis antes de rodar, o main começa com dois setenv, no lugar do LOJA_DESCONTO=15 LOJA_CAIXAS=muitos ./loja do terminal. A saída deve ser:

Desconto: 15%
aviso: LOJA_CAIXAS="muitos" não vale; usando 1
Caixas: 1
Mesas: 4
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

// Lê a variável "nome" como um número de min a max.
// Ausente: devolve padrao. Inválida: avisa no stderr e devolve padrao.
long ler_numero_env(const char *nome, long padrao, long min, long max) {
    // complete aqui
    return padrao;
}

int main(void) {
    // no terminal seria: LOJA_DESCONTO=15 LOJA_CAIXAS=muitos ./loja
    setenv("LOJA_DESCONTO", "15", 1);
    setenv("LOJA_CAIXAS", "muitos", 1);

    printf("Desconto: %ld%%\n", ler_numero_env("LOJA_DESCONTO", 0, 0, 50));
    printf("Caixas: %ld\n", ler_numero_env("LOJA_CAIXAS", 1, 1, 5));
    printf("Mesas: %ld\n", ler_numero_env("LOJA_MESAS", 4, 1, 20));
    return 0;
}
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

// Lê a variável "nome" como um número de min a max.
// Ausente: devolve padrao. Inválida: avisa no stderr e devolve padrao.
long ler_numero_env(const char *nome, long padrao, long min, long max) {
    const char *texto = getenv(nome);
    if (texto == NULL) {
        return padrao;
    }
    char *fim;
    errno = 0;
    long n = strtol(texto, &fim, 10);
    if (fim == texto || *fim != '\0' || errno == ERANGE || n < min || n > max) {
        fprintf(stderr, "aviso: %s=\"%s\" não vale; usando %ld\n", nome, texto, padrao);
        return padrao;
    }
    return n;
}

int main(void) {
    // no terminal seria: LOJA_DESCONTO=15 LOJA_CAIXAS=muitos ./loja
    setenv("LOJA_DESCONTO", "15", 1);
    setenv("LOJA_CAIXAS", "muitos", 1);

    printf("Desconto: %ld%%\n", ler_numero_env("LOJA_DESCONTO", 0, 0, 50));
    printf("Caixas: %ld\n", ler_numero_env("LOJA_CAIXAS", 1, 1, 5));
    printf("Mesas: %ld\n", ler_numero_env("LOJA_MESAS", 4, 1, 20));
    return 0;
}

O %% no printf escreve um % de verdade (aula Formatos do printf). No laboratório, sem os setenv e com as variáveis vindas do terminal, o programa se comporta igual:

$ gcc -std=c23 -Wall -Wextra loja.c -o loja
$ LOJA_DESCONTO=15 LOJA_CAIXAS=muitos ./loja
Desconto: 15%
aviso: LOJA_CAIXAS="muitos" não vale; usando 1
Caixas: 1
Mesas: 4
$ ./loja
Desconto: 0%                    « nenhuma variável: todos os padrões
Caixas: 1
Mesas: 4

Resumo

  • O ambiente é uma lista de NOME=valor (sempre texto) que cada programa recebe, em cópia, de quem o chamou: HOME, USER, PATH, LANG…
  • getenv("NOME") (do stdlib.h) devolve o valor ou NULL. Confira sempre o NULL e tenha um valor padrão.
  • No terminal: NOME=valor ./prog vale para uma execução; export vale para os programas seguintes; uma variável do shell sem export não chega ao programa.
  • O valor veio de uma pessoa: converta com strtol, valide e, se não servir, avise no stderr e use o padrão.
  • Não altere o texto do getenv: guarde num const char * e monte as suas strings com snprintf.
  • setenv/unsetenv/environ são do POSIX (#define _POSIX_C_SOURCE 200809L) e mudam só a cópia do próprio programa, nunca o terminal.