从fgets()输入中删除结尾的换行符


235

我试图从用户那里获取一些数据,并将其发送到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并发送正确的字符串。


21
if (!fgets(Name, sizeof Name, stdin))(至少不要使用两个否定符!和!=)

4
@Roger Pate“不要使用两个否定”-> hmmm,如果我们深入研究“ do n't”和“ negation”都是否定的。;-)。也许“使用if (fgets(Name, sizeof Name, stdin)) {
chux -恢复莫妮卡

3
@chux,我确定您的意思是if (fgets(Name, sizeof Name, stdin) == NULL ) {
R Sahu

@RSahu True:讨厌!
恢复莫妮卡

Answers:


154

稍微难看的方式:

char *pos;
if ((pos=strchr(Name, '\n')) != NULL)
    *pos = '\0';
else
    /* input too long for buffer, flag error */

稍微奇怪的方式:

strtok(Name, "\n");

请注意,strtok如果用户输入一个空字符串(即仅按Enter),该功能将无法正常工作。它使\n角色完好无损。

当然,还有其他的。


7
任何具有线程意识的C运行时库(也就是说,大多数针对多线程平台的C运行时库)strtok()都将是线程安全的(它将线程本地存储用于“内部调用”状态)。也就是说,通常最好使用非标准(但足够通用)的strtok_r()变体。
Michael Burr '04

2
请参阅我的答案以获取完全线程安全和可重入的变体,类似于您的strtok方法(它适用于空输入)。实际上,实现的一个好方法strtok是使用strcspnstrspn
TimČas2015年

2
如果您所在的环境中可能存在超长线路的风险,那么处理其他情况非常重要。默默地截断输入会导致非常有害的错误。
马尔科姆·麦克莱恩

2
如果您喜欢单线并使用glibc,请尝试*strchrnul(Name, '\n') = '\0';
比特币

当使用时strchr(Name, '\n') == NULL,除了“对于缓冲区来说输入时间太长,标志错误”之外,还存在其他可能性:最后输入的文本stdin未以a结尾,'\n'或者读取了一个罕见的嵌入式空字符。
chux-恢复莫妮卡

439

也许最简单的解决方案使用了我最喜欢的鲜为人知的功能之一strcspn()

buffer[strcspn(buffer, "\n")] = 0;

如果要它也处理'\r'(例如,如果流是二进制的):

buffer[strcspn(buffer, "\r\n")] = 0; // works for LF, CR, CRLF, LFCR, ...

该函数对字符数进行计数,直到命中a '\r'或a '\n'(换句话说,找到第一个'\r''\n')。如果没有击中任何东西,它会停在'\0'(返回字符串的长度)。

请注意,即使没有换行符,它也可以正常工作,因为它会strcspn在处停止'\0'。在这种情况下,整行只需替换'\0''\0'


30
这甚至buffer开始时处理的要少'\0',这会使方法感到悲伤buffer[strlen(buffer) - 1] = '\0';
chux-恢复莫妮卡2015年

5
@chux:是的,我希望更多的人对此有所了解strcspn()。IMO中库中更有用的功能之一。今天,我决定编写和发布许多像这样的常见C技巧。一个strtok_r使用实现strcspnstrspn是最早的一个:codepad.org/2lBkZk0w警告:我不能保证它没有臭虫,它匆匆写成,可能有几个)。虽然我不知道我将在哪里出版它们,但是我打算本着著名的“比特扭曲黑客”的精神来实现。
TimČas2015年

4
研究了稳健 修剪的方法fgets()。这strcspn()似乎是唯一正确的单线。 strlen更快-尽管不那么简单。
chux-恢复莫妮卡2015年

6
@sidbushes:问题,无论是在标题和内容,询问其后的换行符fgets()输入。总是第一条换行符。
TimČas17年

9
@sidbushes:我知道您来自哪里,但是对于特定术语的Google搜索结果,我概不负责。与Google对话,而不是我。
蒂姆·恰斯(TimČas)'17年

