提问人:sfactor 提问时间:4/23/2010 最后编辑:Jonathan Lefflersfactor 更新时间:11/22/2023 访问量:549624
从 fgets() 输入中删除尾随换行符
Removing trailing newline character from fgets() input
问:
我正在尝试从用户那里获取一些数据并将其发送到 gcc 中的另一个函数。代码是这样的。
printf("Enter your Name: ");
if (!(fgets(Name, sizeof Name, stdin) != NULL)) {
fprintf(stderr, "Error reading Name.\n");
exit(1);
}
但是,我发现它最后有一个换行符。因此,如果我输入它最终会发送.如何删除它并发送正确的字符串。\n
John
John\n
\n
答:
优雅的方式:
Name[strcspn(Name, "\n")] = 0;
略显丑陋的方式:
char *pos;
if ((pos=strchr(Name, '\n')) != NULL)
*pos = '\0';
else
/* input too long for buffer, flag error */
略显奇怪的方式:
strtok(Name, "\n");
请注意,如果用户输入空字符串(即仅按 Enter),则该函数不会按预期工作。它使角色完好无损。strtok
\n
当然,还有其他人。
评论
strtok()
strtok_r()
strtok
strtok
strcspn
strspn
*strchrnul(Name, '\n') = '\0';
strchr(Name, '\n') == NULL
stdin
'\n'
size_t ln = strlen(name) - 1;
if (*name && name[ln] == '\n')
name[ln] = '\0';
评论
fgets(buf, size, ....)
strlen(buf) == 0
fgets()
char
'\0'
size == 1
fgets()
NULL
buf
size_t ln = strlen(name); if (ln > 0 && name[ln-1] == '\n') name[--ln] = '\0';
ln
size_t
ssize_t
ln
strlen
strchr
strcspn
如果每行都有 '\n',则直接从 fgets 输出中删除 '\n'
line[strlen(line) - 1] = '\0';
否则:
void remove_newline_ch(char *line)
{
int new_line = strlen(line) -1;
if (line[new_line] == '\n')
line[new_line] = '\0';
}
评论
n
strncpy
""
strlen()
size_t
int
下面是从 保存的字符串中删除电位的快速方法。
它使用 ,有 2 个测试。'\n'
fgets()
strlen()
char buffer[100];
if (fgets(buffer, sizeof buffer, stdin) != NULL) {
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[--len] = '\0';
}
// `len` now represents the length of the string, shortened or not.
现在根据需要使用。buffer
len
此方法的附带优点是后续代码的值。它可以很容易地比 .裁判YMMV,但这两种方法都有效。len
strchr(Name, '\n')
buffer
,从原文中在某些情况下不会包含:
A)该行太长,因此仅在前面保存在。未读字符保留在流中。
B) 文件中的最后一行没有以 .fgets()
"\n"
buffer
char
'\n'
buffer
'\n'
如果输入在某处嵌入了 null 字符,则报告的长度将不包括位置。'\0'
strlen()
'\n'
其他一些答案的问题:
strtok(buffer, "\n");
无法删除 when 是 。从这个答案 - 在此答案之后进行修改,以警告此限制。'\n'
buffer
"\n"
在极少数情况下,当第一次读取者是 时,以下操作会失败。当输入以嵌入式 .然后成为访问内存肯定超出了 的合法范围。黑客在愚蠢地读取 UTF16 文本文件时可能会尝试或发现的东西。这是写这个答案时的答案的状态。后来,一个非 OP 对其进行了编辑,以包含类似此答案的代码。
char
fgets()
'\0'
'\0'
buffer[len -1]
buffer[SIZE_MAX]
buffer
""
size_t len = strlen(buffer); if (buffer[len - 1] == '\n') { // FAILS when len == 0 buffer[len -1] = '\0'; }
sprintf(buffer,"%s",buffer);
是未定义的行为:Ref。此外,它不会保存任何前导、分隔或尾随空格。现已删除。[由于后来的好答案而编辑]与方法相比,除了性能之外,1 衬垫没有问题。修整性能通常不是问题,因为代码正在执行 I/O - CPU 时间的黑洞。如果以下代码需要字符串的长度或具有很高的性能意识,请使用此方法。否则,这是一个很好的选择。
buffer[strcspn(buffer, "\n")] = 0;
strlen()
strlen()
strcspn()
评论
strlen(buffer)
malloc
buffer = malloc(allocation_size); length = strlen(buffer);
buffer
buffer = malloc(allocation_size_4_or_more); strcpy(buffer, "abc"); length = strlen(buffer);
也许最简单的解决方案使用我最喜欢的鲜为人知的函数之一 strcspn():
buffer[strcspn(buffer, "\n")] = 0;
如果你希望它也处理(比如说,如果流是二进制的):'\r'
buffer[strcspn(buffer, "\r\n")] = 0; // works for LF, CR, CRLF, LFCR, ...
该函数计算字符数,直到它命中 a 或 a(换句话说,它找到第一个 或 )。如果它没有命中任何东西,它就会停在(返回字符串的长度)处。'\r'
'\n'
'\r'
'\n'
'\0'
请注意,即使没有换行符,这也有效,因为停在 .在这种情况下,整行只是用 替换成 。strcspn
'\0'
'\0'
'\0'
评论
buffer
'\0'
buffer[strlen(buffer) - 1] = '\0';
strcspn()
strtok_r
strcspn
strspn
fgets()
的方法。这似乎是唯一正确的单行。strlen
更快 - 虽然没有那么简单。strcspn()
了 fgets()
输入的尾随换行符。这始终也是第一个换行符。
对于单个 '\n' 修剪,
void remove_new_line(char* string)
{
size_t length = strlen(string);
if((length > 0) && (string[length-1] == '\n'))
{
string[length-1] ='\0';
}
}
对于多个 '\n' 修剪,
void remove_multi_new_line(char* string)
{
size_t length = strlen(string);
while((length>0) && (string[length-1] == '\n'))
{
--length;
string[length] ='\0';
}
}
评论
if
&&
while
while (length > 0 && string[length-1] == '\n') { --length; string[length] = '\0'; }
size_t length = strlen(string); if (length > 0 && string[length-1] == '\n') { string[length-1] = '\0'; }
if
while
Tim Čas 一行对于通过调用 fgets 获得的字符串来说是惊人的,因为你知道它们在末尾包含一个换行符。
如果您处于不同的上下文中,并且想要处理可能包含多个换行符的字符串,则可能需要查找 strrspn。它不是 POSIX,这意味着您不会在所有 Unices 上找到它。我为自己的需要写了一个。
/* Returns the length of the segment leading to the last
characters of s in accept. */
size_t strrspn (const char *s, const char *accept)
{
const char *ch;
size_t len = strlen(s);
more:
if (len > 0) {
for (ch = accept ; *ch != 0 ; ch++) {
if (s[len - 1] == *ch) {
len--;
goto more;
}
}
}
return len;
}
对于那些在 C 中寻找 Perl chomp 等效物的人来说,我认为就是这样(chomp 只删除尾随换行符)。
line[strrspn(string, "\r\n")] = 0;
strrcspn 函数:
/* Returns the length of the segment leading to the last
character of reject in s. */
size_t strrcspn (const char *s, const char *reject)
{
const char *ch;
size_t len = strlen(s);
size_t origlen = len;
while (len > 0) {
for (ch = reject ; *ch != 0 ; ch++) {
if (s[len - 1] == *ch) {
return len;
}
}
len--;
}
return origlen;
}
评论
'\n'
""
strrcspn
\n
goto end;
return len;
goto
goto
return
goto
strchr
strrspn
strrcspn
size_t strrspn(const char *s, const char *accept) { size_t len = strlen(s); while (len > 0 && strchr(accept, s[len - 1])) { len--; } return len; }
size_t strrcspn(const char *s, const char *reject) { size_t len = strlen(s); while (len > 0 && !strchr(reject, s[len - 1])) { len--; } return len; }
下面的函数是我在 Github 上维护的字符串处理库的一部分。它从字符串中删除不需要的字符,这正是您想要的
int zstring_search_chr(const char *token,char s){
if (!token || s=='\0')
return 0;
for (;*token; token++)
if (*token == s)
return 1;
return 0;
}
char *zstring_remove_chr(char *str,const char *bad) {
char *src = str , *dst = str;
while(*src)
if(zstring_search_chr(bad,*src))
src++;
else
*dst++ = *src++; /* assign first, then incement */
*dst='\0';
return str;
}
一个示例用法可以是
Example Usage
char s[]="this is a trial string to test the function.";
char const *d=" .";
printf("%s\n",zstring_remove_chr(s,d));
Example Output
thisisatrialstringtotestthefunction
您可能想检查其他可用功能,甚至为项目做出贡献:)https://github.com/fnoyanisi/zString
评论
*
*src++;
bad
token
d
const char *
strchr
zChrSearch
*src
'\0'
zStrrmv
strchr
for(int i = 0; i < strlen(Name); i++ )
{
if(Name[i] == '\n') Name[i] = '\0';
}
你应该试一试。这段代码基本上遍历字符串,直到找到“\n”。找到后,“\n”将被空字符终止符“\0”替换
请注意,您在此行中比较的是字符而不是字符串,因此无需使用 strcmp():
if(Name[i] == '\n') Name[i] = '\0';
因为您将使用单引号而不是双引号。如果您想了解更多信息,这里有一个关于单引号与双引号的链接
评论
for(int i = 0; i < strlen(Name); i++ )
strlen(Name)
Name[]
N
O(N*N)
strlen(Name)
int i
size_t i
for(size_t i = 0; i < Name[i]; i++ )
for (size_t i = 0; Name[i]; i++) { if (Name[i] == '\n') { Name[i] = '\0'; break; } }
break
i++
Name[i]
i
for(size_t i = 0; i < Name[i]; i++ )
for(size_t i = 0; Name[i]; i++ )
如果使用 POSIX getline()
是一个选项 - 不要忽视它的安全问题,并且如果你想用指针括起来 - 你可以避免使用字符串函数,因为返回字符数。如下所示:getline
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *fname, *lname;
size_t size = 32, nchar; // Max size of strings and number of characters read
fname = malloc(size * sizeof *fname);
lname = malloc(size * sizeof *lname);
if (NULL == fname || NULL == lname)
{
printf("Error in memory allocation.");
exit(1);
}
printf("Enter first name ");
nchar = getline(&fname, &size, stdin);
if (nchar == -1) // getline return -1 on failure to read a line.
{
printf("Line couldn't be read..");
// This if block could be repeated for next getline too
exit(1);
}
printf("Number of characters read :%zu\n", nchar);
fname[nchar - 1] = '\0';
printf("Enter last name ");
nchar = getline(&lname, &size, stdin);
printf("Number of characters read :%zu\n", nchar);
lname[nchar - 1] = '\0';
printf("Name entered %s %s\n", fname, lname);
return 0;
}
注意:getline
的[安全问题]不容忽视。
我的新手方式;-)如果正确,请告诉我。它似乎适用于我的所有情况:
#define IPT_SIZE 5
int findNULL(char* arr)
{
for (int i = 0; i < strlen(arr); i++)
{
if (*(arr+i) == '\n')
{
return i;
}
}
return 0;
}
int main()
{
char *input = malloc(IPT_SIZE + 1 * sizeof(char)), buff;
int counter = 0;
//prompt user for the input:
printf("input string no longer than %i characters: ", IPT_SIZE);
do
{
fgets(input, 1000, stdin);
*(input + findNULL(input)) = '\0';
if (strlen(input) > IPT_SIZE)
{
printf("error! the given string is too large. try again...\n");
counter++;
}
//if the counter exceeds 3, exit the program (custom function):
errorMsgExit(counter, 3);
}
while (strlen(input) > IPT_SIZE);
//rest of the program follows
free(input)
return 0;
}
以最明显的方式删除换行符的步骤:
- 使用 , header 确定里面字符串的长度。请注意,这不计算终止 .
NAME
strlen()
string.h
strlen()
\0
size_t sl = strlen(NAME);
- 查看字符串是否以一个字符开头或仅包含一个字符(空字符串)。在这种情况下,因为正如我上面所说,它不计算并在第一次出现时停止:
\0
sl
0
strlen()
\0
if(sl == 0)
{
// Skip the newline replacement process.
}
- 检查正确字符串的最后一个字符是否为换行符。如果是这种情况,请替换为 .请注意,索引计数从 开始,因此我们需要执行以下操作:
'\n'
\n
\0
0
NAME[sl - 1]
if(NAME[sl - 1] == '\n')
{
NAME[sl - 1] = '\0';
}
请注意,如果仅在字符串请求时按 Enter(字符串内容仅由换行符组成),则此后 in 中的字符串将是一个空字符串。fgets()
NAME
- 我们可以结合第 2 步。和 3.使用逻辑运算符在一个 -语句中:
if
&&
if(sl > 0 && NAME[sl - 1] == '\n')
{
NAME[sl - 1] = '\0';
}
- 完成的代码:
size_t sl = strlen(NAME);
if(sl > 0 && NAME[sl - 1] == '\n')
{
NAME[sl - 1] = '\0';
}
如果您更喜欢通过处理输出字符串来使用此技术的函数,而无需每次都重新键入,这里是:fgets
fgets_newline_kill
void fgets_newline_kill(char a[])
{
size_t sl = strlen(a);
if(sl > 0 && a[sl - 1] == '\n')
{
a[sl - 1] = '\0';
}
}
在您提供的示例中,它将是:
printf("Enter your Name: ");
if (fgets(Name, sizeof Name, stdin) == NULL) {
fprintf(stderr, "Error reading Name.\n");
exit(1);
}
else {
fgets_newline_kill(NAME);
}
请注意,如果输入字符串中嵌入了 s,则此方法不起作用。如果是这样的话,将只返回字符的数量,直到第一个 .但这并不是一种很常见的方法,因为大多数字符串读取函数通常停在第一个函数上,并将字符串直到该 null 字符为止。\0
strlen()
\0
\0
撇开问题本身不谈。尽量避免使代码更不清晰的双重否定:.你可以简单地做.if (!(fgets(Name, sizeof Name, stdin) != NULL) {}
if (fgets(Name, sizeof Name, stdin) == NULL) {}
评论
\n
\0
\n
\n
fgets()
fgets()
strlen
一般来说,与其修剪你不想要的数据,不如首先避免写入它。如果不希望缓冲区中使用换行符,请不要使用 fgets。请改用 or 或 .也许是这样的:getc
fgetc
scanf
#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
char Name[256];
char fmt[32];
if( snprintf(fmt, sizeof fmt, "%%%zd[^\n]", sizeof Name - 1) >= (int)sizeof fmt ){
fprintf(stderr, "Unable to write format\n");
return EXIT_FAILURE;
}
if( scanf(fmt, Name) == 1 ) {
printf("Name = %s\n", Name);
}
return 0;
}
请注意,这种特定方法将使换行符保持未读状态,因此您可能希望使用格式字符串,例如丢弃它(例如,),或者在扫描后加上一个 ."%255[^\n]%*c"
sprintf(fmt, "%%%zd[^\n]%%*c", sizeof Name - 1);
getchar()
评论
sprintf
snprintf
Name
snprintf
sprintf
scanf
getchar()
scanf
这是我的解决方案。很简单。
// Delete new line
// char preDelete[256] include "\n" as newline after fgets
char deletedWords[256];
int iLeng = strlen(preDelete);
int iFinal = 0;
for (int i = 0; i < iLeng; i++) {
if (preDelete[i] == '\n') {
}
else {
deletedWords[iFinal] = preDelete[i];
iFinal++;
}
if (i == iLeng -1 ) {
deletedWords[iFinal] = '\0';
}
}
扩展 @Jerry Coffin 和 @Tim Čas 的答案:
从设计上讲,该版本比 (并且版本可能是最快的)快得多。strchr
strcspn
strlen
的内部必须遍历字符串,如果实现合理,它只执行一次,并将字符串长度存储在某处。然后,在搜索时,它还必须使用嵌套的 for 循环来遍历字符串。strcspn
"\n"
"\n"
忽略这些函数的库质量实现会考虑的字大小等因素,朴素的实现可能如下所示:
char* my_strchr (const char *s, int c)
{
while(*s != '\0')
{
if(*s == c)
return (char*)s;
s++;
}
return NULL;
}
size_t my_strcspn (const char *s1, const char *s2)
{
size_t s2_length = strlen(s2);
size_t i;
for(i=0; s1[i] != '\0'; i++)
{
for(size_t j=0; j<s2_length; j++)
{
if(s1[i] == s2[j])
{
return i;
}
}
}
return i;
}
如果是 ,则每个字符有两个分支。一个搜索 null 终止符,另一个将当前字符与搜索的字符进行比较。
strchr
在 的情况下,它要么必须像我的示例中那样预先计算大小,要么在查找 null 和搜索键时遍历它。后者本质上就是它的作用,所以内部循环可以用 .无论我们如何实现它,都会有很多额外的分支。
strcspn
s2
strchr
strchr
细心的语言律师可能还会发现标准图书馆定义中缺少。这意味着编译器不允许假定 和 是不同的字符串。这也阻止了一些优化。
restrict
strcspn
s1
s2
该版本将比两者都快,因为只需要检查空终止,而不需要检查其他任何内容。虽然正如 @chux - 恢复莫妮卡的答案中提到的,但在某些情况下它不起作用,因此它比其他版本稍微脆弱一些。strlen
strlen
问题的根源是函数的 API 错误——如果它在过去实现得更好,它会返回与实际读取的字符数相对应的大小,这本来很棒。或者,指向最后一个字符的指针读作 .相反,标准库通过返回指向传递的字符串中第一个字符的指针来浪费返回值,这有点有用。fgets
strchr
评论
char* result = fgetsane(s,n,stdin); if(result != NULL) { size_t size = result - s; if(*result == '\n') *result = '\0'; }
\n
上一个:如何在 Java 中拆分字符串?
评论
if (!fgets(Name, sizeof Name, stdin))
(至少不要使用两个否定词,! 和 !=)if (fgets(Name, sizeof Name, stdin)) {
if (fgets(Name, sizeof Name, stdin) == NULL ) {
!