Mind Bending

Seguindo a mesma ideia de implementar padrões POSIX e aumentar a integração de programas em C, hoje vamos ver um assunto bem mais denso e complexo, argumentos e parâmetros.

Os parâmetros e argumentos são aquelas opções que usamos quando executamos um programa na linha de comando, como df -h ou ls -la --color. Tratar esses parâmetros e argumentos faz com que nossos programas estejam em compliance com o sistema operacional.

C Language

A implementação dos parâmetros e argumentos da linha de comando é um assunto complexo e requer dedicação. Abaixo explico de maneira prática como implementá-los através de funções GNU. No entanto, vou explicar também um pouco desse padrão da linha de comando, dos parâmetros e dos argumentos, pois é imprescindível saber como eles funcionam para uma correta implementação.

No geral quando estamos desenvolvendo um programa, usaremos pouquíssimos argumentos, mas em alguns casos precisaremos usar todas as opções na íntegra (Principalmente se vamos desenvolver ferramentas para linha de comando GNU/Linux).

Parâmetros e Argumentos?

Primeiramente, vamos esclarecer a diferença entre o termo parâmetro e o termo argumento. Um parâmetro é uma ‘propriedade’ passada pela linha de comando. Um exemplo de um parâmetro seria a opção -h passada para o comando df. Este parâmetro define ao programa df que a saída das informações deve ser de fácil leitura (Em KB, MB, GB - Human-Readable).

Exemplo df -h

Os argumentos são as opções para cada parâmetro. Um exemplo de um parâmetro com argumento obrigatório seria a opção -t passada para o comando df. Este parâmetro limita o programa df a exibir apenas os tipos de partições definidas no argumento. Portanto, o parâmetro -t requer o argumento ‘tipo de arquivo’:

Comando df -t ext4

Funcionamento dos Parâmetros e Argumentos da linha de comando

Em sistemas operacionais padrão POSIX e GNU é possível combinar dois parâmetros na linha de comando através de uma única entrada. Em outras palavras, caso necessite utilizar o parâmetro -l e o parâmetro -h, é possível combiná-los em um único parâmetro -lh ou -hl

Perceba que a ordem não é importante. No entanto, caso o parâmetro desejado necessite de argumentos, este parâmetros deve ser deixado por último afim de permitir a inclusão de seu argumento, conforme a seguir:

Parâmetro e Argumento

Os parâmetros -l e -h são do tipo no_arguments, sem argumentos - Enquanto o parâmetro -t é do tipo required_arguments, portanto obrigatórios. As constantes de parâmetros são definidas abaixo:

Nome Argumentos
no_arguments Não
optional_arguments Opcionais
required_arguments Obrigatórios

Parâmetros Simples, Longos e Restantes

Parâmetros simples são aqueles parâmetros curtos (apenas 1 caractere), precedidos pelo sinal de -. Já os parâmetros longos são palavras, precedidas pelos sinais --. É uma boa prática que todos os parâmetros longos possuam parâmetro simples equivalentes, no entanto não é possível combinar os parâmetros longos como fazemos com os curtos. Eles devem ser passados de forma literal.

Usando ainda o comando df, um exemplo de parâmetro curto que possui um parâmetro longo equivalente é o -h. Seu parâmetro equivalente é o --human-readable

O padrão POSIX utiliza apenas parâmetros simples. Já o padrão GNU usado no GNU/Linux utiliza tanto os parâmetros simples quanto os parâmetros longos.

Ainda temos outro tipo de parâmetro, que chamo de parâmetros restantes. Esses parâmetros são aqueles não precedidos pelos caracteres - ou --.

O argc e argv

Existem duas variáveis que fazem a ponte entre os parâmetros e argumentos passados na linha de comando para nosso programa.

A primeira delas é a variável argc, que entra na nossa velha main() e é do tipo INT. Ela nos informa o número de parâmetros passados mais 1. Vamos ver isso na prática? Considere o seguinte programa simples:

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

void main(int argc, char** argv) {
        printf("Valor de argc: %d\n", argc);
        exit(0);
}

Agora vamos executar:

Programa Simples

Veja que quando executamos nosso programa sem nenhum parâmetro na linha de comando, argc retorna 1. Quando executamos com os parâmetros adicionais argc retorna 1 + numero de parâmetros. Isso porque o "nome do programa" - ./simples - também é considerado.

Quando usamos os parâmetros param1, param2 e param3 nosso argc retornou 4 (o "nome do programa" + 3 argumentos).

Então, caso a variável argc seja igual a 1 sabemos que nenhum parâmetro foi passado na linha de comando. A partir daí é possível parar nosso programa, caso necessitemos desses parâmetros para que o mesmo seja executado corretamente:

if(argc == 1) { // Sem parametros
        printf("Parametros faltando\n");
        exit(0);
}

