Ir ao conteúdo
  • Cadastre-se

C Programação no linux, trabalho com execv


Jesus Porfirio

Posts recomendados

Olá gente tudo bem, preciso de uma ajuda...

O professor da faculdade pediu pra eu criar um programa utilizando o execv.

NO caso a ideia é simples é só para fazer um programa que o usuario digite comandos de linux, salve numa matriz de caracteres e utilize essa variavel no execv

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

void entrada (char *p){
	fgets(p, sizeof(p),stdin);
	if(p[strlen(p)-1] =='\n') {p[strlen(p)-1]='\0';}
}	
	
int main(){
	char dirPadrao[20]="/bin/";
	char arg[10][20];
	
	char comando[20];
	char argumento[20];
	
	int i =1;
	
	printf("Entre com o comando: ");
	scanf("%s", comando);
	
	strcat(dirPadrao, comando);
	strcpy(arg[0], comando);

	printf("\nEntre com o argumento");
	entrada(argumento);
		
	while(strlen(argumento) != 0){
		strcpy(arg[i], argumento);
		i++;
		
		printf("Entre com o argumento");
		entrada(argumento);
	}

	execv(dirPadrao, argumento);
}

 

 

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

O segundo parâmetro de execv deve receber um vetor de ponteiros char terminado em NULL,

 

A string argumento pode atribuir os parâmetros a arg, como arg é um vetor de ponteiro não precisa de strcpy. Acho que a função entrada poderia ter um retorno para indicar quando recebe 0 para sair do loop,

 

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

int entrada(char *p){
    fgets(p,20,stdin);
    if(p[0] == '0'){return 1;}
    p[strlen(p)-1] = 0;
    return 0;
}

int main(){
    char *arg[10];
    char dirPadrao[10] = "/bin/";
    char argumento[10][20];
    char comando[20];
    int i = 1;

    arg[0] = dirPadrao;

    printf("Comando: ");
    entrada(comando);
    strcat(dirPadrao,comando);

    while(i < 10){
        printf("Argumento: ");
        if(entrada(argumento[i - 1])){
            break;
        }
        arg[i] = argumento[i - 1];
        i++;
    }
    arg[i] = NULL;
    execv(arg[0],arg);
    return 0;
}

 

  • Curtir 1
  • Obrigado 2
Link para o comentário
Compartilhar em outros sites

Cuidado com esse arg[i] = NULL, porque para strings não se deve usar 'NULL' para caractere e sim '\0' como caractere nulo (final da string).

Antigamente (não sei hoje no padrão mais recente), NULL era uma macro com valor (void *)0. Hoje devem ter transformado o NULL em algo mais seguro como o nullptr do C++11

Link para o comentário
Compartilhar em outros sites

@cpusam Não é uma string char, arg é um vetor de ponteiros.

 

Veja a documentação da função execv,

 

The execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that represent the argument list available to the new program. The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a NULL pointer.

 

https://linux.die.net/man/3/execv

 

  • Curtir 3
Link para o comentário
Compartilhar em outros sites

 

Mas qual a sua dúvida? Tem alguma pergunta?
 

 

Vendo seu programa: Essa função e o uso dela não estão bem:
 

void entrada (char *p){
	fgets(p, sizeof(p),stdin);
	if(p[strlen(p)-1] =='\n') {p[strlen(p)-1]='\0';}
}

 

