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

标签 c unix command-line pipe argc

我有一个用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正确地接受来自stdin的输入。使用(5,5)作为输入的xargs:
./a.out | xargs ./test

结果是:
argc value is: 9

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

最佳答案

这会很长的,所以拿你最喜欢的饮料。休息后不要直接跳到答案上来。
首先,检查提供给程序的命令行参数,比如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,其工作方式与Cprintf()标准库函数非常相似。比如我们可以跑
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程序。
当操作文件名或路径时,选项告诉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文件中完成?
当然:只要阅读标准输入。几乎可以肯定,在所有的Unixy系统上,xargs都是用C本身编写的。
[xargs]如何将该格式的字符串分离出来,并决定在数组中放入什么?
简而言之,这取决于使用的选项,因为xargs是一个非常强大的小工具。
完整的答案是,看看来源。GNU xargs(findutils的一部分)的源代码是here,FreeBSD版本的源代码是here
代码的答案取决于您是否可以使用POSIX.1,特别是\0getline()。如果您有一个单字符分隔符(不管它是任何单字节字符,甚至是nul),您可以使用getdelim()将输入中的每个“参数”作为单独的字符串来访问。这是我想做的,但它不是,而是解决方案(现在,如果您有一台维护好的Unixy计算机,几乎可以肯定它的C库内置了POSIX.1支持。)
为什么argc==9?
如果我们使用getdelim()复制您的输入并将其管道化到printf 'o---o\n| |\n| |\n| |\no---o\n',则输出与预期一样,
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 ./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意味着这些工具通常也很容易编写。先把它们写好,这样你就可以相信它们的结果,而且不需要经常重写它们。

关于c - 为什么我的程序不能正确接受另一个程序的管道输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53245031/

相关文章:

支持创建 "wizard-like"命令行应用程序的 Java 框架/库

C 什么是 ""first[a[c] -'a' ]++;"

c++ - C/C++ 中的语法糖

c++ - 如何将 UTF-8 字符串的一部分解析为 C++ 字符串?

java - 登录 Unix 位置

linux - 如何使用 Sed 搜索将整个单词替换为文件中的字符串匹配项

c - Linux 串行 IO - 在两个线程之间拆分 Tx 和 Rx?

unix - 在列/字段中替换 AWK 中的单引号

powershell - 向批处理/powershell 添加超时

c - 在 C 中从命令行参数打印子字符串