A segunda variável que nos auxilia no tratamento de argumentos e parâmetros é a variável argv, que também entra na main() do nosso programa. Esta variável é um ponteiro para um Array de strings e que contém os parâmetros da linha de comando. Como sabemos, em C não é possível deduzir o fim de um array, caso este não possua um terminador pré-definido. Por isso temos que combinar o valor de argc para ler todos os parâmetros corretamente.

Vamos alterar nosso programa simples.c para que ele exiba todos nossos parâmetros da linha de comando:

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

void main(int argc, char** argv) {
        int i;

        printf("Valor de argc: %d\n", argc);

        for(i = 0; i < argc; i++) {
                printf("Valor de argv[argc %d]: %s\n", i, argv[i]);
        }
        exit(0);
}

Compilamos e rodamos. Vamos ao resultado:

Programa simples mostrando argc e argv

Opa! Então quer dizer que que argv[0] é o nome do nosso programa? Não exatamente, argv[0] exibe exatamente a linha de comando que foi usado para executar nosso programa. Caso usemos o caminho completo do executável, veremos um valor de argv[0] respectivo:

Caminho completo

Aqui vai uma dica ótima: O nome do nosso programa (ou processo) puro, sem o caminho ou o diretório pode ser conseguido através uma variável externa especial chamada __progname. Antes de usá-la é necessário declará-la:

extern __progname;
printf("Nome do Programa: %s\n", __progname);

A diretiva extern amplia a visibilidade das variáveis e funções no C. No caso da variável __progname esta informação vem da biblioteca LibC.

Interpretando Parâmetros e Argumentos

Vimos que a passagem dos parâmetros e argumentos pode ser muito flexível, aceitando uma infinidade de combinações. Para atender o compliance POSIX e GNU, precisamos interpretar todas essas variações, opções e argumentos da linha de comando.

Para essa finalidade, podemos contar com getopt() e getopt_long(). Ainda bem! Imagine fazer o parse de cada combinação dessas manualmente! Seria trabalhoso, não?

Aqui entra novamente a praticidade de estar de acordo com as normas GNU: Essas funções estão disponíveis nas bibliotecas padrão do gcc. Para isso precisamos incluí-las no nosso programa:

#include <unistd.h> // *POSIX* Para o getopt() original
#include <getopt.h> // *GNU* Para o getopt_long()

O padrão POSIX faz leitura apenas de parâmetros simples (curtos, precedidos apenas por - ), por isso usaremos o getopt_long(), que dá suporte tanto aos parâmetros simples e aos parâmetros longos.

Abaixo uma tabela que descreve os argumentos que nosso pequenos programa de exemplo irá implementar;

Função Forma longa Forma Curta
Controla a verbosidade —verbose -v
Formatação tabular —tabular -t
Define Usuário —usuario -u
Mensagem de ajuda —ajuda N/A
Versão do programa —versao N/A
Preenchimento com zeros N/A -v

Primeiro vamos definir quais são os parâmetros longos que nosso programa aceitará:

struct option OpcoesLongas[] = {
        {"verbose", no_argument, NULL, 'v'},
        {"tabular", no_argument, NULL, 't'},
        {"usuario", required_argument, NULL, 'u'},
        {"ajuda", no_argument, NULL, 1}
        {"versao", no_argument, NULL, 2}
        {0, 0, 0, 0}
};

O formato para cada opção longa é:

Formato Opções Longas

O terceiro argumento é uma flag que indica se o valor do quarto argumento (as letras v, t e u ou os números 1 e 2) deve ser retornado ou se deve preencher uma variável específica.

Note que não foram definidos os argumentos que possuem apenas a forma curta. Os parâmetros simples (ou curtos) são definidos diretamente na chamada à função ‘getopt_long()’. Eles são definidos pelo caractere que representará cada parâmetro e finalizados com o símbolo :, conforme a seguir:

Ofrmato getopt long

Os parâmetros longos já explicamos acima. Vamos dar um zoom nos parâmetros simples: Veja que as opções ztuv são finalizadas com o : e logo depois temos a opção a e o finalizador :. Isto agrupa quais os parâmetros poderão ser usados em conjunto (z, t, u, v) e qual o parâmetro que deve ser usado separado (a).

Agora vamos implementar a leitura de todos os parâmetros (simples e longos) e seus argumentos e os parâmetros restantes com os seguintes códigos:

Passo 1: Ler os parâmetros (simples e longos) e seus argumentos

char optc = 0; // Parece estranho... Mas todo CHAR é na verdade um INT

while((optc = getopt_long(argc, argv, "ztvu:a:", OpcoesLongas, NULL)) != -1) {
        switch(optc) {
                // código...
                // código...
                case 'u' :
                        printf("Arquivo: %s\n", optarg);
                        break;
                // código...
                // código...
        }
}

