【问题标题】:How to write a program for input and output redirection wc < f1.txt > f2.txt in c如何在c中编写一个输入输出重定向的程序wc < f1.txt > f2.txt
【发布时间】:2021-06-30 18:05:19
【问题描述】:

我正在尝试这样的事情,但被卡住了,不知道往哪个方向前进。我什至尝试使用 fork() 然后将任务分别分配给孩子和父母,但在这种情况下重定向无法按预期工作。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    execlp("cat","<","f1.txt",">","f2.txt",NULL);
    return 0;
}

【问题讨论】:

  • 如果你在做本质上是 bash 的事情,我可以问你为什么要使用 C 吗?我也认为您正在寻找的是fprintf() 和一些读取文件的方法。抱歉,我对 C 不是很熟悉。但如果您只需要 cat &lt; f1.txt &gt; f2.txt(或 cat f1.txt &gt; f2.txt),请考虑使用 bash 脚本,如果这对您有用
  • 使用 C 的主要原因是我想通过 execlp 等函数在 C 中实现重定向“”。

标签: c linux terminal operating-system exec


【解决方案1】:

在 Linux 和其他 POSIXy 操作系统中,标准输入对应文件描述符 0 (STDIN_FILENO),标准输出对应文件描述符 1 (STDOUT_FILENO),标准错误对应文件描述符 2 (STDERR_FILENO)。

标准文件句柄 stdinstdoutstderr 是标准 C 抽象,在 Linux 中是在这些文件描述符之上实现的。

要重定向标准输入、输出或错误,首先您需要获取一个打开的文件描述符到您想要重定向的任何内容。对于文件,您可以通过open() 函数执行此操作,该函数返回文件描述符编号。然后,您使用dup2() 函数将其复制(复制)到您想要的描述符。

考虑以下example.c

// SPDX-License-Identifier: CC0-1.0
#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

/* Duplicate oldfd to newfd, and close oldfd.
   In error cases, tries to close both descriptors.
   Returns 0 if success, error code (with errno set) otherwise.
*/
static inline int move_descriptor(int oldfd, int newfd)
{
    if (oldfd == -1 || newfd == -1) {
        if (oldfd != -1)
            close(oldfd);
        if (newfd != -1)
            close(newfd);
        return errno = EBADF;
    }

    if (oldfd == newfd)
        return 0;

    if (dup2(oldfd, newfd) == -1) {
        const int  saved_errno = errno;
        close(oldfd);
        close(newfd);
        return errno = saved_errno;
    }

    if (close(oldfd) == -1) {
        const int  saved_errno = errno;
        close(newfd);
        return errno = saved_errno;
    }

    return 0;
}


/* Write a message to standard error, keeping errno unchanged.
   This is async-signal safe.
   Returns 0 if success, error code otherwise.
*/
static inline int wrerr(const char *msg)
{
    const char *end = (msg) ? msg + strlen(msg) : msg;

    if (end == msg)
        return 0;

    const int  saved_errno = errno;

    while (msg < end) {
        ssize_t  n = write(STDERR_FILENO, msg, (size_t)(end - msg));
        if (n > 0) {
            msg += n;
        } else
        if (n != -1) {
            errno = saved_errno;
            return EIO;
        } else
        if (errno != EINTR) {
            const int  retval = errno;
            errno = saved_errno;
            return retval;
        }
    }

    errno = saved_errno;
    return 0;
}

static inline void errormessage(const char *name, const char *cause)
{
    wrerr(name);
    wrerr(": ");
    wrerr(cause);
    wrerr(".\n");
}

