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

时间:2018-11-11 01:22:50

标签: c unix command-line pipe argc

我有一个用3个.c文件编译的C程序。本质上,该程序根据我在主菜单中定义的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才能正确接受来自stdin的输入。在主输入中使用带有(5,5)作为输入的xargs:

./a.out | xargs ./test

导致:

argc value is: 9

因此,我有两个问题。有没有一种方法不需要xargs,并且可以在c文件本身中完成?知道测试文件的输入后,为什么argc == 9?程序如何分离出该格式的字符串并确定要放入数组的内容?

2 个答案:

答案 0 :(得分:2)

这会很长,所以请抓住您最喜欢的饮料。休息后不要只跳到答案。

首先,检查提供给程序的命令行参数,例如 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都具有命令行实用程序或内置的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将输入中的每个标记(由空格分隔)转换为命令行参数。如果我们告诉xargs通过使用-d SEPARATOR选项(以换行符作为分隔符)将每个输入行变成一个单独的参数:

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"

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

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字符\0,在C语言中它分隔字符串。如果我们在xargs的输入中使用该参数,则即使带有换行符的字符串等也将正确地拆分为参数。例如:

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

将输出

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

如果以一种可靠的方式处理文件名或路径,这正是人们想要的。


  

是否有一种不需要xargs的方法并且可以在c文件本身中完成?

当然:只需阅读标准输入即可。几乎可以肯定,xargs在所有Unixy系统上都是用C编写的。

  

[xargs]如何分离出该格式的字符串并决定将哪些内容放入数组中?

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

完整的答案是,查看源代码。 GNU xargs(findutils的一部分)的来源是here,而FreeBSD版本的来源是here

代码答案取决于您是否可以使用POSIX.1,特别是getline()getdelim()。如果您有一个单字符分隔符(可以是任何单字节字符,甚至是nul),则可以使用getdelim()作为单独的字符串从输入中访问每个“参数”。这是我要做的,但不是,而是解决方案。 (现在,如果您拥有一台维护良好的Unixy计算机,则几乎可以肯定的是,其C库内置了POSIX.1支持。)

  

为什么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 philosophyKISS principle意味着这些工具通常也很容易编写。首先,只需将它们写得很好,这样您就可以信任它们的结果,而不必经常重写它们。

答案 1 :(得分:0)

之所以发生这种情况,是因为xargs接受整个输入(所有行,而不仅仅是一行),并用空格字符将其分割。因此,您的测试代码得到的参数是(您可以自己打印以进行调试):

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

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