Para cada parâmetro temos um argumento referente. Esse argumento pode ser lido pelo ponteiro optarg. Este ponteiro irá ser automaticamente atualizado para cada argumento a cada rodada do loop while.

Passo 2: Ler os parâmetros restantes com um loop while e incrementar a variável especial optind usada como indexador de argv

printf("Parâmetros Restantes:\n");
do {
        printf("%s\n", argv[optind]);
}
while(++optind < argc);

Mão Na Massa

Agora que expliquei cada parte do código, vamos fazer um teste com nosso programa na íntegra. Vamos chamar nosso programa de argumentos, conforme o código abaixo:

#include <stdio.h>
#include <unistd.h> // *POSIX* Para o getopt() original
#include <getopt.h> // *GNU* Para o getopt_long()
#include <string.h>
#include <stdlib.h>

#define MAJOR_VERSION   1
#define MINOR_VERSION   0

int main(int argc, char** argv) {
        // Variaveis para os parametros e argumentos
        short tabular = 0; // Opcao 't'
        short verbose = 0; // Opcao 'v'
        short zero    = 0; // Opcao 'z'

        char optc = 0;  // Parece estranho... Mas todo CHAR é na verdade um INT

        struct option OpcoesLongas[] = {
                {"verbose", no_argument, NULL, 'v'},
                {"tabular", no_argument, NULL, 't'},
                {"usuario", required_argument, NULL, 'u'},
                {"arquivo", required_argument, NULL, 'a'},
                {"ajuda", no_argument, NULL, 1},
                {"versao", no_argument, NULL, 2},
                {0, 0, 0, 0}
        };

        if(argc == 1) { // Sem argumentos
                printf("Parametros faltando\n");
                exit(0);
        }

        while((optc = getopt_long(argc, argv, "ztvu:a:", OpcoesLongas, NULL)) != -1) {
                switch(optc) {
                        case 1 : // Ajuda
                                printf("Mensagem de ajuda do programa\n");
                                exit(0);
                        case 2 : // Versao
                                printf("Versão %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
                                exit(0);
                        case 'u' : // Usuario
                                printf("Usuario: %s\n", optarg);
                                break;
                        case 'a' : // Arquivo
                                printf("Arquivo: %s\n", optarg);
                                break;
                        case 't' : // Tabular
                                tabular = 1;
                                break;
                        case 'v' : // Verbose
                                verbose = 1;
                                break;
                        case 'z' : // Zero
                                zero = 1;
                                break;
                        default : // Qualquer parametro nao tratado
                                printf("Parametros incorretos.\n");
                                exit(0);
                }
        }

        printf("Argumentos e Parametros do programa:\n");

        printf("Verbose: %c - Tabular: %c - Zero: %c\n\n", ((verbose) ? ('S') : ('N')), ((tabular) ? ('S') : ('N')), ((zero) ? ('S') : ('N')));

        if(optind < argc) { // Se optind for menor que argc entao nao temos parametros restantes
                printf("Parametros Restantes:\n");
                do {
                        printf("\t%s\n", argv[optind]);
                }
                while(++optind < argc);

                printf("\n");
        }
}

Todo o código acima está disponível para download aqui.

Vamos compilar e rodar o programa. Use todas as combinações diferentes de parâmetros e argumentos e veja como o programa se comporta. Em especial eu gostaria de demonstrar o que acontece se usamos um parâmetro inválido, não tratado pelo programa.

Veja o que acontece se usarmos o parâmetro -f, por exemplo:

Parâmetro Inválido

Perceberam alguma coisa estranha aqui? Bem, esta mensagem invalid option -- 'f' está aonde no nosso código? Na verdade esta mensagem de erro não veio do nosso código, mas sim da biblioteca LibC - a mesma onde se encontra o getopt() e o getopt_long().

Esta mensagem foi mostrada em inglês porque o meu GNU/Linux está em inglês. Caso tivéssemos um sistema em português, esta mensagem seria mostrada neste idioma. Tratarei o assunto "Regionalização" no meu próximo artigo.

É isso! Tentei ser o mais prático na demostração dos parâmetros e argumentos e como implementá-los em C. A partir daqui é possível criar programas para GNU/Linux que tratam os parâmetros e argumentos corretamente.

Saiba Mais

Para mais informações sobre os parâmetros e argumentos recomendo os seguintes links:

Obrigado!

Fernando Almeida

Fernando Almeida

Formado em Gestão da Tecnologia da Informação, divide seu dia entre dois mundos: A vida de “engravatado” como analista de risco e broker para empresa de Leasing OPEX e a vida de desenvolvedor de sistemas embarcados para empresa de telecomunicações. Grande entusiasta do movimento OpenSource, aproveita suas horas vagas para um bom passeio de moto com sua esposa.


Comments

comments powered by Disqus