int main(int argc, char *argv[])
{
    int  fd;

    if (argc < 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        const char *arg0 = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
        wrerr("\n");
        wrerr("Usage: "); wrerr(arg0); wrerr(" [ -h | --help ]\n");
        wrerr("       "); wrerr(arg0); wrerr(" INPUT OUTPUT COMMAND [ ARGUMENTS ... ]\n");
        wrerr("\n");
        return EXIT_FAILURE;
    }

    fd = open(argv[1], O_RDONLY | O_NOCTTY);
    if (fd == -1) {
        errormessage(argv[1], strerror(errno));
        return EXIT_FAILURE;
    }
    if (move_descriptor(fd, STDIN_FILENO)) {
        errormessage(argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    fd = open(argv[2], O_WRONLY | O_CREAT, 0666);
    if (fd == -1) {
        errormessage(argv[2], strerror(errno));
        return EXIT_FAILURE;
    }
    if (move_descriptor(fd, STDOUT_FILENO)) {
        errormessage(argv[2], strerror(errno));
        return EXIT_FAILURE;
    }

    if (strchr(argv[3], '/'))
        execv(argv[3], (char *const *)(argv + 3));
    else
        execvp(argv[3], (char *const *)(argv + 3));

    errormessage(argv[3], strerror(errno));
    return EXIT_FAILURE;
}

move_descriptor() 只是 dup2()close() 的包装。我包含它是为了展示如何移动描述符(复制和关闭旧的)安全;有足够的错误检查。

wrerr(msg) 函数类似于fputs(msg, stderr),只是它直接使用文件描述符接口(@98​​7654323@),完全绕过了 C 标准错误流抽象。它也是async-signal safe*,这意味着您可以在信号处理程序中使用它。

*:从技术上讲,人们可能会争论 strlen() 是否是异步信号安全的。在使用 glibc、newlibc 或 avr-libc 的 Linux 中,它是。

许多 Linux/Unix/POSIXy 错误消息使用格式 "filename: Error message." 由于 wrerr() 函数只接受一个参数,我包含了 errormessage(filename, message) 帮助函数来打印此类错误消息。这种将常用任务拆分为辅助函数的做法使代码更易于阅读,也更易于维护。

程序本身至少需要四个命令行参数。 (第一个参数argv[0]是命令本身。第一个参数argv[1]。例如,如果你编译并运行它为./example arg1 arg2 arg3,那么argv[0]=="./example",@987654351 @,argv[2]=="arg2",和argv[3]=="arg3"。在Linux和POSIXy系统中,argv[argc] == NULL,所以我们可以直接在execv()execvp()中使用argv数组和相关函数。

如果命令行参数少于四个,或者如果argv[1] 匹配"-h""--h",我们打印usage 并退出。

否则,第一个参数 (argv[1]) 命名我们重定向输入的文件(并且它必须存在),第二个参数 (argv[2]) 命名我们将输出重定向到的文件(如果它,我们将创建它还不存在)。

O_NOCTTY 标志起初可能看起来令人困惑,但我将其包含在内,因为它在重定向来自类文件对象的输入时非常常见。这基本上意味着即使路径名指的是终端,打开它时也不要做任何与终端和会话相关的魔法:“如果它是一个终端,并且我们碰巧没有控制终端,请不要不要让它成为我们的控制终端”。 (它只影响在新会话中运行的程序(通过setsid)或通过 cron、udev 等服务运行的程序,因为您通常从终端运行的程序将该终端作为其控制终端。关于终端和会话的主要内容就是如果控制终端关闭,每个以该终端作为控制终端的进程都会收到挂断(SIGHUP)信号。)

当打开我们将输出重定向到的文件时,我们使用O_CREAT 标志,并添加一个额外的参数,文件访问模式。前导零表示0666是八进制常数,即base-8,指十进制值6·82 + 6·81 + 6·8 0 = 438。这是您看到最常用的标准值。它由当前的 umask(通过运行umask 可以在 shell 中看到其值)修改(由内核)。它是用八进制写的,因为右起第三个数字指定所有者(用户)权限,右起第二个数字指定组权限,最右边是其他所有人的权限; 1 是读访问,2 是写访问,4 是执行(对于文件)或通过/工作(对于目录)。 (每个文件在 Linux 中都有一个所有者(用户)和组,就像在所有 Unix 和 POSIXy 系统中一样。)

只要open() 标志包含O_CREAT,就必须提供额外的访问模式值。它几乎总是0666,除了一些你想使用更严格的值的极少数情况;但一般规则是使用0666,并让用户通过修改umask 使其更具限制性:无论如何,这几乎是所有实用程序所做的。

第三个命令行参数(第四个参数argv[3])包含我们将运行的可执行文件的名称或路径。如果它包含斜杠 (/),我们假设它是路径名引用,并使用 execv()。如果它不包含斜杠,我们假设它是一个名称,并使用execvp(),它使用PATH 环境变量来查找具有该名称的可执行文件。

如果exec成功,则用新程序替换进程;执行在 exec 处结束。

如果 exec 由于任何原因失败,execv() 和 execvp() 都会设置 errno。打印命令(不带任何参数,如果有的话)和错误消息是有意义的,在这种情况下以失败退出。

【讨论】:

    【解决方案2】:

    AFAIK,重定向是一个 shell 功能。

    我看不出你想做什么。但是要实现您正在尝试的目标,您需要运行

    sh -c 'cat < f1.txt > f2.txt'
    

    执行格式。 (我对C中的exec格式不熟悉)

    您可以使用bash 代替sh-c 标志接受一个字符串参数——称为命令字符串,并执行它。


    我对 exec 格式的猜测是:

    execlp( "sh", "-c", " 'cat < f1.txt > f2.txt' ", NULL);
    

    请注意,函数的第三个参数包含在两组引号" '...' " 中。这是一个要求。否则 shell 的空间分割会破坏它并且只传递 cat 作为命令字符串。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多