83
size_t ln = strlen(name) - 1;
if (*name && name[ln] == '\n') 
    name[ln] = '\0';

8
如果字符串为空,可能会抛出异常,不是吗?索引超出范围。
爱德华·奥拉米桑

1
@EdwardOlamisan,但是字符串永远不会为空。
詹姆斯·莫里斯

5
@James Morris在特殊情况下fgets(buf, size, ....)-> strlen(buf) == 0。1)fgets()读为第char一个'\0'。2)size == 13)fgets()返回,NULLbuf内容可以是任何内容。(虽然OP的代码确实会测试NULL)建议:size_t ln = strlen(name); if (ln > 0 && name[ln-1] == '\n') name[--ln] = '\0';
chux-恢复Monica 2014年

2
如果字符串为空怎么办?ln将为-1,除非事实size_t是未签名的,否则将其写入随机存储器。我认为您要使用ssize_t并检查ln> 0。
2015年

2
@ legends2k:搜索编译时值(尤其是中的零值strlen)比纯逐字符搜索要有效得多。因此,我认为此解决方案比strchrstrcspn基于解决方案的解决方案更好。
AnT

17

以下是'\n'从中保存的字符串中删除电位的快速方法fgets()
它使用strlen(),进行2次测试。

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';
  }

现在根据需要使用bufferlen

此方法的附带好处是len后续代码的值。它可以比轻松地快strchr(Name, '\n')引用 YMMV,但是两种方法都可以。


buffer,在某些情况下fgets()不会包含原始内容"\n"
A)行太长了,buffer因此仅将char之前的行'\n'保存在中buffer。未读字符保留在流中。
B)文件中的最后一行没有以结尾'\n'

如果输入'\0'在某处嵌入了空字符,则报告的长度strlen()将不包括'\n'位置。


其他一些答案的问题:

  1. strtok(buffer, "\n");无法删除'\n'when bufferis "\n"。从此答案 -在此答案后进行修改以警告此限制。

  2. 以下罕见的情况下出现故障时,首先char由读fgets()'\0'。当输入以Embedded开头时,就会发生这种情况'\0'。然后buffer[len -1]变成buffer[SIZE_MAX]肯定访问的合法范围之外的内存buffer。黑客在愚蠢地读取UTF16文本文件时可能尝试或发现的某些东西。这是写答案时的答案状态。后来,非OP对其进行了编辑,以包含类似此答案的check的代码""

    size_t len = strlen(buffer);
    if (buffer[len - 1] == '\n') {  // FAILS when len == 0
      buffer[len -1] = '\0';
    }
  3. sprintf(buffer,"%s",buffer);是未定义的行为:Ref。此外,它不保存任何前导,分隔或尾随空格。现在删除

  4. [由于稍后的答复,请进行编辑] buffer[strcspn(buffer, "\n")] = 0;与该strlen()方法相比,1衬板除了性能外没有任何问题。如果代码正在执行I / O,则修剪性能通常不是问题,这是CPU时间的黑洞。如果下面的代码需要字符串的长度或对性能有高度的了解,请使用此strlen()方法。其他strcspn()是一个很好的选择。


感谢您的帮助。strlen(buffer)当使用来动态分配缓冲区大小时,可以使用malloc吗?
rrz0

@ Rrz0 buffer = malloc(allocation_size); length = strlen(buffer);不好-指向的内存中的数据buffer未知。 buffer = malloc(allocation_size_4_or_more); strcpy(buffer, "abc"); length = strlen(buffer);没关系
chux-恢复莫妮卡

谢谢这个!我正在修CS课程,这对其中一项作业很有帮助。我将您的回答归功于源代码。
纳撒尼尔·霍伊特

8

如果每行都有'\ 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';
}

1
请注意,使用代替会更安全strnlenstrlen
Mike Mertsock