Entenda que

  • nunca retorne void: em geral é um desperdício e muitas vezes um erro. Aqui é um desperdício. 
     
  • fgets() SEMPRE retorna um '\n' no final da string mesmo que não tenha um no arquivo de entrada. Só vai retornar 0 se não leu nada. E todo o propósito de ler algo é tratar o que foi lido. Em seu programa logo depois de retornar de entrada() você testa strlen(argumento) então porque não retornar o tamanho da string de uma vez? E se fgets() retornar 0 seu programa vai  "ler" de novo o valor da última leitura.

    Eis o que acontece: fgets() retorna um ponteiro, mas só se ler algo. Se não ler nada vai retornar NULL. Essa é uma distinção importante. Se acostume a escrever
     
    	p = fgets(p, sizeof(p),stdin);


    e nunca
     

    	fgets(p, sizeof(p),stdin);


    porque se não ler nada não vai mudar o ponteiro. Esse é um erro muito chato de descobrir, mesmo em programas de produção: se fgets() não ler nada veja o que acontece em seu programa:
    vai chamar strlen(p) na linha seguinte e continuar, só que não leu nada, e duplicar a leitura anterior...

	fgets(p, sizeof(p),stdin);
	if(p[strlen(p)-1] =='\n') {p[strlen(p)-1]='\0';
  • Veja o que escreveu:
printf("\nEntre com o argumento");
	entrada(argumento);
		
	while(strlen(argumento) != 0){
		strcpy(arg[i], argumento);
		i++;
		
		printf("Entre com o argumento");
		entrada(argumento);
	}

 

Então entrada() faria mais sentido como:
 

int     entrada (const char* msg, char* p)
{
	if( p == NULL ) return 0; // melhor previnir
	if ( msg != 0 ) printf("%s ", msg );
	p = fgets(p, sizeof(p),stdin);
	if ( p == NULL ) return 0;
	int n = strlen(p); // unica chamada a strlen()
	p[n-1] = 0;
	return n-1;
}	

 

Porque?

  • trata o retorno de fgets()
  • retorna o tamanho da string lida, assim não precisa chamar strlen() a cada valor lido, já que aqui chamou.
  • retorna zero se o ponteiro vem inválido para não cancelar o programa a toa

tem um parâmetro msg que é um prompt para a leitura, assim fica mais útil. Pode usar
 

	int n = entrada("\nPrograma a executar (de /bin apenas): ", linha);


ou
 

	int n = entrada("", linha);


Sobre essa versão
 

@Midori
 

int entrada(char *p)
{
    fgets(p,20,stdin);
    if(p[0] == '0'){return 1;}
    p[strlen(p)-1] = 0;
    return 0;
}

 

deveria também testar o retorno de fgets(). Se a entrada padrão for redirecionada,ou o instrutor teclar control-Z para testar...

 

e o if() deveria testar com ZERO --- 0 ---  e não '0'
 

    char *arg[10];
    char dirPadrao[10] = "/bin/";
    char argumento[10][20];
    char comando[20];

 

Essas construções são arriscadas. Está montando um bloco de parâmetros para chamar execv(), é como o bloco de main(int arc, char** argv). E dirPadrao é const char[10], que não é exatamente char*. 


E comando é char[20]. Quando usa 
 

    printf("Comando: ");
    entrada(comando);
    strcat(dirPadrao,comando);


Pode cancelar o programa porque só cabem 10 em dirPadrao, incluído o zero no fim. E comando é char[20]. Note que entrada() poderia aceitar um prompt e devolver o tamanho da string lida, como eu mostrei. Seria bem mais útil.

  • E se entrada() não ler nada para o nome do programa não adianta continuar rodando e ler os argumentos porque não tem programa para chamar.
  • Pois é: entrada() podia ser melhor. E essa parte ficou estranha para ler
    while(i < 10){
        printf("Argumento: ");
        if(entrada(argumento[i - 1])){
            break;
        }
        arg[i] = argumento[i - 1];
        i++;
    }


sempre vai ler 10 argumentos, o que provavelmente não é correto. Muitos testes vão ter apenas o program por exemplo, "ls" para mostrar o diretório corrente. Zero parâmetros. E assim o cara vai ter que digitar DEZ campos a toa. ;) 

Provavelmente um for() até 10 com um break quando não ler nada e declarando int i localmente seria melhor opção. E o último ponteiro tem que ser NULL.

 

E char[10][20] não serve para execv(). Testou isso de verdade? Eis execv():

image.png.c2e5c9b0dcc3c80936d991e1a00fea28.pngO bloco de argumentos é char* const[]

 

 




 

 

 

 

 

 

 

   execv(arg[0],arg);
   return 0;

 

Se execv() falhar seria melhor ter escrito 
 

    execv(arg[0],arg);
    perror("Erro: ");
    printf("execv() falhou\n\n");
    return 0;

 

e assim poderia ler a resposta no terminal

40 minutos atrás, cpusam disse:

Cuidado com esse arg[i] = NULL, porque para strings não se deve usar 'NULL' para caractere e sim '\0' como caractere nulo (final da string).

Antigamente (não sei hoje no padrão mais recente), NULL era uma macro com valor (void *)0. Hoje devem ter transformado o NULL em algo mais seguro como o nullptr do C++11


Em 2020  NULL é zero.

Expande para
 

    ((void*) 0) 


e essa é "segurança" de que falou. Ao menos em gcc 9.3.

 

O último ponteiro do bloco para execv() tem que ser NULL. O tipo é char* const*. Ao contrário do bloco de main() que é precedido por um int como em
 

    int main(int argc, char** argv);


o de execv() é terminado por um NULL
 

EXEC(3)                                       Linux Programmer's Manual EXEC(3)

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

SYNOPSIS
       #include <unistd.h>
       extern char **environ;
       int execl(const char *pathname, const char *arg, ...
                       /* (char  *) NULL */);
       int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
       int execle(const char *pathname, const char *arg, ...
                       /*, (char *) NULL, char *const envp[] */);
       int execv(const char *pathname, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                       char *const envp[]);

 

 

 

  • Curtir 1
  • Amei 1
Link para o comentário
Compartilhar em outros sites

27 minutos atrás, arfneto disse:

e o if() deveria testar com ZERO --- 0 ---  e não '0'

Não, o teste deve ser com '0' mesmo.

 

Se p.ex digitar 0 e exibir a linha deste comando a saída será: 48, 0 ,48

 

printf("%d, %d, %d\n",p[0],0,'0');

 

27 minutos atrás, arfneto disse:

Pode cancelar o programa porque só cabem 10 em dirPadrao, incluído o zero no fim. E comando é char[20].

Bem observado, dirPadrao também poderia ter 20 elementos.

 

char dirPadrao[20] = "/bin/";

 

 

27 minutos atrás, arfneto disse:

sempre vai ler 10 argumentos, o que provavelmente não é correto. Muitos testes vão ter apenas o program por exemplo, "ls" para mostrar o diretório corrente. Zero parâmetros. E assim o cara vai ter que digitar DEZ campos a toa. ;) 

 

Não vai obrigatoriamente até dez, se o usuário digitar 0 vai sair do loop

 

27 minutos atrás, arfneto disse:

E char[10][20] não serve para execv(). Testou isso de verdade? Eis execv():

Testei o comando ls com argumentos -l e -l -r

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

16 minutos atrás, Midori disse:

Não, o teste deve ser com '0' mesmo.

 

Se p.ex digitar 0 e exibir a linha deste comando a saída será: 48, 0 ,48

 

Fiquei curioso agora. Qual seria o propósito de testar com 48, ou '0'? Algo como o usuário digitar um zero para sair?

 

18 minutos atrás, Midori disse:

Bem observado, dirPadrao também poderia ter 20 elementos.

 

Sim, afinal "/bin/" já vai ficar com 5.

 

19 minutos atrás, Midori disse:

Não vai obrigatoriamente até dez, se o usuário digitar 0 vai sair do loop

 

Entendo. E não está escrito em lugar algum. E se '0' fosse um argumento para o comando? Não era mais simples algo como teclar apenas um ENTER? 

 

Esse bloco funcionaria:
 

	for ( int i = 1; i<10; i+=1 )
	{
		n = entrada("Arg:", linha);
		if ( n == 0 ) break;
		argv[i] = (char*) malloc(n);
		strcpy( argv[i], linha); // esse foi digitado
		argc += 1;
	};
	argv[argc] = NULL; // o ultimo

 

e provavelmente mais seguro e legível. 

 

26 minutos atrás, Midori disse:

Testei o comando ls com argumentos -l e -l -r


Bom que funcionou. De todo modo para execv() e essas rotinas que esperam blocos de ponteiros sugiro montar o bloco com cuidado e passar endereços que não vão sair de escopo. 

  • Curtir 2
Link para o comentário
Compartilhar em outros sites

12 horas atrás, arfneto disse:

Fiquei curioso agora. Qual seria o propósito de testar com 48, ou '0'? Algo como o usuário digitar um zero para sair?

