E/S Avançada e Linha de Comando

Argumentos de linha de comando são como fazer um pedido em um restaurante — você diz ao programa o que quer (argv), o programa conta quantos itens você pediu (argc) e então prepara seu pedido.

Argumentos de Linha de Comando

argc e argv

A função main pode receber dois parâmetros:

C
int main(int argc, char *argv[])
C
#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Contagem de argumentos: %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}

Execução:

BASH
./programa olá mundo 123
TEXT
Contagem de argumentos: 4
argv[0] = ./programa
argv[1] = olá
argv[2] = mundo
argv[3] = 123
💡 Dica: Argumentos de linha de comando são todos strings! Se você precisar de números, deve convertê-los usando atoi, atol ou strtol.

Análise de Argumentos na Prática

Em projetos reais, argumentos de linha de comando tipicamente usam prefixos - para opções:

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

int main(int argc, char *argv[]) {
    int verbose = 0;
    int count = 1;
    const char *filename = NULL;

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
            verbose = 1;
        } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
            count = atoi(argv[++i]);
        } else if (argv[i][0] != '-') {
            filename = argv[i];
        } else {
            fprintf(stderr, "Opção desconhecida: %s\n", argv[i]);
            return 1;
        }
    }

    if (filename == NULL) {
        fprintf(stderr, "Uso: %s [-v] [-n contagem] arquivo\n", argv[0]);
        return 1;
    }

    printf("Arquivo: %s, Contagem: %d, Verbose: %s\n",
           filename, count, verbose ? "ligado" : "desligado");
    return 0;
}
⚠️ Atenção: A análise manual de argumentos é propensa a erros. Projetos complexos podem usar a função getopt, que trata tanto combinações de opções curtas quanto longas.

Variáveis de Ambiente

getenv

C
char *getenv(const char *name);

Retorna o valor da variável de ambiente, ou NULL se ela não existir.

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

int main(void) {
    const char *path = getenv("PATH");
    if (path != NULL) {
        printf("PATH = %s\n", path);
    }

    const char *home = getenv("HOME");
    if (home != NULL) {
        printf("HOME = %s\n", home);
    }
    return 0;
}

A Variável extern environ

C define uma variável global environ que aponta para um array de todas as strings de variáveis de ambiente:

C
#include <stdio.h>

extern char **environ;

int main(void) {
    char **env = environ;
    while (*env) {
        printf("%s\n", *env);
        env++;
    }
    return 0;
}
💡 Dica: Variáveis de ambiente são comumente usadas para configurar o comportamento do programa, como strings de conexão de banco de dados e níveis de log. Isso é muito mais flexível do que codificar valores diretamente no código fonte.

Tratamento de Erros com errno

O Mecanismo errno

Quando funções da biblioteca padrão do C encontram erros, elas tipicamente não falham diretamente. Em vez disso, definem a variável global errno e retornam um valor de erro:

C
#include <errno.h>

errno não é automaticamente zerado após uma chamada bem-sucedida de função da biblioteca! Você deve verificá-lo apenas após uma chamada de função ter falhado.

perror / strerror

C
void perror(const char *s);
char *strerror(int errnum);

perror exibe s: mensagem de erro em stderr. strerror retorna a descrição textual correspondente ao código de erro.

C
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(void) {
    FILE *fp = fopen("arquivo_inexistente.txt", "r");
    if (fp == NULL) {
        perror("fopen");
        printf("Código de erro: %d, Mensagem de erro: %s\n", errno, strerror(errno));
        return 1;
    }
    fclose(fp);
    return 0;
}
TEXT
fopen: No such file or directory
Código de erro: 2, Mensagem de erro: No such file or directory

Exemplo

Padrão completo de tratamento de erros:

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

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Uso: %s arquivo\n", argv[0]);
        return 1;
    }

    FILE *fp = fopen(argv[1], "r");
    if (fp == NULL) {
        fprintf(stderr, "Não é possível abrir '%s': %s\n", argv[1], strerror(errno));
        return 1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    if (ferror(fp)) {
        fprintf(stderr, "Ocorreu um erro de leitura: %s\n", strerror(errno));
        fclose(fp);
        return 1;
    }

    fclose(fp);
    return 0;
}
▶ Experimente
⚠️ Atenção: ferror verifica se um fluxo sofreu um erro de leitura/escrita, o que é diferente de feof (que verifica o final do arquivo).

Fluxos Padrão

C abre automaticamente três fluxos quando um programa inicia:

Fluxo Nome Dispositivo Padrão Descritor de Arquivo
stdin Entrada padrão Teclado 0
stdout Saída padrão Tela 1
stderr Erro padrão Tela 2

A diferença entre stdout e stderr: stdout é armazenado em buffer, stderr não é armazenado em buffer. Ao redirecionar, eles não afetam um ao outro.

C
#include <stdio.h>

int main(void) {
    fprintf(stdout, "Esta é a saída normal\n");
    fprintf(stderr, "Esta é a saída de erro\n");
    return 0;
}
BASH
./programa > saida.txt

Após executar dessa forma, a "saída normal" vai para o arquivo, enquanto a "saída de erro" ainda aparece na tela.