3
问题链接状态中对第一个答案的注释:“请注意,strlen(),strcmp()和strdup()是安全的。“ n”个替代项为您提供了附加功能。”
艾蒂安

4
@esker不,不是。插入n并不能神奇地提高安全性,在这种情况下,实际上会使代码更危险。与相似strncpy,这是非常不安全的功能。您链接到的帖子是不好的建议。
MM

3
对于空字符串(""),此操作将失败。也不会strlen()返回。size_tint
ALK

4
这对于空字符串是不安全的,它将在索引-1处写入。不要使用这个。
让·弗朗索瓦·法布尔

3

对于单“ \ 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';
  }
}

1
if当您可以简单地使用一个条件编写条件时,为什么要嵌套&&?这个while循环有一个奇怪的结构。可能只是while (length > 0 && string[length-1] == '\n') { --length; string[length] = '\0'; }
melpomene 18/09/17

@melpomene感谢您的建议。更新代码。
BEPP '18

1
我建议第一个函数更自然地定义为:size_t length = strlen(string); if (length > 0 && string[length-1] == '\n') { string[length-1] = '\0'; }。这也更好地反映了第二个定义(只是使用if代替while)。
melpomene

@elpomene谢谢。这说得通。我更新了代码。
BEPP '18

1

我的新手方式;-)请让我知道是否正确。它似乎适用于我所有的情况:

#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;
}

1

以也许最明显的方式删除换行符的步骤:

  1. NAME使用strlen()header 来确定内部字符串的长度string.h。请注意,strlen()这不算终止\0
size_t sl = strlen(NAME);

  1. 查看字符串是否以一个\0字符开头或仅包含一个字符(空字符串)。在这种情况下sl,是0因为strlen()如上所述,我不计算\0并在第一次出现时停止:
if(sl == 0)
{
   // Skip the newline replacement process.
}

  1. 检查正确字符串的最后一个字符是否为换行符'\n'。如果是这样的情况下,更换\n\0。请注意,索引计数从开始,0因此我们需要这样做NAME[sl - 1]
if(NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

请注意,如果仅在fgets()字符串请求时按Enter (字符串内容仅由换行符组成),NAME此后的字符串将为空字符串。


  1. 我们可以在短短的一个结合步骤2和3一起if通过逻辑运算语句来&&
if(sl > 0 && NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

  1. 完成的代码:
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);
}

请注意,如果输入字符串中嵌入\0s,则此方法不起作用。如果是这样的话strlen(),只会返回直到第一个字符为止的字符数\0。但是,这并不是一种很常见的方法,因为大多数读取字符串的函数通常在第一个处停止,\0然后将字符串提取到该空字符为止。

除了问题本身。尝试避免使代码不清楚的双重否定:if (!(fgets(Name, sizeof Name, stdin) != NULL) {}。你可以简单地做if (fgets(Name, sizeof Name, stdin) == NULL) {}


不知道为什么要这么做。删除换行符的目的不是要以null终止的字符串。它是删除换行符。在字符串末尾\n用a 代替是一种“删除”换行符的方法。但是替换字符串中的字符会从根本上改变字符串。故意包含多个换行符的字符串很常见,这将有效地切掉这些字符串的结尾。要删除此类换行符,数组内容需要向左移动以覆盖。\0\n\n
前nihilo

@exnihilo有人如何使用来输入一个包含多个换行符的字符串fgets()
RobertS

好的,您可以将通过多次调用获得的字符串连接起来fgets()。但是我不理解您的反对意见:您是提议处理多个换行符的代码。
前nihilo

@exnihilo你是正确的,我会考虑这个策略。我只是想添加一个非常苛刻但可能的方法以获得期望的结果。
RobertS

@exnihilo完全编辑了我的答案,并通过使用strlenetc 遵循了主要方法。避免重复的理由:1.分步解释代码。2.作为功能和基于上下文的解决方案提供。3.提示以避免双重否定表达式。
RobertS

0

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;
}

