【问题标题】:C Program - printf overlapping with other printf charsC 程序 - printf 与其他 printf 字符重叠
【发布时间】:2017-07-18 16:37:24
【问题描述】:

我正在使用 C 语言编写一个程序,该程序将使用 Linux 系统调用打开、读取和关闭文件,并将文件的内容打印到屏幕上。命令格式为

$ mycat [-bens] f1 [f2 ...].

开关如下:

  • -b 显示每个非空行的行号,从 1 开始

  • -e 在每行末尾显示一个“$”

  • -n 显示每一行的行号

  • -s 从输出中删除所有空行(有效地使输出单间距)

问题是当我使用-b 或-n 开关时,printf 似乎与缓冲区试图从文本文件本身打印的行号“重叠”。

这是我为程序编写的代码:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>

#define BUFFERSIZE 4096

void oops(char *, char *);

int main(int ac, char *av[])
{
    int fd, numRead, curr, i, c;
    char buf[BUFFERSIZE] = {0};
    extern char *optarg; 
    extern int optind;
    int tmpS = 0;
    int tmpB = 0;
    int bFlag = 0;
    int eFlag = 0;
    int nFlag = 0;
    int sFlag = 0;
    int bLineNum = 1;
    int nLineNum = 1;

    /* Flag processing in argument list */
    while( (c = getopt(ac, av, "bens")) != -1)
    {
        switch(c)
        {
            case 'b':
                bFlag = 1;
                break;
            case 'e':
                eFlag = 1;
                break;
            case 'n':
                nFlag = 1;
                break;
            case 's':
                sFlag = 1;
                break;
            default:
                exit(EXIT_FAILURE);
        }
    }

    /* Scan through each argument after flag */
    for(i = optind; i < ac; i++)
    {
        /* Error handling when opening each file */
        if((fd = open(av[i], O_RDONLY)) == -1)
            oops("Cannot open ", av[i]);

        /* Read from file to buffer, until end is reached */
        while( (numRead = read(fd, buf, BUFFERSIZE)) > 0)
        {

            /* Once buffer is filled, process each address in buffer */
            for(curr = 0; curr < BUFFERSIZE; curr++)
            {
            /* sFlag squeezes output, eliminating blank lines */
            if(sFlag && buf[curr] == '\n')
            {
                tmpS = curr + 1;

                while(buf[tmpS] != '\r')
                {
                    if(isspace(buf[tmpS]))
                        tmpS++;
                    else
                        break;
                }

                curr = tmpS + 1;
            }

            /* nFlag numbers each line, starting from 1 */
            if(nFlag && buf[curr] == '\n')
                printf("%d ", nLineNum++);

            /* eFlag puts a '$' at the end of every line */
            if(eFlag && buf[curr] == '\r')
                printf(" $");

            /* bFlag numbers every non-blank line, starting from 1 */
            if(bFlag && buf[curr] == '\n')
            {
                tmpB = curr + 1;

                if(isEmptyLine(buf, tmpB))
                    printf("%d ", bLineNum++);

            }

            /* Print the current character in the buffer address */
                printf("%c", buf[curr]);
            }
        }

        if(numRead == -1)
            oops("Read error from ", av[i]);
    }

    return 0;
}

void oops(char *s1, char *s2)
{
    fprintf(stderr, "Error: %s ", s1);
    perror(s2);
    exit(1);
}

int isEmptyLine(char *buf, int tmp)
{
    while(buf[tmp] != '\n')
    {
        if(!isspace(buf[tmp]))
            return 0;
        tmp++;
    }
    return 1;
}

示例输入(file1.txt):

An excerpt from LEARNING DOS FOR THE COMPLETE NOVICE, by Steven Woas, copyright 1993.  

1.   Change to the compressed drive and then issue a CHKDSK
     command like so:

           c: <ENTER>

           chkdsk /f <ENTER>

     The /F tells DOS to fix errors. 

     Another option is to do it like so:

           dblspace /chkdsk /f  <ENTER>

     A shortcut for the DBLSPACE /CHKDSK /F command is:

           dblspace /chk /f  <ENTER>

打开并运行 -n 标志的输出:

sh-4.2$ ./main -n file1.txt    

