为什么我的程序不能正确地接受另一个程序的管道输出?

Why won't my program accept the piped output of another program properly?

我有一个用 3 个 .c 文件编译的 C 程序。本质上,该程序根据我在 main 中定义的 x 和 y 大小输入将正方形打印到标准输出。相关代码如下:

void    rush(int x, int y);

int     main(void)
{
    rush(3, 3);
    return (0);
}

运行 main 的可执行文件如下所示:

./a.out

给出以下内容:

o-o
| |
o-o

并将传递给 rush 函数的参数更改为 (5, 5) 会产生以下结果:

o---o
|   |
|   |
|   |
o---o

你懂的。每行由 \n 分隔,允许函数打印正确的下一行。我有另一个测试程序,它是一个简单的编译主程序,它只打印 ARGC 的值,因为我想测试这样的输入会给出什么管道的行为。第二个主程序是这样的:

#include <stdio.h>

int     main(int argc, char **argv)
{
    printf("argc value is: %d\n", argc);
    return (0);
}

运行 以下命令:

./a.out | ./test

我得到以下输出:

argc value is: 1

最初对我来说没有意义,但后来我想起来了,因为有些命令需要 xargs 才能正确接受来自标准输入的输入。在 main 中使用带有 (5, 5) 作为输入的 xargs:

./a.out | xargs ./test

结果:

argc value is: 9

所以我有两个问题。有没有办法在不需要 xargs 的情况下做到这一点,并且可以在 c 文件中完成?并且知道测试文件的输入,为什么argc == 9?程序如何分离出该格式的字符串并决定将什么放入数组?

发生这种情况是因为 xargs 获取整个输入(所有行,而不仅仅是一行)并将其拆分为 white-space 个字符。所以你的测试代码得到的参数是(你可以自己打印出来调试):

  1. ./测试
  2. o---o
  3. |
  4. |
  5. |
  6. |
  7. |
  8. |
  9. o---o

如果您打算从标准输入读取而不是解析参数,请使用 cin >> string_variable

这会很长,所以喝点你最喜欢的饮料吧。不要在休息后直接跳到答案。

首先,检查提供给程序的命令行参数,比如 args.c:

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

int main(int argc, char *argv[])
{
    int  i;
    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++)
        printf("argv[%d] = \"%s\"\n", i, argv[i]);
    return EXIT_SUCCESS;
}

使用您最喜欢的 C 编译器编译它;我使用 gcc:

gcc -Wall -O2 args.c -o args

如果你运行说

./args one two

它会输出

argc = 3
argv[0] = "./args"
argv[1] = "one"
argv[2] = "two"

所有 Unix 都有命令行实用程序或 shell 内置 printf,其工作方式与 C printf() 标准库函数非常相似。例如我们可以运行

printf 'Hello, world!\nSecond line\nThird line\n'

我们会看到

Hello, world!
Second line
Third line

现在,如果我们用管道连接两者,

printf 'Hello, world!\nSecond line\nThird line\n' | ./args

我们得到

argc = 1
argv[0] = "./args"

因为 ./args 没有参数,上面 args.c 完全忽略标准输入。

xargs 实用程序命令读取输入,然后将其自己的命令行参数作为命令执行,并将读取的输入添加为附加参数。它也是高度可配置的。如果你运行

printf 'Hello, world!\nSecond line\nThird line\n' | xargs ./args

你会得到

argc = 7
argv[0] = "./args"
argv[1] = "Hello,"
argv[2] = "world!"
argv[3] = "Second"
argv[4] = "line"
argv[5] = "Third"
argv[6] = "line"

因为 xargs 将输入中的每个标记(以空格分隔)转换为命令行参数。如果我们通过使用 -d SEPARATOR 选项告诉 xargs 将每个输入行变成一个单独的参数,并以换行符作为分隔符:

printf 'Hello, world!\nSecond line\nThird line\n' | xargs -d '\n' ./args

我们得到

argc = 4
argv[0] = "./args"
argv[1] = "Hello, world!"
argv[2] = "Second line"
argv[3] = "Third line"

如果我们通过添加 -n 2 选项告诉 xargs 在每个执行的命令中最多添加两个参数,

printf 'Hello, world!\nSecond line\nThird line\n' | xargs -d '\n' -n 2 ./args

我们会得到

argc = 3
argv[0] = "./args"
argv[1] = "Hello, world!"
argv[2] = "Second line"
argc = 2
argv[0] = "./args"
argv[1] = "Third line"

此输出意味着我们的 ./args 实际上执行了两次。第一个实际上是 ./args 'Hello, world!' 'Second line',第二个是 ./args 'Third line'.

xargs 的另一个重要选项是 -r,它告诉它不要 运行 没有任何附加参数的命令:

true | xargs -r ./args

不输出任何东西,因为 xargs 看不到任何输入,并且 -r 选项告诉它如果没有额外的参数就不要 运行 我们的 args 程序。

当操作文件名或路径时,-0(破折号零)选项告诉 xargs 输入分隔符是 nul 字符,[=41=],它在 C 中分隔字符串。如果我们在 xargs 的输入中使用它,即使是带有换行符的字符串也会被正确地拆分成参数。例如:

printf 'One thing\non two lines[=25=]Second thing' | xargs -0 ./args

会输出

argc = 3
argv[0] = "./args"
argv[1] = "One thing
on two lines"
argv[2] = "Second thing"

如果以稳健的方式处理文件名或路径,这正是人们想要的。


Is there a way to do this without needing xargs and can be done in the c files themselves?

当然:只读标准输入。 xargs 几乎肯定是在所有 Unixy 系统上用 C 本身编写的。

How does [xargs] separate out a string in that format and decide what to put in the array?

简短的回答是它取决于所使用的选项,因为 xargs 是一个非常强大的小工具。

完整的答案是,查看来源。 GNU xargs(findutils 的一部分)的来源是 here, and the source for FreeBSD version is here.

代码答案取决于您是否可以使用 POSIX.1,特别是 getline()getdelim()。如果你有一个单字符分隔符(可以是任何单字节字符,甚至是 nul),你可以使用 getdelim() 从输入中获取每个 "parameter" 作为单独的字符串。这就是我要做的,但这不是 , it is a 解决方案。 (现在,如果你有一台维护好的 Unixy 计算机,几乎可以肯定它的内置 C 库中有 POSIX.1 支持。)

Why is argc == 9?

如果我们使用 printf 'o---o\n| |\n| |\n| |\no---o\n' 复制您的输入并将其通过管道传输到 xargs ./args,则输出符合预期,

argc = 9
argv[0] = "./args"
argv[1] = "o---o"
argv[2] = "|"
argv[3] = "|"
argv[4] = "|"
argv[5] = "|"
argv[6] = "|"
argv[7] = "|"
argv[8] = "o---o"

即ascii 艺术的每个部分以空格分隔,并作为命令行参数提供。如果我们将它通过管道传输到 xargs -d '\n' ./args,输出是

argc = 6
argv[0] = "./args"
argv[1] = "o---o"
argv[2] = "|   |"
argv[3] = "|   |"
argv[4] = "|   |"
argv[5] = "o---o"

如果最初的 args.c 程序是您自己编写的,您可能会通过探索自己找到问题的答案。这就是使编程如此强大的原因:您可以编写工具来帮助您理解您希望解决的问题。应用 Unix philosophy and the KISS principle 意味着这些工具通常也很容易编写。一开始就把它们写好,这样你就可以相信它们的结果,不需要经常重写它们。