如何知道文件是否以换行符结尾

How to know if the file end with a new line character or not

我正在尝试在具有以下形状的文件末尾输入一行 "1 :1 :1 :1" ,因此在某些时候文件的末尾可能有一个换行符它,为了执行我必须处理的操作,所以我想出了以下解决方案: 转到文件末尾并向后移动 1 个字符(我猜是 Linux OS 中换行符的长度),读取该字符,如果它不是换行符插入一个,然后插入整行,否则去插入该行,这是 C 上该解决方案的翻译:

int insert_element(char filename[]){
    elements *elem;
    FILE *p,*test;
    size_t size = 0;
    char *buff=NULL;
    char c='\n';
    if((p = fopen(filename,"a"))!=NULL){
        if(test = fopen(filename,"a")){
            fseek(test,-1,SEEK_END );
            c= getc(test);
            if(c!='\n'){
                fprintf(test,"\n");
            }
        }
        fclose(test);
        p = fopen(filename,"a");
        fseek(p,0,SEEK_END);
        elem=(elements *)malloc(sizeof(elements));
        fflush(stdin);
        printf("\ninput the ID\n");
        scanf("%d",&elem->id);
        printf("input the adress \n");
        scanf("%s",elem->adr);
        printf("innput the type \n");
        scanf("%s",elem->type);
        printf("intput the mark \n");
        scanf("%s",elem->mark);
        fprintf(p,"%d :%s :%s :%s",elem->id,elem->adr,elem->type,elem->mark);
        free(elem);
        fflush(stdin);
        fclose(p);
   return 1;
   }else{
       printf("\nRrror while opening the file !\n");
       return 0;
   }
}

你可能会注意到整个程序取决于换行符的长度(1 个字符“\n”)所以我想知道是否有最佳方法,换句话说,对所有 OS的

看来你已经了解了附加到文件的基础知识,所以我们只需要弄清楚文件是否已经以换行符结尾。

在理想情况下,您会跳到文件末尾,备份一个字符,读取该字符,然后查看它是否与 '\n' 匹配。像这样:

FILE *f = fopen(filename, "r");
fseek(f, -1, SEEK_END);  /* this is a problem */
int c = fgetc(f);
fclose(f);
if (c != '\n') {
  /* we need to append a newline before the new content */
}

虽然这可能适用于 Posix 系统,但不适用于许多其他系统。问题的根源在于系统分隔文本文件中 and/or 终止行的许多不同方式。在 C 和 C++ 中,'\n' 是一个特殊值,它告诉 文本模式 输出例程执行插入换行符所需的任何操作。同样,文本模式输入例程会将每个换行符转换为 '\n',因为它 return 是读取的数据。

在 Posix 系统上(例如 Linux),换行符由换行符 (LF) 表示,它在 UTF-8 编码文本中占据一个字节。所以编译器只是将 '\n' 定义为换行符,然后输入和输出例程不必在文本模式下做任何特殊的事情。

在一些较旧的系统(如旧的 MacOS 和 Amiga)上,换行符可能由回车 return 字符 (CR) 表示。许多 IBM 大型机使用称为 EBCDIC 的不同字符编码,它们没有 LF 或 CR 的直接映射,但它们有一个称为下一行 (NL) 的特殊控制字符。甚至有些系统(如 VMS、IIRC)不使用文本文件的流模型,而是使用可变长度记录来表示每一行,因此换行符本身是隐式的,而不是用特定的控制字符标记。

其中大部分是您在现代系统中不会遇到的挑战。 Unicode 添加了更多的换行约定,但很少有软件以通用方式支持它们。

剩下的主要换行约定是组合 CR+LF。使 CR+LF 具有挑战性的是它是两个控制字符,但 C i/o 函数必须使它们在程序员看来就像是单个字符 '\n'。这对于输入或输出流文本来说没什么大不了的。但这使得文件中的 seeking 难以定义。这让我们回到有问题的行:

fseek(f, -1, SEEK_END);

在换行符由两个字符序列(如 LF+CR)指示的系统中,从末尾备份“一个字符”是什么意思?我们真的希望 i/o 系统必须扫描整个文件以便 fseek(和 ftell)弄清楚如何理解偏移量吗?

人们反对的 C 标准。 在文本模式下fseek 的偏移量参数只能是0 或由先前调用ftell 编辑的值return .因此,具有负偏移量的有问题的调用是无效的。 (在 Posix 系统上,对 fseek 的无效调用可能会起作用,但标准并不要求这样做。)

另请注意,Posix 将 LF 定义为一行 终止符 而不是 分隔符 ,因此 non-empty 不以 '\n' 结尾的文本文件应该很少见(尽管确实会发生)。

为了更便携的解决方案,我们有两个选择:

  1. 以文本模式阅读整个文件,记住您最近阅读的字符是否是'\n'

    此选项效率极低,因此除非您只是偶尔或仅对短文件执行此操作,否则我们可以排除这种情况。

  2. binary方式打开文件,从末尾向后查找几个字节,然后读到末尾,记住最后读的是不是是一个有效的换行序列。

    如果我们的 fseek 在以二进制模式打开文件时不支持 SEEK_END 来源,这可能是个问题。是的,C 标准说支持是可选的。但是,大多数实现都支持它,因此我们将保持此选项打开。

    由于文件将以二进制模式读取,输入例程不会将平台的换行符序列转换为 '\n'。我们需要一个状态机来检测超过一个字节长的换行序列。

    让我们做一个简单的假设,即换行符是 LF 或 CR+LF。对于后一种情况,我们不关心CR,所以我们可以简单地从最后备份一个byte,然后测试它是否是LF。

    哦,我们必须弄清楚如何处理空文件。

bool NeedsLineBreak(const char *filename) {
  const int LINE_FEED = '\x0A';
  FILE *f = fopen(filename, "rb");  /* binary mode */
  if (f == NULL) return false;
  const bool empty_file = fseek(f, 0, SEEK_END) == 0 && ftell(f) == 0;
  const bool result = !empty_file ||
    (fseek(f, -1, SEEK_END) == 0 && fgetc(f) == LINE_FEED);
  fclose(f);
  return result;
}