如何从终端输入(Linux)运行程序?

5

虽然我认为我已经理解了C语言中fork(), exec(), wait()pid的工作原理,但我还没有找到在程序内部运行个人程序的方法。

以下是我的代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> /* for fork() */
#include<sys/types.h> /* for pid_t */
#include<sys/wait.h> /* fpr wait() */

int main(int argc, char* argv[])
{
    char  fileName[255];
    pid_t pid;

    switch (pid = fork()) {
    case -1: //Did not fork properly
        perror("fork");
        break;

    case 0: //child
        execv(fileName[0],fileName);

        puts("Oh my. If this prints, execv() must have failed");
        exit(EXIT_FAILURE);
        break;
    default: //parent
        //Infinite Loop
        while (1) {
            printf(" %s > ", argv[0]);
            scanf("%s", fileName); // gets filename
            if (fileName[0] == '\0') continue;
            printf("\n Entered file: %s",fileName); // prints the fileName
            waitpid(pid,0,0); /* wait for child to exit() */
            break;
        }
    }

    return 0;
}

我的问题如下:

  1. I want to take a string as an input and I want to limit its scope to 255 characters. Is char fileName[255] and then scanf("%s", fileName); the way to go? Should I use getLine() or some other function instead?

  2. Let's say that the input is taken correctly. How do I execute say an existing hello world program. Will the input be stored in *argv[] ? I found out that in a different program I could use

    static char *argv[] = { "echo", "Foo is my name." , NULL };
    execv("/bin/echo", argv);
    

    in order to echo "Foo is my name.". Can I do something similar with a helloWorld program?

2个回答

2
您正在将单个字符作为命令名称传递,并将名称字符串作为参数列表的开头 - 就好像 execv() 的原型是 int execv(char cmd, char *args) 一样。

实际原型是:int execv(char *cmd, char **args),因此您需要:

char *args[2] = { fileName, 0 };

execv(args[0], args);

我假设您在某处将fileName设置为有意义的值,但该部分未显示。例如,它可能是"./local_program"。它将被视为可执行文件的路径名。
如果您想读取名称,则可以使用fgets()getline(),但需要删除换行符:
if (fgets(fileName, sizeof(fileName), stdin) != 0)
{
    fileName[strcspn(fileName, "\n")] = '\0';
    …as before…
}

或者:

char *fileName = 0;
size_t length = 0;
if (getline(&fileName, &length, stdin) != -1)  /* Not EOF! */
{
    fileName[strcspn(fileName, "\n")] = '\0';
    …as before…
}
free(fileName);

使用strcspn()可以避免特殊情况,比如命令行过长而fileName中没有换行符。请注意,这并不尝试在空格或其他特殊字符处将输入行分割为命令名称和参数。这是shell实现的下一个级别:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    char fileName[256];
    if (fgets(fileName, sizeof(fileName), stdin) != 0)
    {
        fileName[strcspn(fileName, "\n")] = '\0';
        char *args[129];
        char **argv = args;
        char *cmd = fileName;
        const char *whisp = " \t\f\r\b\n";
        /* Ugh — strtok()?  OK while only handling white space separators */
        char *token;
        while ((token = strtok(cmd, whisp)) != 0)
        {
            *argv++ = token;
            cmd = 0;
        }
        *argv = 0;

        execv(args[0], args);
        fprintf(stderr, "Oops!\n");
    }
    return 1;
}

我不需要检查args数组的溢出,因为256个输入字符(减去终止空值)无法分割产生超过128个单字符参数,每个参数用一个空格字符隔开。使用strtok()只是一个临时的权宜之计。一旦你需要处理真实的Shell语法(管道、I/O重定向、带有空格的引号字符串等),strtok()就显得非常不适用。(strtok()它在任何库函数中都是错误的工具。如果必须在库代码中使用类似strtok()的函数,请在Unix上使用POSIX strtok_r()或在Windows上使用Microsoft的strtok_s()。)

1
感谢您快速而彻底的回答。我唯一无法完全理解的是您问题中假设我将fileName设置为一个值的部分。您是否指初始化?如果是这样,那我没有这么做。我需要吗?在我的想法中,当程序运行时,它会提示用户,用户会输入类似“./helloWorld”或“/bin/./helloWorld”的内容。这将是fileName的值。 - Joseph Athan
1
我所说的“将fileName设置为一个值”是指输入名称。按照你的写法,你使用了一个未初始化的字符字符串作为命令,这样做可能不会很好地工作。只要你以某种方式将fileName设置为合适的值,就应该没问题。./helloWorld/bin/./helloWorld都可以工作,尽管./是不必要的(execv()不受$PATH影响,如果你只指定helloWorld,它将被视为./helloWorld)。/bin/./helloWorld有点靠不住;/./可以简化为/,而且你不应该把软件放在/bin目录下。 - Jonathan Leffler

0

目前情况是,你需要修复代码中的编译错误:

g++ -std=c++11 -g -Wall -Wextra -Wwrite-strings     36501711.cpp   -o 36501711
36501711.cpp: In function ‘int main(int, char**)’:
36501711.cpp:18:25: error: invalid conversion from ‘char’ to ‘const char*’ [-fpermissive]
         execv(fileName[0],fileName);
                         ^
36501711.cpp:18:35: error: cannot convert ‘char*’ to ‘char* const*’ for argument ‘2’ to ‘int execv(const char*, char* const*)’
         execv(fileName[0],fileName);
                                   ^
36501711.cpp: At global scope:
36501711.cpp:7:5: warning: unused parameter ‘argc’ [-Wunused-parameter]
 int main(int argc, char* argv[])
     ^

关于未使用的argc的警告是无害的,但错误是真实的,需要修复。

您需要在分叉之前初始化fileName,否则子进程将没有任何有效的数据:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> /* for fork() */
#include<sys/types.h> /* for pid_t */
#include<sys/wait.h> /* fpr wait() */

int main(int argc, char* argv[])
{
    char  fileName[255];
    pid_t pid;

    //Infinite Loop
    while (1) {
        printf(" %s > ", argv[0]);
        scanf("%s", fileName);
        if (fileName[0] == '\0')
            break;
        printf("\n Entered file: %s\n", fileName);

        pid = fork();
        switch (pid) {
        case -1: //Did not fork properly
            perror("fork");
            break;

        case 0: //child
            execl(fileName, fileName, 0);
            perror("exec");
            exit(EXIT_FAILURE);
            break;

        default: //parent
            waitpid(pid,0,0); /* wait for child to exit() */
            break;
        }
    }

    return EXIT_SUCCESS;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接