String read in by fgets doesn't write newline with fputs

huangapple go评论57阅读模式
英文:

String read in by fgets doesn't write newline with fputs

问题

以下是代码部分的翻译:

#include <stdio.h>;

void FlushInputBuf(void)
{
    while (getchar() != '\n');
}

int main(void)
{
    FILE *fp = fopen("details.txt", "wt");
    if (fp == NULL)
    {
        puts("File open failed.\n");
        return -1;
    }
    char name[20];
    char id[15];
    char phoneNum[14];

    printf("Enter name: ");
    fgets(name, sizeof(name), stdin);
    printf("Enter ID: ");
    fgets(id, sizeof(id), stdin);
    FlushInputBuf();
    printf("Enter phone number: ");
    fgets(phoneNum, sizeof(phoneNum), stdin);

    fputs("#Name: ", fp);
    fputs(name, fp);
    fputs("#SSID: ", fp);
    fputs(id, fp);
    fputs("#Phone number: ", fp);
    fputs(phoneNum, fp);
  
    fclose(fp);

    return 0;
}

请注意,上述翻译中并没有包括问题中的代码部分,只提供了代码的翻译。如果您需要进一步的帮助或解决问题中的代码问题,请提出具体的问题。

英文:

I am writing a program that takes in user input on a person's details that consists of name, ID and phone number. I am limiting my ID and phone number to be maximum of 14 and 13 characters respectively (excluding newline char) - e.g. 010203-1234567 for ID and 010-1111-2222 for phone number. By right if I understood correctly, fgets should automatically add in a newline byte after it reads in a string from user input and fputs should write the newline at the end of the string stored in id and phoneNum, but when try to input more characters than allowed, e.g. 010203-12345678 for ID, the following fputs doesn't write out the newline as it should. How do I ensure even if the user inputs more characters than allowed, the newline is written out by fputs?

Following is my code:

#include &lt;stdio.h&gt;