Princípios de Redirecionamento

Na linha de comando:

BASH
./programa 2>&1 tudo.log

Esse comando mescla stderr em stdout e escreve ambos em tudo.log.

Arquivos Temporários

tmpfile

C
FILE *tmpfile(void);

Cria um arquivo temporário aberto no modo "wb+". O arquivo é automaticamente excluído ao ser fechado ou quando o programa termina.

C
#include <stdio.h>

int main(void) {
    FILE *tmp = tmpfile();
    if (tmp == NULL) {
        perror("tmpfile");
        return 1;
    }

    fprintf(tmp, "Dados temporários %d\n", 42);
    rewind(tmp);

    char buf[64];
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        printf("%s", buf);
    }

    fclose(tmp);
    return 0;
}
TEXT
Dados temporários 42

tmpnam

C
char *tmpnam(char *s);

Gera um nome de arquivo temporário que não conflita com arquivos existentes. Porém, há uma condição de corrida — entre gerar o nome e criar o arquivo, outro programa poderia criar um arquivo com o mesmo nome.

⚠️ Atenção: Prefira tmpfile — ele cria e abre o arquivo atomicamente, sendo mais seguro. tmpnam não é seguro em ambientes multi-thread.

Exemplo

Usando um arquivo temporário para processar dados intermediários:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    FILE *tmp = tmpfile();
    if (tmp == NULL) {
        perror("tmpfile");
        return 1;
    }

    char *lines[] = {"banana", "apple", "orange", "grape"};
    int n = 4;

    for (int i = 0; i < n; i++) {
        fprintf(tmp, "%s\n", lines[i]);
    }

    rewind(tmp);
    printf("Antes da ordenação:\n");
    char buf[64];
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        buf[strcspn(buf, "\n")] = '\0';
        printf("  %s\n", buf);
    }

    rewind(tmp);
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            char a[64], b[64];
            long pos = ftell(tmp);
            fgets(a, sizeof(a), tmp);
            fgets(b, sizeof(b), tmp);
            a[strcspn(a, "\n")] = '\0';
            b[strcspn(b, "\n")] = '\0';

            if (strcmp(a, b) > 0) {
                fseek(tmp, pos, SEEK_SET);
                fprintf(tmp, "%s\n%s\n", b, a);
            } else {
                fseek(tmp, pos, SEEK_SET);
                fprintf(tmp, "%s\n%s\n", a, b);
            }
            fseek(tmp, pos + strlen(a) + strlen(b) + 2, SEEK_SET);
        }
        rewind(tmp);
    }

    rewind(tmp);
    printf("Depois da ordenação:\n");
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        buf[strcspn(buf, "\n")] = '\0';
        printf("  %s\n", buf);
    }

    fclose(tmp);
    return 0;
}
▶ Experimente
TEXT
Antes da ordenação:
  banana
  apple
  orange
  grape
Depois da ordenação:
  apple
  grape
  orange
  banana

Saída Formatada Avançada

snprintf

C
int snprintf(char *str, size_t size, const char *format, ...);

Comparado ao sprintf, snprintf adiciona um parâmetro size para limitar o comprimento da escrita, prevenindo estouro de buffer.

C
#include <stdio.h>

int main(void) {
    char buf[10];
    int n = snprintf(buf, sizeof(buf), "Olá, %s!", "Mundo");
    printf("buf = \"%s\", comprimento necessário = %d\n", buf, n);
    return 0;
}
TEXT
buf = "Olá, Mu", comprimento necessário = 12
💡 Dica: O valor de retorno de snprintf é o comprimento que a saída formatada deveria ter, não o número real de bytes escritos. Se o valor de retorno for maior que size-1, a saída foi truncada.

❓ Perguntas Frequentes

P: Qual é a ordem dos argumentos em argv? R: argv[0] é o próprio nome do programa, e argv[1] em diante são os argumentos fornecidos pelo usuário, na mesma ordem em que aparecem na linha de comando.

P: Por que perror exibe em stderr em vez de stdout? R: Porque mensagens de erro não devem ser perdidas quando stdout é redirecionado para um arquivo. stderr não é armazenado em buffer e é independente de stdout, garantindo que as mensagens de erro estejam sempre visíveis.

P: Quando errno é zerado? R: Ele nunca é automaticamente zerado. Você deve definir errno = 0 manualmente antes de chamar uma função que pode falhar, e então verificá-lo após a função retornar um valor de erro.

P: Onde tmpfile cria seu arquivo? R: O local depende do sistema, tipicamente um diretório temporário (como /tmp). Você não precisa se preocupar com o caminho porque tmpfile retorna um FILE, e o arquivo desaparece automaticamente quando o programa termina.*

📖 Resumo

📝 Exercícios

  1. Escreva um programa de linha de comando que aceite -c para contar caracteres, -l para contar linhas e -w para contar palavras em um arquivo
  2. Escreva um programa que use tmpfile para reverter o conteúdo de um arquivo (escreva tudo em um arquivo temporário, depois leia de volta em ordem reversa)
  3. Escreva um programa que demonstre a diferença entre redirecionamento de stdout e stderr, usando > e 2> para capturar cada tipo de saída separadamente
100%