paulo1205
(usa Ubuntu)
Enviado em 17/11/2019 - 16:42h
Bom, o C é uma linguagem simples sintaticamente e em termos das construções que oferece. Essa simplicidade essencial se reflete no poder de expressividade que está à disposição dos programadores, que não é muito grande.
Porém, há de ter em mente que o propósito do C, originalmente, não era mesmo do de ser uma linguagem expressiva, mas de ser uma ferramenta versátil e relativamente eficiente para construir ferramentas em nível de sistema.
Quando se dispõe apenas de comandos de controle de fluxo, variáveis de tipos simples e exatamente três formas de agregar tais variáveis (
arrays ,
struct e
union ) e de funções, com ponteiros explicitamente passando de um lado a outro, não se pode querer que a forma de trabalhar com expressões regulares seja tão simples como é em Perl, por exemplo, que, por sinal, foi concebida e construída com o propósito específico de processamento de textos através de expressões regulares.
A expressividade muitas vezes
esconde a complexidade . Por exemplo, o seguinte bloco em Perl, com suas poucas linhas, tem
exatamente a mesma complexidade interna que o equivalente em C.
# Perl: Faz o parsing de uma linha válida no formato de /etc/passwd.
if($str=~/^([^#+-]?[^:]+):([^:]):([+-]?\d+):([+-]?\d+):([^:]*):([^:]*):([^:]*)$/){
($username, $password, $uid, $gid, $gecos, $homedir, $shell)=($1, $2, 0+$3, 0+$4, $5, $6, $7);
}
/* C: Faz o parsing de uma linha válida no formato de /etc/passwd. */
// (Supõe que os tipos de str, username, password, gecos, home dir e shell sejam char *
// e os de uid e gid sejam uid_t e gid_t, respectivamente).
regex_t re;
if(regcomp(&re, "^([^#+-]?[^:]+):([^:]):([+-]?[[:digit:]]+):([+-]?[[:digit:]]+):([^:]*):([^:]*):([^:]*)$", REG_EXTENDED)==0){
regmatch_t parts[8];
if(regexec(&re, str, 8, parts, 0)==0){
char *part_str[8]={NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
for(int p=0; p<8; ++p){
ssize_t len=parts[p].rm_eo-parts[p].rm_so;
if(parts[p].rm_so>=0 && len>0){
if((parts_str[p]=malloc(1+len))!=NULL){
strncpy(parts_str[p], str+parts[p].rm_so, len);
parts_tr[p][len]='\0';
}
else{
perror("Não foi possível alocar memória para armazenar parte da expressão regular");
abort();
}
}
else
parts_str[p]=strdup("");
}
username=strdup(parts_str[1]);
password=strdup(parts_str[2]);
uid=atoll(parts_str[3]);
gid=atoll(parts_str[4]);
gecos=strdup(parts_str[5]);
homedir=strdup(parts_str[6]);
shell=strdup(parts_str[7]);
for(int p=0; p<8; ++p)
free(parts_str[p]);
}
regfree(&re);
}
Em C++, cujas classes dão um poder de expressividade maior do que o do C, a coisa fica um pouco mais limpa visivelmente, mas tem um custo perceptível no tamanho do código executável gerado porque, além das expressões regulares em si, há um peso decorrente da incorporação das funcionalidades associadas de
strings e de tratamento de exceções, que o C não tem. Nada vem de graça. (O Perl, aliás, traz consigo o peso ainda maior do que apenas
strings e exceções, pois carrega junto com cada programa o peso de todo o interpretador da linguagem, já que os programas são interpretados a cada execução, e o interpretador não tem como saber de antemão quais recursos cada programa vai usar ou deixar de usar.)
// C++: Parsing de uma linha no formato do /etc/passwd usando a classe std::regex.
// (Supõe que os tipos de str, username, password, gecos, home dir e shell sejam std::string
// e os de uid e gid sejam uid_t e gid_t, respectivamente).
std::smatch parts;
if(regex_match(str, parts, std::regex("^([^#+-]?[^:]+):([^:]):([+-]?\\d+):([+-]?\\d+):([^:]*):([^:]*):([^:]*)$"))){
username=parts[1];
password=parts[2];
uid=stoll(parts[3]);
gid=stoll(parts[4]);
gecos=parts[5];
homedir=parts[6];
shell=parts[7];
}
Note que, nos casos acima (inclusive o de Perl, em versões antigas), as expressões regulares são reinterpretadas e transformadas para um formato interno cada vez que o fluxo de execução passar pelas linhas em que elas aparecem. Se os blocos acima forem executados dentro de laços de repetição, o ideal seria que essas transformações para formatos internos (a sintaxe “
/ .../ ” do Perl, o
regcomp () do C/POSIX ou o construtor de
std::regex do C++) fossem feitas fora dos laços de repetição, e apenas a operação de
match (“
=~ ”,
regexec () ou
regex_match ()) ficassem dentro do laço de repetição.
Com relação a sua preocupação quanto a velocidade, se você vai apenas ler o rquivo de logs e extrair partes que lhe interessam para um processamento simples, provavelmente Perl terá desempenho suficiente. Só seria interessante partir para algo mais especializado se você tiver boa parte do processamento fora da parte de extração de dados. (Mas você pode fazer protótipos para valiar o desempenho relativo, se quiser.)
----
NOTA: Não testei nenhum dos código acima, pois os escrevi diretamente na postagem no fórum. Pode ser que contenham erros.
... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)