web-dev-qa-db-ja.com

fgets()入力から末尾の改行文字を削除する

ユーザーからデータを取得し、それを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を削除し、適切な文字列を送るのですか。

191
sfactor

ちょっと醜い方法:

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

ちょっと奇妙な方法:

strtok(Name, "\n");

ユーザーが空の文字列を入力すると(つまり、Enterキーのみが押されると)、strtok関数は期待通りに機能しません。 \n文字はそのまま残ります。

もちろん他にもあります。

121
Jerry Coffin

おそらく最も簡単な解決策は、私のお気に入りのあまり知られていない関数の1つ、 strcspn() を使用することです。

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

'\r'も処理したい場合(ストリームがバイナリの場合など)

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

この関数は、'\r'または'\n'に達するまで文字数をカウントします(つまり、最初の'\r'または'\n'を見つけます)。何もヒットしなければ、'\0'で止まります(文字列の長さを返します)。

strcspn'\0'で停止するため、改行がなくてもこれは問題なく機能することに注意してください。その場合、行全体が単に'\0''\0'に置き換えるだけです。

356
Tim Čas
size_t ln = strlen(name) - 1;
if (*name && name[ln] == '\n') 
    name[ln] = '\0';
82
James Morris

以下はfgets()によって保存された文字列から潜在的な'\n'を削除するための速いアプローチです。
これは2つのテストで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';
  }

必要に応じてbufferlenを使用してください。

このメソッドには、後続のコードのlen値という副次的な利点があります。それはstrchr(Name, '\n')より簡単に速いかもしれません。 Ref YMMVですが、どちらの方法でも動作します。


状況によっては、元のfgets()buffer"\n"に含まれなくなります。
A)行がbufferに対して長すぎるため、'\n'の前のcharのみがbufferに保存されます。未読文字はストリームに残ります。
B)ファイルの最後の行が'\n'で終わっていません。

入力のどこかにnull文字'\0'が埋め込まれている場合、strlen()によって報告される長さには'\n'の位置は含まれません。


いくつかの他の答えの問題:

  1. buffer'\n'の場合、strtok(buffer, "\n");"\n"の削除に失敗します。これから 回答 - この回答の後にこの制限を警告するように修正しました。

  2. fgets()によって最初に読み取られたchar'\0'である場合、まれに以下が失敗します。これは、入力が埋め込み'\0'で始まるときに起こります。それからbuffer[len -1]buffer[SIZE_MAX]になり、bufferの正当な範囲外で確実にメモリにアクセスします。ハッカーが愚かにUTF16テキストファイルを読むことで試みるか、または見つけるかもしれない何か。この答えが書かれたとき、これは 答え の状態でした。後でOPではない人がこの回答の""のチェックのようなコードを含むように編集しました。

    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. [後日のために編集 answer ] buffer[strcspn(buffer, "\n")] = 0;アプローチと比較して、パフォーマンス以外に1ライナーstrlen()に問題はありません。コードがI/O(CPU時間のブラックホール)を行っている場合、トリミングのパフォーマンスは通常問題にはなりません。以下のコードが文字列の長さを必要とする場合、またはパフォーマンスを重視する場合は、このstrlen()アプローチを使用してください。そうでなければstrcspn()は良い代替案です。

16
chux

すべての行に '\ 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';
}
3
Amitabha

シングル '\ 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
Naveen Kumar

私の初心者の方法;-)それが正しいかどうか私に知らせてください。私のすべてのケースでうまくいっているようです。

#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;
}
0
Pawel Flajszer

TimČasの1つのライナーは、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;
}
0
Philippe A.

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では無視してはいけません。

0
sjsam