转移选定的 cmets 以形成答案。
一般诊断
这是您系统上终端驱动程序的属性,而不是程序或 C 库的属性。诸如 Bash 之类的现代 shell 不会读取一行;他们使用非规范输入在字符可用时读取字符。另见Canonical vs non-canonical terminal input。
Barmarnoted:
请注意,read() 不会向它读取的输入添加空终止符,但 printf() 需要一个空终止字符串。
您可以告诉printf() 要打印多少个字符,而不是添加空终止符:
printf("%.*s\n", c, buf);
然而,这与如何获得一长串输入的问题无关。
如果您使用开源 o/s,您可以修改终端驱动程序源代码并重新编译您的内核,以允许您在单行中输入超过 1 KiB 的内容,但任何不足的内容都不会工作。终端驱动程序施加限制;您必须更改终端驱动程序才能更改该限制。如果您在 Linux 上,您可以在 /proc 文件系统中查看是否有可以更改的动态配置参数(因此您不必重新编译内核,但您必须更改终端驱动程序);我还没有听说有可能。
如果您从浏览器复制'n'粘贴超过 1 KiB 且其中没有换行符的文本并希望将其粘贴到系统上的文件中,则该限制可能会令人讨厌。使用 Vim 之类的程序来管理它——它将终端置于非规范模式,因此不会遇到限制。
使用 POSIX termios 从终端获取输入
如果您希望程序在没有行长的情况下从终端读取(但也可以进行行编辑,例如擦除或终止处理),那么您可以考虑这个程序 — slurp:
/*
@(#)File: $RCSfile: slurp.c,v $
@(#)Version: $Revision: 1.3 $
@(#)Last changed: $Date: 2018/10/28 17:14:24 $
@(#)Purpose: Put terminal into non-canonical mode to slurp input
@(#)Author: J Leffler
*/
/*TABSTOP=4*/
#include "posixver.h"
#include "stderr.h"
#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
static const char optstr[] = "a:ho:V";
static const char usestr[] = "[-hV][-a output | -o output]";
static const char hlpstr[] =
" -a output Append to named file (creating it if necessary)\n"
" -h Print this help message and exit\n"
" -o output Output to named file (truncating it if it exists)\n"
" -V Print version information and exit\n"
;
static struct termios saved = { 0 };
static bool sigint_enabled = false;
static bool sigquit_enabled = false;
static bool slurping = false;
static void reset_termios(void);
static void set_non_canonical(void);
static void sig_handler(int signum);
static void set_signal_handling(void);
static void slurp(int ofd, const char *filename);
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
extern const char jlss_id_slurp_c[];
const char jlss_id_slurp_c[] = "@(#)$Id: slurp.c,v 1.3 2018/10/28 17:14:24 jonathanleffler Exp $";
#endif /* lint */
int main(int argc, char **argv)
{
const char *filename = "standard output";
int ofd = STDOUT_FILENO;
int oflag = 0;
err_setarg0(argv[0]);
int opt;
while ((opt = getopt(argc, argv, optstr)) != -1)
{
switch (opt)
{
case 'h':
err_help(usestr, hlpstr);
/*NOTREACHED*/
case 'o':
case 'a':
if (ofd != STDOUT_FILENO)
{
err_remark("the -a and -o flags are mutually exclusive\n");
err_usage(usestr);
}
oflag = (opt == 'o') ? O_TRUNC : O_APPEND;
if ((ofd = open(optarg, O_WRONLY | O_CREAT | oflag, 0644)) < 0)
err_syserr("failed to open file %s for writing: ", optarg);
filename = optarg;
break;
case 'V':
err_version("PROG", &"@(#)$Revision: 1.3 $ ($Date: 2018/10/28 17:14:24 $)"[4]);
/*NOTREACHED*/
default:
err_usage(usestr);
/*NOTREACHED*/
}
}
if (optind != argc)
{
err_remark("unexpected file name options (first is '%s')\n", argv[optind]);
err_usage(usestr);
}
set_non_canonical();
if (slurping)
set_signal_handling();
slurp(ofd, filename);
return 0;
}
static void reset_termios(void)
{
tcsetattr(STDIN_FILENO, 0, &saved);
}
static void set_non_canonical(void)
{
if (tcgetattr(STDIN_FILENO, &saved) == 0)
{
struct termios modified = saved;
atexit(reset_termios);
/*
** On macOS 10.14 (at least), if you don't reset ISIG, the
** signal characters are not transferred to the program, so
** you can't detect those signals. With ICANON reset, they
** don't generate the signal either. The code does not try
** to handle the suspend (^Z) key specially, nor any other
** keys than EOF, INTR, QUIT.
*/
modified.c_lflag &= ~(ICANON | ISIG);
modified.c_cc[VMIN] = 1;
modified.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &modified);
slurping = true;
}
}
static void sig_handler(int signum)
{
reset_termios();
_exit(128 + signum);
}
/* Almost worth a data structure and a loop, but not quite */
static void set_signal_handling(void)
{
/* Simulate SIGINT and SIGQUIT */
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
{
(void)signal(SIGINT, sig_handler);
sigint_enabled = true;
}
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
{
(void)signal(SIGQUIT, sig_handler);
sigquit_enabled = true;
}
/* Have program terminate when sent normal signals */
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
(void)signal(SIGHUP, sig_handler);
if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
(void)signal(SIGTERM, sig_handler);
if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
(void)signal(SIGPIPE, sig_handler);
}
static void slurp(int ofd, const char *filename)
{
char buffer[4096];
int nbytes;
while ((nbytes = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0)
{
/* Simulate EOF and interrupt and quit signals */
if (nbytes == 1 && slurping)
{
if (buffer[0] == saved.c_cc[VEOF])
break;
if (sigint_enabled && buffer[0] == saved.c_cc[VINTR])
exit(128 + SIGINT);
if (sigquit_enabled && buffer[0] == saved.c_cc[VQUIT])
exit(128 + SIGQUIT);
}
if (write(ofd, buffer, nbytes) != nbytes)
err_syserr("failed to write %d bytes to %s: ", nbytes, filename);
}
}
所使用的库代码可在 GitHub 上的 SOQ(堆栈溢出问题)存储库中以文件 stderr.c、stderr.h 和 posixver.h 的形式在 libsoq 子目录中找到。
这可以解决大多数粗心的陷阱。当终端退出时,它会尽最大努力将终端重置回初始(“已知良好”)状态。它确实模拟了 EOF、中断和退出键盘信号,但它不模拟常规的终端处理,例如擦除或终止。
当标准输入不是终端时使用它是没有意义的,但是代码也应该可以处理它(它只是进行正常读取)。您可以将输出发送到标准输出(默认)或文件(-o file 用于创建或截断文件,-a file 用于附加或创建文件)。