void FlushInputBuf(void)
{
    while (getchar() != &#39;\n&#39;);
}

int main(void)
{
    FILE *fp = fopen(&quot;details.txt&quot;, &quot;wt&quot;);
    if (fp == NULL)
    {
        puts(&quot;File open failed.\n&quot;);
        return -1;
    }
    char name[20];
    char id[15];
    char phoneNum[14];

    printf(&quot;Enter name: &quot;);
    fgets(name, sizeof(name), stdin);
    printf(&quot;Enter ID: &quot;);
    fgets(id, sizeof(id), stdin);
    FlushInputBuf();
    printf(&quot;Enter phone number: &quot;);
    fgets(phoneNum, sizeof(phoneNum), stdin);

    fputs(&quot;#Name: &quot;, fp);
    fputs(name, fp);
    fputs(&quot;#SSID: &quot;, fp);
    fputs(id, fp);
    fputs(&quot;#Phone number: &quot;, fp);
    fputs(phoneNum, fp);
  
    fclose(fp);

    return 0;
}

I am assuming input for name will not be longer than 20.
When I input ID to be 010203-1234567 and phone number to be 010-1111-2222 I get the following result in details.txt:

#Name: John
#SSID: 010203-1234567#Phone number: 010-1111-2222

How do I get the code to write to details.txt as following:

#Name: John
#SSID: 010203-1234567
#Phone number: 010-1111-2222

答案1

得分: 4

根据我正确理解,fgets应该在从用户输入中读取字符串后自动添加一个换行字符。

不是的。fgets()会将字符复制到缓冲区,直到没有足够的空间(同时保留一个字符串终止符),或者复制了一个换行符。它不会添加任何不在输入中的换行符,因此无法确保字符串末尾有一个换行符。通常情况下会有一个换行符,但如果缓冲区太小无法容纳整行,那么就不会有换行符。这是判断是否获取了完整行的主要方式。

fputs应该在存储在id和phoneNum中的字符串末尾写入换行字符。

是的,fputs会写入指定字符串中出现的任何换行符。如果没有打印出任何换行符,这很强烈证明没有读入您的id(和phoneNum)缓冲区中的换行符。

当尝试输入超过允许的字符时,例如“010203-12345678”作为ID,下面的fputs不会写出应该写出的换行符。

它并不会打印出您期望的换行符,但它确实在执行应该执行的操作。

您的id数组长度为15个字符,足够容纳14个字符的ID和一个空终止符。因此,fgets在复制第14个字符('7')后停止读取,并使用缓冲区的最后一个字节终止字符串。然后,您的FlushInputBuf()函数读取并丢弃输入行的尾部,包括下一个换行符。没有存储换行符。对于恰好包含14个字符的输入,也会发生同样的情况。如果输入的ID比这还要短,那么FlushInputBuf()调用将强制您在提示输入电话号码之前再次键入一个换行符,并将在此之前丢弃您输入的任何其他内容。

解决这个问题的一个好方法是:

  1. 提供一个足够长的缓冲区,以容纳预期的输入,再加上一个换行符和一个字符串终止符。

    #define MAX_ID_LENGTH 14
    // ...
    char id[MAX_ID_LENGTH + 2];
    

    有时允许一些比实际预期的更多的空间会有利。

  2. fgets()调用之后,检查缓冲区中是否有换行符。只有在没有读取换行符的情况下才消耗输入行的尾部。

    size_t newline_pos = strcspn(id, "\n");
    if (id[newline_pos] != '
    size_t newline_pos = strcspn(id, "\n");
    if (id[newline_pos] != '\0') {
        assert(id[newline_pos] == '\n');
        FlushInputBuf();
    }
    
    '
    ) {
    assert(id[newline_pos] == '\n'); FlushInputBuf(); }
  3. 验证或至少修正输入。特别是,如果存在换行符,则移除它,因为它实际上不是ID的一部分。

    id[newline_pos] = '
    id[newline_pos] = '\0';
    
    '
    ;

    拒绝或截断太长的ID。

    // 可能是:
    id[MAX_ID_LENGTH] = '
    // 可能是:
    id[MAX_ID_LENGTH] = '\0';
    
    '
    ;

    执行任何其他适当的验证。

  4. 在需要时手动在输出中打印换行符。在您的情况下,我会在格式中适当地放置一个fprintf()调用,以取代每对fputs()调用,并在格式中适当地放置换行符。

    fprintf(fp, "#SSID: %s\n", id);
    

    但如果必须仅使用fputs(),则可以添加fputs("\n", fp);。如果您不关心短输入的影响(但您应该关心),则仅执行此步骤就足够了。

英文:

> By right if I understood correctly, fgets should automatically add in a newline char after it reads in a string from user's input

No. fgets() will copy characters into the buffer until it runs out of room (while reserving space for a string terminator), or it has copied a newline. It does not add any newline that wasn't in the input already, and (therefore) it cannot ensure that there is a newline at the end of the string. Often there is one, but if your buffer is too small for the whole line then there won't be. That's the primary way to tell whether you got a full line.

> fputs should write the newline char at the end of the string stored in id and phoneNum

Yes, fputs will write any newlines appearing in the specified string. That none are being printed is strong evidence that none are being read into your id (and phoneNum) buffer.

> when try to input more characters than allowed, e.g. "010203-12345678" for ID, the following fputs doesn't write out the newline as it should.

It doesn't print a newline as you expected. But it it is doing exactly what it should.

Your id array is 15 chars long, which is big enough for 14 characters of ID and one null terminator. Accordingly, fgets stops reading after copying the 14th character, the '7', and terminates the string with the last byte of the buffer. Your FlushInputBuf() function then reads and discards the tail of the line, up to and including the next newline. No newline is stored. The same thing will happen with an entry that is exactly 14 characters, too. And if the ID entered is shorter than that, then the FlushInputBuf() call will force you to type a second newline before it prompts for the phone number (and will discard anything else you type before that).

A good way to do this would be

  1. Provide a buffer at least long enough to accommodate the expected input, plus an newline, plus a string terminator.

    #define MAX_ID_LENGTH 14
    // ...
    char id[MAX_ID_LENGTH + 2];
    

    Sometimes it's advantageous to allow for a little more than you actually expect.

  2. After the fgets() call, check for a newline in the buffer. Consume the tail of the input line only if there is any -- that is, if no newline was already read.

    size_t newline_pos = strcspn(id, &quot;\n&quot;);
    if (id[newline_pos] != &#39;
    size_t newline_pos = strcspn(id, &quot;\n&quot;);
    if (id[newline_pos] != &#39;\0&#39;) {
    assert(id[newline_pos] == &#39;\n&#39;);
    FlushInputBuf();
    }
    
    &#39;) { assert(id[newline_pos] == &#39;\n&#39;); FlushInputBuf(); }
  3. Validate or at least fix up the input. In particular, remove the newline, if present, since it is not actually part of the ID.

    id[newline_pos] = &#39;
    id[newline_pos] = &#39;\0&#39;;
    
    &#39;;

    Reject or truncate IDs that are otherwise too long.

    // maybe:
    id[MAX_ID_LENGTH] = &#39;
    // maybe:
    id[MAX_ID_LENGTH] = &#39;\0&#39;;
    
    &#39;;

    Perform any other validations that are appropriate.

  4. Print newlines to the output manually where you want them. In your particular case, I would use one fprintf() call in place of each pair of fputs() calls, with newlines placed appropriately in the formats.

    fprintf(fp, &quot;#SSID: %s\n&quot;, id);
    

    But if you must use fputs() only, then you can instead add a fputs(&quot;\n&quot;, fp);.

    If you were not concerned about the effects of short entries (which you should be), then this step would be sufficient by itself.

答案2

得分: 2

#include <errno.h>;
#include <stdio.h>;
#include <string.h>;

int input_string(const char *prompt, char *dest, int size) {
    int c;      // 从标准输入读取的字节
    int i = 0;  // dest的索引
    int n = 0;  // 读取的字节数(不包括换行符)

    printf("%s", prompt);
    /* 确保提示可见(在一些较老的系统上必要) */
    fflush(stdout);

    /* 从用户输入行中读取所有字节 */
    while ((c = getchar()) != EOF && c != '\n') {
        if (i + 1 < size) {
            /* 将字符追加到dest,空间允许的话 */
            dest[i++] = (char)c;
        }
        n++;
    }
    if (size > 0) {
        // 设置空字符终止符
        dest[i] = '\0';
    }
    if (n >= size) {
        printf("丢弃了 %d 个额外字符\n", n - size - 1);
    }
    if (c == EOF) {
        if (i == 0)
            printf("文件提前结束\n");
            return EOF;
        } else {
            printf("文件末尾缺少换行符\n");
        }
    }
    /* 返回存储到dest中的字符数 */
    return i;
}

int main(void) {
    FILE *fp = fopen("details.txt", "w");
    if (fp == NULL) {
        fprintf(stderr, "无法打开 %s:%s\n", "details.txt",
                strerror(errno));
        return 1;
    }
    char name[20];
    char id[15];
    char phoneNum[14];

    if (input_string("输入姓名:", name, sizeof(name)) < 0
    ||  input_string("输入ID:", id, sizeof(id)) < 0
    ||  input_string("输入电话号码:", phoneNum, sizeof(phoneNum)) < 0) {
        fprintf(stderr, "缺少输入\n");
        fclose(fp);
        return 1;
    }
    fprintf(fp, "#姓名:%s\n", name);
    fprintf(fp, "#SSID:%s\n", id);
    fprintf(fp, "#电话号码:%s\n", phoneNum);

    fclose(fp);
    return 0;
}
英文:

In addition to @JohnBollinger's excellent response, here is a modified version of your code, using an ancillary function for controlled user input:

#include &lt;errno.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
int input_string(const char *prompt, char *dest, int size) {
int c;      // byte read from stdin
int i = 0;  // index into dest
int n = 0;  // number of bytes read excluding the newline
printf(&quot;%s&quot;, prompt);
/* ensure prompt is visible (necessary on some older systems) */
fflush(stdout);
/* read all bytes from the user input line */
while ((c = getchar()) != EOF &amp;&amp; c != &#39;\n&#39;) {
if (i + 1 &lt; size) {
/* append the character to dest, space permitting */
dest[i++] = (char)c;
}
n++;
}
if (size &gt; 0) {
// set the null terminator
dest[i] = &#39;\0&#39;;
}
if (n &gt;= size) {
printf(&quot;discarded %d extra characters\n&quot;, n - size - 1);
}
if (c == EOF) {
if (i == 0)
printf(&quot;premature end of file\n&quot;);
return EOF;
} else {
printf(&quot;missing newline at end of file\n&quot;);
}
}
/* return the number of characters stored into dest */
return i;
}
int main(void) {
FILE *fp = fopen(&quot;details.txt&quot;, &quot;w&quot;);
if (fp == NULL) {
fprintf(stderr, &quot;Failed to open %s: %s\n&quot;, &quot;details.txt&quot;,
strerror(errno));
return 1;
}
char name[20];
char id[15];
char phoneNum[14];
if (input_string(&quot;Enter name: &quot;, name, sizeof(name)) &lt; 0
||  input_string(&quot;Enter ID: &quot;, id, sizeof(id)) &lt; 0
||  input_string(&quot;Enter phone number: &quot;, phoneNum, sizeof(phoneNum)) &lt; 0) {
fprintf(stderr, &quot;Missing input\n&quot;);
fclose(fp);
return 1;
}
fprintf(fp, &quot;#Name: %s\n&quot;, name);
fprintf(fp, &quot;#SSID: %s\n&quot;, id);
fprintf(fp, &quot;#Phone number: %s\n&quot;, phoneNum);
fclose(fp);
return 0;
}

huangapple
  • 本文由 发表于 2023年4月4日 09:51:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/75924928.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定