1
“因为您知道它们的末尾包含一个换行符。” ->甚至在没有'\n'(或字符串为"")时也可以使用。
chux-恢复莫妮卡2015年

为了回应您的第一个评论,我的回答保留了这一点。我曾在以抛出resetlen strrcspn的时候没有\n
Philippe A.

为什么用goto end;代替return len;
chqrlie

@chqrlie我需要摆脱我陷入的这个优雅的2级循环。伤害已经造成。为什么不去?
Philippe A.

goto的代码中有两种:goto可以用一条return语句替换的无用的,以及goto被认为是有害的向后的。使用strchr有助于实现strrspnstrrcspn以更简单的方式: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; }
chqrlie

0

如果选择使用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不应该被忽视,虽然。


-1

下面的函数是我在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


您应该删除*in *src++;和make badtoken以及d const char *。还为什么不使用strchr代替zChrSearch*src不能'\0'在你的zStrrmv功能。
chqrlie

谢谢@chqrlie!更新了代码以反映您的建议..... zstring最初是一个有趣的项目,旨在创建一个不使用任何标准库函数的字符串处理库,因此我没有使用strchr
fnisi

1
编写“ 不使用任何标准库函数的字符串处理库 ”是一个不错的练习,但是为什么要告诉其他人使用它呢?如果有的话,它将比任何标准库更慢并且测试更少。
melpomene 18/09/17

这与问题所要求的工作不同。它可能可以用来摆脱唯一的换行符,但是感觉有点过分了。
乔纳森·莱夫勒

-1
 for(int i = 0; i < strlen(Name); i++ )
{
    if(Name[i] == '\n') Name[i] = '\0';
}

您应该尝试一下。这段代码基本上遍历字符串,直到找到'\ n'。找到后,将用空字符终止符“ \ 0”替换“ \ n”

请注意,您在此行中比较的是字符而不是字符串,因此无需使用strcmp():

if(Name[i] == '\n') Name[i] = '\0';

因为您将使用单引号而不是双引号。如果您想了解更多,这里是单引号和双引号的链接


2
如果您解释并编辑代码格式会更好。
Anh Pham

通常,最好解释一个解决方案,而不是仅仅发布一些匿名代码行。您可以阅读《我如何写一个好的答案》,也可以解释完全基于代码的答案
Massimiliano Kraus

1
抱歉,这是我在这里的第一个贡献。我会修好它。感谢您的反馈
马修斯马丁斯JERONIMO

3
效率低下:for(int i = 0; i < strlen(Name); i++ )将调用strlen(Name)很多次(循环更改 Name[]),所以一定长度N,这是一个O(N*N)解决方案。strlen(Name)提供O(N)`解决方案只需调用1 (如果有的话)。不清楚为什么int i用代替size_t i。考虑for(size_t i = 0; i < Name[i]; i++ )
chux-恢复莫妮卡

@chux更像for (size_t i = 0; Name[i]; i++) { if (Name[i] == '\n') { Name[i] = '\0'; break; } }
melpomene 18/09/17

-1

试试这个:

        int remove_cr_lf(char *str)
        {
          int len =0;


          len = strlen(str);

          for(int i=0;i<5;i++)
          {
            if (len>0)
            if (str[len-1] == '\n')
            {
              str[len-1] = 0;
              len--;
            }

            if (len>0)
            if (str[len-1] == '\r')
            {
              str[len-1] = 0;
              len--;
            }
          }

          return 0;
        }

1
len = strlen(str)可能溢出:strlen返回size_t,而不是int。奇怪的if (len>0) if (...)条件是什么?你不知道&&吗?如果要删除CR / LF的多个尾随实例,为什么将自己限制为5个?为什么不删除它们呢?为什么函数int总是返回时具有返回类型0?为什么不返回void
melpomene 18/09/17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.