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


236

ユーザーからデータを取得し、それを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))(少なくとも、2つの否定、!と!=は使用しないでください)

4
@Roger Pate "2つの否定を使用しないでください"->ふーむ、深く掘り下げた場合、 "否定"と "否定"はどちらも否定です。;-)。おそらく、「使用if (fgets(Name, sizeof Name, stdin)) {
chux -復活モニカ

3
@chux、私はあなたが意味していたと確信していますif (fgets(Name, sizeof Name, stdin) == NULL ) {
R Sahu

@RSahu :厄介な!
chux -復活モニカ

回答:


155

少し醜い方法:

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()バリアントを使用する方が良いでしょう。
マイケル・バー

2
あなたのstrtokアプローチと同様に、完全にスレッドセーフで再入可能なバリアントについては、私の回答を参照してください(空の入力でも機能します)。実際、実装するにstrtokは、strcspnおよびを使用することをお勧めしstrspnます。
TimČas、2015

2
回線が長すぎるリスクがある環境では、elseケースを処理することが重要です。入力をサイレントに切り捨てると、非常に有害なバグが発生する可能性があります。
Malcolm McLean 2017年

2
ワンライナーが好きで、glibcを使用している場合は、を試してください*strchrnul(Name, '\n') = '\0';
ビット2017

場合はstrchr(Name, '\n') == NULL、次に脇から「入力が長すぎるバッファ、フラグエラーのために」、他の可能性が存在し、:最後のテキストstdinで終わらなかった'\n'か、まれな組み込みヌル文字が読みました。
chux-モニカを2017年

440

おそらく最も簡単な解決策は、私のお気に入りのあまり知られていない関数の1つを使用します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のより便利な関数の1つ。今日、このような一般的なCハックの束を作成して公開することにしました。strtok_rを使用strcspnした実装strspnは最初の1つでした:codepad.org/2lBkZk0w警告:バグがないことを保証することはできません。急いで書かれ、おそらくいくつかあります)。まだ公開する予定はありませんが、有名な「ビットいじりハック」の精神で作成するつもりです。
TimČas、2015

4
ロバストに トリミングfgets()する方法を検討しました。これstrcspn()唯一の正しいワンライナーのようです。 strlen簡単ですが、それほど単純ではありません。
chux-モニカを2015年

6
@sidbushes:タイトルとコンテンツの両方で、入力からfgets()の末尾の改行について質問ます。これは常に最初の改行でもあります。
TimČas17年

9
@sidbushes:私はあなたがどこから来たのか理解していますが、特定の用語のGoogle検索結果について責任を負うことはできません。私ではなく、Googleに話しかけてください。
TimČas、

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

8
文字列が空の場合、おそらく例外をスローしますか?範囲外のインデックスのようです。
Edward Olamisan 2013年

1
@EdwardOlamisan、ただし、文字列が空になることはありません。
James Morris

5
@James Morris異常な場合fgets(buf, size, ....)-> strlen(buf) == 0。1)fgets()最初のchara として読み取ります'\0'。2)size == 13)fgets()戻り値のNULL場合、bufコンテンツは何でもかまいません。(ただし、OPのコードはNULLをテストします)size_t ln = strlen(name); if (ln > 0 && name[ln-1] == '\n') name[--ln] = '\0';
提案

2
文字列が空の場合はどうなりますか?ln-1になりますが、事実size_tは署名されていないため、ランダムメモリに書き込まれます。私はあなたが使いたいと思いますssize_t、そして、チェックlnは> 0です。
2015年

2
@ legends2k:コンパイル時の値(特にのようなゼロ値strlen)の検索は、通常の文字単位の検索よりもはるかに効率的に実装できます。どのような理由から私はより良いよりも、この解決策を検討したいstrchrstrcspnに基づくもの。
AnT 2016年

17

以下は、'\n'によって保存された文字列から可能性を取り除くための迅速なアプローチfgets()です。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';
  }

bufferそしてlen、必要に応じて、を使用してください。

このメソッドには、len後続のコードの値という副次的な利点があります。それよりも簡単に速くなる可能性がありますstrchr(Name, '\n')。 YMMVを参照しますが、どちらの方法でも機能します。


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

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


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

  1. strtok(buffer, "\n");削除に失敗した'\n'ときbufferです"\n"。この回答から-この制限を警告するためにこの回答の後に修正されました。

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

    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;比べて性能以外は1ライナーに問題ありませんstrlen()。コードが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);OKです
chux -復活モニカ

これをありがとう!! 私はCSコースを受講していますが、これは課題の1つに非常に役立ちました。私はあなたの答えをソースコードに入れました。
Nathaniel Hoyt

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
の代わりに使用する方が安全strnlenですstrlen
Mike Mertsock 2013年

3
リンクされた質問の最初の回答へのコメントは、「strlen()、strcmp()、およびstrdup()は安全です。「n」の選択肢は、追加の機能を提供します。」
エティエンヌ

4
@eskerいいえ、そうではありません。挿入しnても安全に魔法のように安全性が向上するわけではありません。この場合、実際にはコードがより危険になります。同様にstrncpy、非常に危険な関数です。あなたがリンクした投稿は悪いアドバイスです。
2015年