1  excerpt from LEARNING DOS FOR THE COMPLETE NOVICE, by Steven Woas, copyright 1993.                                                                                                                                                                  
2                                                                                                                                                                                                                                                      
3    Change to the compressed drive and then issue a CHKDSK                                                                                                                                                                                            
4    command like so:                
5                                                                                                                                                                                                                                                      
6          c: <ENTER>                                                                                                                                                                                                                                  
7                                                                                                                                                                                                                                                      
8          chkdsk /f <ENTER>                                                                                                                                                                                                                           
9                                                                                                                                                                                                                                                      
10   The /F tells DOS to fix errors.                                                                                                                                                                                                                   
11                                                                                                                                                                                                                                                     
12   Another option is to do it like so:                                                                                                                                                                                                               
13                                                                                                                                                                                                                                                     
14         dblspace /chkdsk /f  <ENTER>                                                                                                                                                                                                                
15                                                                                                                                                                                                                                                     
16   A shortcut for the DBLSPACE /CHKDSK /F command is:                                                                                                                                                                                                
17                                                                                                                                                                                                                                                     
18         dblspace /chk /f  <ENTER>

-b 标志也有同样的问题,我不知道为什么。是否与\r 和\n 未正确读取有关?

【问题讨论】:

  • 我没有看到你提到的“重叠”。 应该输出如何?
  • 也许你根本不应该打印\r 字符。
  • read() 告诉您它实际读取了多少字节,并且您尽职尽责地将其记录在变量numRead 中。为什么不使用它来确定要处理的缓冲区的字节数?
  • 在带有 -n 标志的“1”行上,开头缺少“An”。在“3”行,它缺少“1”。 before 'Change 另外,在使用 -b 标志时,我遇到了类似的重叠问题。 -b 在打印行号时也会错过文本文件的第二行和最后一行,我不知道为什么。
  • 这很好,约翰 - 我确实将第二个 for() 循环更改为使用 numRead 而不是 BUFFERSIZE,但我仍然遇到打印正确行号的问题,我不能找出原因。感谢您提及有关 numRead 的内容。

标签: c file printf system-calls


【解决方案1】:

你的程序表现出你描述的具有 Windows- (DOS-) 样式行结尾 (\r\n) 的文件的不当行为,但对于具有 UNIX 样式行结尾的文件(仅\n)和具有 MacOS 类样式行结尾的文件(仅\r)。鉴于您似乎总体上假设了 Windows 样式的行尾,我将重点关注这一点。

考虑当您的程序到达行尾时会发生什么。它首先处理\r 字符,最终打印它。这会导致输出位置返回到行首(这是可能的,因为标准输出默认是行缓冲的)。然后打印行号,覆盖之前可能存在的任何内容,最后打印\n 字符,导致缓冲区被刷新,输出移动到下一行。

您可能应该将\r\n sequence 识别为行结尾,而不是尝试单独处理这些字符。这可能有点挑战性,因为您需要考虑到这对被分成两个read()s 的可能性,但这不应该太难。这也让您有机会考虑如果遇到单独的 \n 和/或单独的 \r 时该怎么办,您的程序可以比现在更优雅地处理。

【讨论】:

  • 我实际上是在处理 UNIX 文件,因为我在 Raspberry Pi 2 上编写这个程序(对不起,我应该从一开始就指定这个)。所以这应该会让事情变得更容易,因为我只需要找到 \n 字符。但是使用 -b 标志仍然会让人头疼——我不知道为什么它会给一些非空行一个数字,但其他的却不会
  • @Elliot,这对我的回答没有任何影响。此外,(1)您提供的程序仅在输入文件具有 DOS 样式的行终止时才会产生您描述的不当行为。我的结论是它有什么。您在 Pi 上运行软件的事实与此无关。 (2) 您的程序明显设计 用于具有 DOS 风格的行终止的输入(尽管它已损坏)。同样,您在 Pi 上运行它的事实与它无关。
  • 多亏了你,我想通了——结果输入文件的行终止样式取决于创建文件的系统,而不是当前输入的系统。由于我在 Windows 上创建了输入文件,因此它具有 DOS 样式的行终止符,这使得程序逻辑与我刚刚在 UNIX 上创建的不同。我在 Pi 上重新创建了输入文件,然后将我的逻辑更改为只处理 \n,这让一切变得更加容易!!处理 \r\n 行终止被证明是相当痛苦的......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-21
  • 1970-01-01
  • 2020-08-15
  • 2021-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多