É para sair quando digitar zero, mas é melhor mesmo sair quando só digitar ENTER como você comentou.

 

Por que você acha melhor usar malloc e strcpy? É para não deixar reservado espaço desnecessário no caso do usuário entrar com menos argumentos? Sem alocação dinâmica na declaração de argumento já tem os tamanhos definidos com arg só apontando para esses elementos.

 

Fiz outro código sem o uso de string.h (não vejo problema em usar, só quis fazer sem dessa vez) e da função entrada e aplicando essa forma de sair ao digitar só enter.

 

Deixei a lista de arg para nove argumentos mesmo, o primeiro já recebe dpadrao.

 

#include <stdio.h>
#include <unistd.h>

int main(void) {
    char dpadrao[20] = "/bin/";
    char argumento[10][20];
    char *arg[10], c;
    int i = 5;

    while((c = fgetc(stdin)) != '\n'){
        if(i >= 20){break;}
        dpadrao[i] = c;
        i++;
    }
    i = 0;
    arg[i++] = dpadrao;
    while(i < 10){
        char *a = fgets(argumento[i],20,stdin);
        int n = 0;
        if(*a == '\n'){break;}
        while(*a++){n++;}
        arg[i] = argumento[i];
        arg[i][n - 1] = 0;
        i++;
    }  
    arg[i] = NULL;
    execv(dpadrao,arg);
    return 0;
}

 

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

4 horas atrás, Midori disse:

Por que você acha melhor usar malloc e strcpy? É para não deixar reservado espaço desnecessário no caso do usuário entrar com menos argumentos? Sem alocação dinâmica na declaração de argumento já tem os tamanhos definidos com arg só apontando para esses elementos

 

Porque não é seguro usar endereços de variáveis locais.

 

Alocando novas áreas e montando todo o bloco no heap você garante que todos os ponteiros continuam válidos e você pode passar adiante o endereço do bloco com segurança. Se você usa o endereço de variáveis locais elas vão ficar fora de escopo se for uma função e cancelar o programa. Não acontece em main() no exemplo aqui porque main() só vai sair de escopo quando o programa terminar. Outro caso, no geral, é o de literais, tipo const char*: se você passa isso como se fosse char* e no meio do programa alguém resolve converter a string do ponteiro 4 para maiúsculas e ele veio de uma constante o programa já era. 

 

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

Hoje eu parei para ler, os comentários do programa atual, disposto aqui no Clube do Hardware.

E simplesmente me deixei levar, por essa onda de agradecimentos, de coração, muito obrigado, as dicas e conselhos que recebo desta comunidade, neste tópico me fortalecem na programação em C, de coração obrigado.

No caso do programa acima  @Midori o uso do prototipo entrada tinha como ideia justamente adicionar um alemento vazio para o vetor de caracteres, para que quando o programa passase para a area de uso no execv o próprio recebesse somente os comando rs

E@arfneto a minha duvida em si era referente a como eu iria fazer a entrada dos comando no execv, foi bem simples no fim das contas, utilizei ponteiro de ponteiros e salvou noites de sono, kk, estarei compartilhando aqui uma solução que eu tive como luz a questão do execv.

Novamente muitíssimo obrigado.

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

void escanfe(char * srt){
    fgets(srt, sizeof(srt), stdin);
    if (srt[strlen(srt) - 1] == '\n') {srt[strlen(srt) - 1] = '\0';}
}

int main()
{
    char comando[20];
    char argumento[20][20];
    int i = 1;
    char **arg = malloc(10 * sizeof(char *));

    printf("Entre com o comando: ");
    escanfe(comando);
    
    arg[0] = comando;
    printf("Entre com o argumento %i: ", i);
    escanfe(argumento[i]);
    while (strlen(argumento[i]) != 0){
        arg[i] = argumento[i];
        i ++;
        printf("Entre com o argumento %i: ", i);
        escanfe(argumento[i]);
    }

    arg[i] = NULL;

    execvp(arg[0], arg);
}

  

Link para o comentário
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...

Ebook grátis: Aprenda a ler resistores e capacitores!

EBOOK GRÁTIS!

CLIQUE AQUI E BAIXE AGORA MESMO!