3
空の文字列("")の場合、これは無残に失敗します。not もstrlen()返します。size_tint
2017年

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使用して1つの条件を簡単に記述できるのに、なぜネストするのです&&か そのwhileループは奇妙な構造をしています。それは単に可能性がありますwhile (length > 0 && string[length-1] == '\n') { --length; string[length] = '\0'; }
メルポメン2018

@melpomene提案をありがとう。コードを更新します。
BEPP、

1
最初の関数はより自然に次のように定義することをお勧めしますsize_t length = strlen(string); if (length > 0 && string[length-1] == '\n') { string[length-1] = '\0'; }。これは、2番目の定義をよりよく反映しています(のif代わりに使用するだけですwhile)。
メルポメン

@elpomeneありがとう。それは理にかなっている。コードを更新しました。
BEPP、

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. header NAMEを使用してstrlen()、内部の文字列の長さを判別しstring.hます。strlen()は終了をカウントしないことに注意してください\0
size_t sl = strlen(NAME);

  1. 文字列が1 \0文字(空の文字列)で始まるか、または1つだけ含まれるかどうかを確認します。この場合slになります0ので、strlen()私は数えるdoesn't上に述べたように\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. 私達はちょうど1のステップ2と3を一緒に組み合わせることができif、論理演算子を使って-statement &&
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);
}

入力文字列にが埋め込まれている場合、このメソッドは機能しないことに注意してください\0。その場合strlen()、最初の文字数までの文字数のみが返され\0ます。しかし、これは一般的なアプローチではありません。ほとんどの文字列読み取り関数は通常、最初に停止するためです。\0そのnull文字まで文字列を取得するためです。

それ自体の質問は別として。コードが不明確になる二重否定を回避するようにしてください:if (!(fgets(Name, sizeof Name, stdin) != NULL) {}。簡単にできますif (fgets(Name, sizeof Name, stdin) == NULL) {}


なぜこれをしたいのかわからない。改行を削除する目的は、文字列をnullで終了することではありません。改行を削除することです。文字列の末尾の a \nをaに置き換えると、改行を「削除」できます。ただし、文字列内の文字を置き換えると、基本的に文字列が変更されます。意図的に複数の改行文字を含む文字列を使用することは珍しくありません。これにより、これらの文字列の末尾が効果的に削除されます。このような改行を削除するには、配列の内容を左にシフトして、を上書きする必要があります。\0\n\n
ex nihilo

@exnihiloを使用して、内部に複数の改行を含む文字列を入力するにはどうすればよいfgets()ですか?
RobertSは19:26にモニカチェリオ

さて、あなたはへの複数の呼び出しによって得られた文字列を連結するかもしれませんfgets()。しかし、私はあなたの反対を理解していません。あなたは複数の改行を処理するコードを提案している人です。
ex nihilo

@exnihiloそうです、私は戦略を考え直します。希望する結果を得るために、非常に厳しいが可能な方法を追加したかっただけです。
RobertSは

@exnihilo私の回答を完全に編集し、メインのアプローチを使用して、strlenなどを使用しました。重複しないことの正当化:1.ステップごとのコードの説明。2.関数およびコンテキストベースのソリューションとして提供されます。3.二重否定式を回避するためのヒント。
RobertSは

0

TimČ1つのライナーは、fgetsの呼び出しによって取得された文字列に最適です。これは、最後に改行が1つ含まれていることがわかっているためです。

別のコンテキストで、複数の改行を含む可能性のある文字列を処理する場合は、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
「最後に改行が1つ含まれていることを知っているからです。」->がない'\n'場合(または文字列がの場合"")でも機能します。
chux-モニカを2015年

あなたの最初のコメントに応えて、私の答えはそれを守ります。strrcspnがない場合は、resetlenをスローする必要がありました\n
フィリップA.

goto end;代わりになぜ使用するのreturn len;ですか?
chqrlie

@chqrlie入り込んだこのエレガントな2レベルのループから抜け出す必要がありました。危害は加えられました。なぜ後藤じゃないの?
フィリップA.

gotoコードには2種類のがあります。ステートメント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
標準ライブラリ関数を使用せずに文字列操作ライブラリ」を作成するのは良い練習ですが、なぜ他の人にそれを使用するように伝えるのでしょうか。どちらかと言えば、標準ライブラリよりも遅く、テストも少なくなります。
メルポメン2018

これは、質問が尋ねるものとは異なる仕事をしています。おそらく、改行だけを取り除くために使用できますが、やり過ぎのように感じます。
Jonathan Leffler、2018年

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

二重引用符ではなく単一引用符を使用するためです。詳細については、シングルクォートとダブルクォートのリンクをご覧ください。


2
コードのフォーマットを説明して編集するほうがよいでしょう。
Anh Pham 2017

通常は、匿名コードのいくつかの行を投稿するだけでなく、解決策を説明する方が良いでしょう。どのようにすればよい答えを書くことができ、また完全にコードベースの答えを説明することもできます
Massimiliano Kraus 2017

1
申し訳ありませんが、これは私の最初の貢献でした。私はそれを修正します。フィードバックをお
寄せ

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-モニカを復活させる'12年

@chuxもっと好きfor (size_t i = 0; Name[i]; i++) { if (Name[i] == '\n') { Name[i] = '\0'; break; } }
メルポメン

-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ですか?
メルポメン2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.