【问题标题】:Why is no output produced when executing ls?为什么执行ls时没有输出?
【发布时间】:2020-05-11 09:44:57
【问题描述】:

我正在尝试编写一个程序来执行 argv 中给出的任何内容。但是,当我实际运行 execvp 时,它没有给出任何输出,也没有产生任何错误。这是我的程序。

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

int main (int argc, char * argv[]) {
    char * args[argc];
    for (int i = 0; i < argc - 1; ++i) 
        args[i] = strdup(argv[i+1]);
    args[argc-1] = malloc(1);
    *args[argc-1] = '\0';
    printf("%s\n", args[argc-1]);
    int rc = fork();
    if (rc == 0)
        execvp(args[0], args);
}

我运行 ./exec ls 时产生的输出只是一个空行。但是,如果我运行一个将 ls 硬编码到数组中的程序,它就可以正常工作。为什么这个程序没有列出目录的内容?

【问题讨论】:

  • 如果你给出ls的完整路径?你永远不会检查调用是否成功。
  • 你为什么要重复参数——真的没有必要这样做,char args[argc]; 是一个指针不足的要求;你需要char args[argc + 1];,所以你可以设置args[argc] = NULL;。使用args[argc - 1] 和 mallocing 单个字节等会让人感到困惑。您的代码仔细打印args[argc - 1] 中的空字符串,后跟换行符,说明您看到的内容。 execvp() 失败(但您没有发现这一点)。它失败了,因为你传递的参数列表没有正确地以空值结尾——你必须有args[argc] == NULL
  • 基本调试技巧:(1)检查系统调用是否正常; (2) 在执行函数之前将参数打印到execvp() 等。 —— 请注意,我没有篇幅指出您的for (int i = 0; i &lt; argc - 1; i++) 循环忽略了您传递给程序的最后一个参数。这可能没有帮助,特别是如果你在运行它时不提供任何参数(所以argc == 1,所以你甚至不复制argv[0])。
  • 此外,尚不清楚您从fork() 中获得了什么好处——没有它,它会起作用,也可能会失败。该代码仅(间接)报告成功,因为在 main() 函数结束之前没有 return,因此它每次都隐式返回 0(又名成功)。
  • 经过@JonathanLeffler 的分析,这听起来是次要的,但您也忘记了释放使用 strdup 分配的指针。

标签: c execvp


【解决方案1】:

假设有必要复制参数并使用fork(),并在运行时观察:

./exec ls

那么您只想在对execvp() 的调用中使用索引 1 中的参数,那么您最终会得到如下代码:

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

int main (int argc, char *argv[])
{
    char *args[argc];
    for (int i = 0; i < argc - 1; ++i)
    {
        if ((args[i] = strdup(argv[i+1])) == NULL)
        {
            fprintf(stderr, "%s: failed to allocate memory for '%s'\n",
                    argv[0], argv[i+1]);
            exit(EXIT_FAILURE);
        }
    }
    args[argc-1] = NULL;

    int rc = fork();
    if (rc == 0)
    {
        execvp(args[0], args);
        fprintf(stderr, "%s: failed to execute '%s'\n", argv[0], args[0]);
        exit(EXIT_FAILURE);
    }
    else if (rc < 0)
    {
        fprintf(stderr, "%s: failed to fork\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    return 0;
}

在退出之前,父进程确实应该为子进程wait()。代码可能应该包含一个转储参数列表内容的函数。这是代码的修订版本,它不使用strdup(),尽管它仍然分叉。它使用我在 GitHub 上的 SOQ(堆栈溢出问题)存储库中提供的一些代码,作为 src/libsoq 子目录中的文件 stderr.cstderr.h。这些简化了错误处理——错误报告使用单个函数调用,而不是像以前那样使用多行代码。

#include <unistd.h>
#include <sys/wait.h>
#include "stderr.h"

int main (int argc, char *argv[])
{
    err_setarg0(argv[0]);
    if (argc <= 1)
        err_usage("cmd [arg ...]");

    int rc = fork();
    if (rc == 0)
    {
        execvp(argv[1], &argv[1]);
        err_syserr("failed to execute '%s': ", argv[1]);
    }
    else if (rc < 0)
        err_syserr("failed to fork: ");

    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
    {
        if (corpse != rc)
            err_remark("unexpected child PID %d status 0x%.4X\n", corpse, status);
    }
    if (WIFEXITED(status))
        rc = WEXITSTATUS(status);
    else if (WIFSIGNALED(status))
        rc = 128 + WTERMSIG(status);
    else
        rc = 255;   /* Something weird happened! */

    return rc;
}

当然,如果你不使用fork(),你也不需要wait()。这从根本上减少了代码:

#include <unistd.h>
#include "stderr.h"

int main (int argc, char *argv[])
{
    err_setarg0(argv[0]);
    if (argc <= 1)
        err_usage("cmd [arg ...]");
    execvp(argv[1], &argv[1]);
    err_syserr("failed to execute '%s': ", argv[1]);
}

【讨论】:

    猜你喜欢
    • 2013-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-24
    • 2017-08-19
    • 1970-01-01
    相关资源
    最近更新 更多