如何知道文件是否以换行符结尾
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'
结尾的文本文件应该很少见(尽管确实会发生)。
为了更便携的解决方案,我们有两个选择:
以文本模式阅读整个文件,记住您最近阅读的字符是否是'\n'
。
此选项效率极低,因此除非您只是偶尔或仅对短文件执行此操作,否则我们可以排除这种情况。
以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;
}
我正在尝试在具有以下形状的文件末尾输入一行 "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'
结尾的文本文件应该很少见(尽管确实会发生)。
为了更便携的解决方案,我们有两个选择:
以文本模式阅读整个文件,记住您最近阅读的字符是否是
'\n'
。此选项效率极低,因此除非您只是偶尔或仅对短文件执行此操作,否则我们可以排除这种情况。
以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;
}