我如何在C语言中编写一个过滤程序?

9

由于UNIX具有所有这些精彩的过滤器程序(比如 grepsedtr等等),那么用标准C编写这些程序最简单的方法是什么?

所谓过滤器,就是读取标准输入数据,对其进行操作后将其写入标准输出流的程序。这在构建一系列命令的管道时非常有用,每个命令都对数据进行额外的操作,例如:

grep xyzzy input.file | tr '[A-Z]' '[a-z]' | sed 's/plugh/PLUGH/g'

每个|管道符将前一个命令的标准输出连接到下一个命令的标准输入,因此形成了管道的隐喻。

假设我需要一个将所有大写字符转换为小写字符的命令。是的,我意识到这个“特定”的问题可以通过UNIX解决:

tr '[A-Z]' '[a-z]'

但那只是一个例子。我实际需要的是最简单的标准C源代码来实现这种过滤器。

2
我有什么遗漏吗?4月1日已经过去几天了... - Michael Burr
1
对于你提到的所有工具,你都可以轻松找到源代码。为什么不看一下它们是如何实现的呢? - Sander De Dycker
2
@Michael,不是的,这个问题是在我回答另一个问题时想到的,发现没有覆盖到这个问题。根据指南(SO适用于所有级别的用户和回答自己的问题),我想我应该把它放上去。显然,_我_知道如何做,但我不会为了声望而回答,而是让其他人回答(除非他们在几天内没有回答,那么我就会尽情地回答 :-) - paxdiablo
2
@Sander,我不需要查看源代码,我知道如何做。然而,由于这个问题不是在SO上,所以我把它放在这里。我的想法是让SO成为涉及编程的问题的首选地点。 - paxdiablo
3
我认为我的观点仍然是有效的。外部代码非常好,经过试验和测试,并且完整,相对于你可能在这里获取的快速、具体、有限的示例。编写标准 C 中的这些工具最简单的方法是什么?看看其他人已经如何做到了这一点。 - Sander De Dycker
显示剩余3条评论
4个回答

6
您可以像 @hroptatyr 描述的那样使用getline,但您也可以做得更简单:
#include <stdio.h>
#include <ctype.h>
int main(void) {
    int c;
    while ((c = getchar()) != EOF)
        putchar(tolower(c));
    return 0;
}

3
我认为有人应该真正解释一下关键点:过滤器是一种读取stdin数据的程序,对数据进行某些操作(包括不进行任何操作,比如cat),并将转换后的数据写入stdout。当然,许多过滤器会做更多的事情,比如如果通过选项指定,则读/写除stdin/stdout之外的文件。但我认为这就是过滤器的核心概念。 - Michael Burr

4
一个“过滤器”程序就是一个从标准输入流(stdin)读取数据并写入标准输出流(stdout)的程序。在写入读取的数据之前,通常会以某种方式进行转换(如果你不进行任何转换或过滤,则基本上编写了一个cat程序,它只打印出给定的内容)。过滤器程序的强大之处在于它们不指定其输入来自何处或输出将去向何方。相反,由调用程序提供输入/输出通道。

过滤器程序的核心可能看起来像这样(您可以将其用作自己的过滤器程序的模板):

#include <stdio.h>

int filter( FILE *input, FILE *output );

int main( void )
{
    const int retval = filter( stdin, stdout );
    fflush( stdout );
    return retval;
}

就是这样。实际的工作是由一个 filter 函数完成的,该函数执行所需的转换。例如,这里有一个简单的程序,它从输入文件中读取字符,将它们转换为小写字母,然后将它们打印到输出文件中:

#include <stdio.h>
#include <ctype.h> /* for tolower */

int filter( FILE *input, FILE *output )
{
    while ( !feof( input ) ) {
        if ( ferror( input ) ) {
            return 1;
        }
        fputc( tolower( fgetc( input ) ), output );
    }
    return 0;
}

int main( void )
{
    const int retval = filter( stdin, stdout );
    fflush( stdout );
    return retval;
}

如果您编译并运行此程序,它将只是静静地等待从标准输入文件 stdin 读取数据。该文件通常绑定到控制台,这意味着您必须手动输入一些数据。但是,命令 shell 实现了一种名为管道的特性,允许您将一个命令的输出导入到另一个命令的输入中。这使得可以将多个程序组合成一个 pipeline 来形成强大的命令。
以下是如何使用我们的过滤程序(假设您将结果二进制文件称为 lower):
$ echo Hello | lower
hello
$

由于我们的过滤程序没有定义要读取的数据来自哪里,因此我们可以将其与所有在标准输出上产生输出的程序结合使用。例如,以下是如何将整个文件转换为小写(在 Windows 机器上可以使用 type):
$ cat myfile.txt
Hello, World!
This is a simple test.

$ cat myfile.txt | lower
hello, world!
this is a simple test.

$

1
fflush(stdout); 似乎是无用的: "如果 main 函数返回到其原始调用者,[...] 在程序终止之前,所有打开的文件都将关闭(因此所有输出流都被刷新)." (ISO/IEC 9899:1999, 7.9.13, §5). - undur_gongor
@undur_gongor:说实话,我同意;在我的第一个版本中,我没有使用fflush调用。然而,在尝试在Windows XP上运行程序时,我注意到我没有看到任何输出。显式刷新stdout有所帮助-我没有进一步检查(我在Windows上对C API的经验不是很好)。 - Frerich Raabe
那么你的C实现有问题,或者程序在main函数之后但在实际刷新stdout缓冲区之前被终止。通常,如果stdout是终端,则它是行缓冲的,因此您不会看到输出,直到输出换行符(或刷新/退出)。 - Peter Cordes

3

伪代码示例:

do
  line = read(stdin);
  filter(line);
  print(line);
until no_more_lines

在实际代码中:

char *line = NULL;
size_t len = 0U;
ssize_t n;

while ((n = getline(&line, &len, stdin)) >= 0) {
        /* LINE is of length N, filter it */
        filter(line, n);
        /* print it */
        fputs(line, stdout);
}
free(line);

filter() 的使用方法如下:

static void filter(char *line, size_t length)
{
        while ((*line++ = tolower(*line)));
}

编辑:不要忘记定义_POSIX_C_SOURCE >= 200809L_XOPEN_SOURCE >= 700。 并且不要忘记包含stdio.h以使用getline(),以及ctype.h以使用tolower()


@paxdiablo 这可能是在其他地方定义的函数。 - glglgl
1
标准C有函数可以做到这一点,但它们并不是非常安全:[...] 因此,GNU库提供了非标准的getline函数。http://www.gnu.org/software/libc/manual/html_node/Line-Input.html - BoBTFish
我更新了答案,指导人们在哪里/如何获取getline()函数。 - hroptatyr

-5
L1:
 mov dx,081
 mov cx,1
 mov bx,0
 mov ax,03f00
 int 021
 cmp ax,0
 je L2
 cmp b[081],'a'
 jb L3
 cmp b[081],'z'
 ja L3
 sub b[081],020
L3:
 mov dx,081
 mov cx,1
 mov bx,1
 mov ax,04000
 int 021
 jmp L1
L2:
 mov ax,04c00
 int 021

; Example in A86 Assembler see eji.com for A86/D86 

1
你能更详细地解释一下吗? - Matthew R.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接