C语言中fgets函数的使用

4

我的任务之一是编写自己的UNIX Shell。为了从用户那里接收输入,我使用fgets将输入作为字符串捕获,但我不确定它是如何工作的。当我运行:

char command[50];
fgets(command, sizeof(command), stdin);

printf("Your Command: %s", &command);
int length = strlen(command);
printf("Length of String: %d\n", length);

假设我的输入是"exit"。strlen函数返回字符串长度为5个字符,而不是四个字符。我想要这样做:

if( (strcmp(command, "exit")) == 0 ){
    doSomething();
}

但是命令永远不会等于我想要的字符串;就像它有一个未知的字符,我不确定是什么。它是末尾的空字符吗?我该如何更改if语句来检查用户输入是否与fgets捕获的"exit"相等?谢谢!


奇怪的字符是回车符还是换行符?(如果我没记错,是0xA还是0xD?) - user541686
你可以尝试类似这样的命令 echo $command|od -a,它将输出变量内部的每个字符。如果行末有什么奇怪的东西,它也会被显示出来。 - Marc B
@Mehrdad - 如果是OxD,我会有点惊讶,因为stdin不应该以二进制读取模式打开。 - Chris Lutz
@Chris:好观点,我没想到那一部分。 :) - user541686
@Marc:也许不需要,因为这是C语言,而不是sh。 - Dennis Williamson
7个回答

8

fgets函数将行结束符视为有效字符,这就是你收到额外字符的原因。

只需像command[strlen(command) - 1] = '\0';这样做,即可删除行结束符。然后你就可以自由地进行所有strcmp操作了。


在所有的答案中,这个是最简单的,而且它起作用了。谢谢! - user446836
谢谢你的回答。我相信你的回答在范围和简洁方面都很恰当。还有:它能够正常工作,同时也很适合新手。 - Smith Will Suffice

4
fgets手册页面上可以看到:

fgets()从流中读取最多比大小少一个字符的数据,并将它们存储到指向s的缓冲区中。当遇到EOF或换行符时,读取停止。如果读取到换行符,则将其存储在缓冲区中。在缓冲区的末尾存储一个'\0'。

底线是:在比较时,您的字符串末尾有一个额外的换行符。

3

fgets函数总是会在输入字符串中包含行终止符。您可以通过以下方式从“命令”的末尾删除任何空格,包括换行符:

char command[50];
fgets(command, sizeof(command), stdin);

size_t length = strlen(command);
// Trim off trailing "spaces" including newline characters
while ((length > 0) && isspace(command[length-1]))
      command[--length] = '\0';

printf("Your Command: %s\n", &command); // Include newline now...
// This is computed above...
// int length = strlen(command);

// Continue as before

1
我相信有一种更快的方法来转储空格字符,而不是在while循环中调用3次strlen(假设strlen将迭代整个字符串)。 - Mark Elliot
@Mark: 更好了吗?考虑到这些字符串的长度以及他在等待用户输入,我并没有真正担心那个优化级别...现在它只有一个strlen(原来就在他的原始代码中)。 - Reed Copsey
@Mark - 你甚至可以只使用一个赋值语句来完成,无论你有多少尾随空格。(有人可能会认为这是不安全的,但这只会对本来就不安全的代码造成问题。) - Chris Lutz
几乎好了。一个拥有113k声望值的人应该知道不要在有符号类型中存储字符串长度。使用size_tsize_tsize_t - Chris Lutz
你说得对,我认为优化对于这个任务来说并不是很重要。if((strncmp(command, "exit", 4)) == 0) 可以工作,但我将使用这个while循环,这样键入"exitasdjhsd"也不会退出shell。 - user446836

2

fgets函数会连同换行符一起读取。

请注意,您可以通过多种方式解决这个问题,其中一种方法是使用strncmp函数:

if((strncmp(command, "exit", 4)) == 0)

这个选项检查命令的前四个字符是否匹配(不过在这里可能不是合适的选项)。

另一种策略是检查换行符是否存在:

if((strcmp(command, "exit\n")) == 0)

2

处理这个问题最简单的方法可能是切换到使用scanf来读取输入:

char command[51];

scanf("%50[^\n]", command);

if (0 == strcmp(command, "exit"))
    do_something();

这看起来不像是一个有效的格式字符串。它适用于哪个编译器(或更准确地说,libc实现)? - Ben Voigt
@Ben Voigt:除非我打错了一个看不见的字,否则这是一个有效的格式字符串。它应该可以在任何符合C标准的实现中使用。我最近没有进行过太多的符合性测试,但如果有一个实现不能正确处理这个字符串,那将是相当令人惊讶的(它在C89标准中,甚至那时候也不是新的)。 - Jerry Coffin
嗯,微软声称它不符合ANSI标准,但我猜他们可能是错的。 - Ben Voigt
C标准,§7.19.6.2/12声称它是标准。我认为这可能是正确的。使用“-”表示范围(例如a-z)可能是他们作为扩展所说的内容,但我以上没有使用过。 - Jerry Coffin

1

你的字符串末尾仍然有换行符。你可以与"exit\n"进行比较,或者使用类似于strncmp(command, "exit", 4)的方法。请注意,这将接受任何以"exit"开头的内容并忽略其余部分。


0
如上所述,fgets(3)会给你尾随的 '\n'。如果使用 gets(3),则不会得到尾随的换行符。一致性是最重要的,我这么说。

Perl 有一个手动的 chomp() 函数,可以修剪掉尾随的换行符,如果存在的话 - 你可以自己编写一个比这更好的函数:

#define NUL ((char)0)
void chomp( char *s )
{
  if ( s != null )
  {
    int len = strlen(s) ;
    if ( len >= 1 && s[len-1] == "\n" )
    {
      s[len-1] = NUL ;
    }
  }
  return ;
}

2
虽然 strtok 并非最初的设计目的,但它非常适合这种情况:strtok(s, "\n"); 实际上,这是我唯一使用 strtok 的事情。 - Jerry Coffin
1
然而,gets 不应被视为一种修复方法,因为它不能防止缓冲区溢出。 - Ben Voigt
你的函数有一些问题。a) 我更喜欢将空字符表示为 '\0',这显然是一个字符字面量,虽然你也可以使用 0。将其称为 NUL 很容易与空指针混淆。而空指针 b) 被称为 NULL,而不是 null。c) strlen 返回一个 size_t,它既不能保证与 int 大小相同,也不是带符号的。不要使用 int 存储对象大小或数组索引,也不要使用无符号整数类型。使用 size_t。这就是它存在的原因。d) 我更喜欢使用 if(s == NULL) return; 来减少总体缩进。 - Chris Lutz
@Ben Voigt:你说得没错——gets是一个问题,而不是解决方案。 - Jerry Coffin
@Chris Lutz:使用“NUL”的原因是这个字符在ASCII中的控制字符代码点0x00处的名称。40多年来,“NUL”这个名称一直被使用(可以追溯到ANSI X3.2制定的1968年ASCII标准)。以下是该标准的代码表的扫描图像:http://www.asciitable.biz/images/ascii-small.jpg(1963年版本的ASCII标准仅使用描述性短语,此时0x00为“NULL”。C编程语言宏“NULL”是新手。 - Nicholas Carey
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接