Я хочу прочитать все, что находится на стандартном вводе, через 10 секунд, а затем сломаться. Код, который я смог написать до сих пор:
#include <stdio.h>
#include <stdlib.h>
int main() {
sleep(10);
char c;
while (1) { // My goal is to modify this while statement to break after it has read everything.
c = getchar();
putchar(c);
}
printf("Everything has been read from stdin");
}
Поэтому, когда буква «с» вводится до истечения 10 секунд, она должна напечатать «с» (после того, как sleep
будет выполнено), а затем «Все было прочитано со стандартного ввода».
До сих пор я пробовал:
c
EOF
-> getchar
и подобные функции никогда не возвращают EOF
для stdin
stat
для stdin
-> stat
-ing stdin
всегда возвращает 0 для размера (st_size
).@AndrewHenle Изменение char c;
на int c;
и while (1) {
на while ((c = getchar()) != EOF) {
не решило проблему для меня.
@AndrewHenle Чтобы уточнить, теперь я могу сделать echo "hello world" | ./myprogram
, а затем он печатает «hello world», а затем «Все было прочитано со стандартного ввода», но чтение из stdin
таким образом, а не пользовательский ввод в течение периода sleep
не является моей целью.
@user3121023 user3121023 Я знаю, что терминал обычно буферизуется. Мой вопрос в том, если я разбуферю его или нажму Enter, как я узнаю, что больше нечего читать?
@ user3121023 Я бы предпочел подход termios
. Не могли бы вы привести пример в ответе?
Вы можете использовать функцию select
, чтобы увидеть, есть ли что-то для чтения на стандартном вводе с тайм-аутом, который начинается с 10 секунд. Когда он что-то обнаруживает, вы читаете символ и проверяете наличие ошибок или EOF. Если все хорошо, то вы снова вызываете select
, сокращая время ожидания на прошедшее до сих пор время.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
struct timeval tdiff(struct timeval t2, struct timeval t1)
{
struct timeval result;
result.tv_sec = t2.tv_sec - t1.tv_sec;
result.tv_usec = t2.tv_usec - t1.tv_usec;
while (result.tv_usec < 0) {
result.tv_usec += 1000000;
result.tv_sec--;
}
return result;
}
int cmptimestamp(struct timeval t1, struct timeval t2)
{
if (t1.tv_sec > t2.tv_sec) {
return 1;
} else if (t1.tv_sec < t2.tv_sec) {
return -1;
} else if (t1.tv_usec > t2.tv_usec) {
return 1;
} else if (t1.tv_usec < t2.tv_usec) {
return -1;
} else {
return 0;
}
}
int main()
{
struct timeval cur, end, delay;
int rval, len = 0;
fd_set fds;
gettimeofday(&cur, NULL);
end = cur;
end.tv_sec += 10;
FD_ZERO(&fds);
FD_SET(0, &fds);
if (fcntl(0, F_SETFL, O_NONBLOCK) == -1) {
perror("fcntl failed");
exit(1);
}
do {
delay = tdiff(end, cur);
rval = select(1, &fds, NULL, NULL, &delay);
if (rval == -1) {
perror("select failed");
} else if (rval) {
char c;
len = read(0, &c, 1);
if (len == -1) {
perror("read failed");
} else if (len > 0) {
printf("c=%c (%d)\n", c, c);
} else {
printf("EOF\n");
}
} else {
printf("timeout\n");
}
gettimeofday(&cur, NULL);
} while (rval > 0 && len > 0 && cmptimestamp(end,cur) > 0);
return 0;
}
Обратите внимание, что это не определяет клавиши, когда вы их нажимаете, только после того, как вы нажали RETURN или закрыли стандартный ввод.
Это прекрасно работает, однако у меня есть несколько вопросов: 1) Как бы выглядел этот код, если бы задержка была разделена на значение, подобное delayInSeconds
, которое, возможно, даже принимало бы нецелочисленные значения? 2) Как насчет неблокирующего стандартного ввода? Когда я разблокирую свой стандартный ввод, символы печатаются, но программа никогда не останавливается / «тайм-аут» никогда не печатается.
Думаю, это правильное направление. однако: 1 - Вы, вероятно, хотите break;
по ошибке. 2 - ничто в вопросе не говорит, что мы можем предположить, что через 10 секунд закрывается и мы достигли EOF - вы также, вероятно, захотите использовать повторное открытие стандартного ввода с O_NONBLOCK
read(2)
вместо getchar()
, чтобы избежать каких-либо эффектов буферизации stdio.
@gurkensaas для переменной задержки, поместите часть секунд в delay.tv_sec
и часть микросекунд в delay.tv_usec
и добавьте те же суммы к end
(вам нужно будет проверить, больше ли end.tv_usec
1000000, и если да, вычтите это количество и добавьте 1 к end.tv_sec
Если для разблокированного стандартного ввода вы имеете в виду передачу ввода, он просто прочитает все и остановится, когда будет достигнут конец.
@root Проверка rval > 0
обрабатывает как тайм-аут, так и ошибку от select
, а проверка c != 1
обрабатывает ошибки от getchar
.
Нет, это не так.
Если ввод не содержит \n
, то в какой-то момент getchar()
заблокируется через 10 секунд. Или, если stdin постоянно записывается (навсегда), цикл while
может не завершиться через 10 секунд или вообще не завершиться. Если переключение контекста во время printf()
происходит в течение 10-секундной отметки, следующая итерация do
передаст отрицательное значение delay
и напечатает ошибку. Если после \n
в буфере stdio есть что-то, оно не будет напечатано на следующей итерации, если select()
столкнется с ошибкой/тайм-аутом.
@root хорошо, я не учел возможное бесконечное чтение на стандартный ввод, а также то, что потоки, отличные от переданного файла или терминала (т. Е. Сокета или какого-либо другого процесса), могут прекратить отправку данных без EOF. Обновлено, чтобы сделать стандартный ввод неблокирующим и удалить внутренний цикл, а также добавить проверку истечения времени перед вызовом select
.
Вот предложение, которое соответствует моей интерпретации ваших требований:
alarm()
устанавливает будильник на целое число секунд, и система генерирует сигнал SIGALRM
, когда время истекло. Сигнал тревоги прерывает системный вызов read()
, даже если данные не были прочитаны.signal()
в macOS Big Sur 11.7.1 ввод продолжался после сигнала будильника, что не помогло — использование sigaction()
дает вам лучший контроль./* SO 7450-7966 */
#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#undef sigemptyset /* MacOS has a stupid macro that triggers -Wunused-value */
static struct termios sane;
static void stty_sane(void)
{
tcsetattr(STDIN_FILENO, TCSANOW, &sane);
}
static void stty_raw(void)
{
tcgetattr(STDIN_FILENO, &sane);
struct termios copy = sane;
copy.c_lflag &= ~ICANON;
tcsetattr(STDIN_FILENO, TCSANOW, ©);
}
static volatile sig_atomic_t alarm_recvd = 0;
static void alarm_handler(int signum)
{
signal(signum, SIG_IGN);
alarm_recvd = 1;
}
static void other_handler(int signum)
{
signal(signum, SIG_IGN);
stty_sane();
exit(128 + signum);
}
static int getch(void)
{
char c;
if (read(STDIN_FILENO, &c, 1) == 1)
return (unsigned char)c;
return EOF;
}
static void set_handler(int signum, void (*handler)(int signum))
{
struct sigaction sa = { 0 };
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; /* No SA_RESTART! */
if (sigaction(signum, &sa, NULL) != 0)
{
perror("sigaction");
exit(EXIT_FAILURE);
}
}
static void dump_string(const char *tag, const char *buffer)
{
printf("\n%s [", tag);
int c;
while ((c = (unsigned char)*buffer++) != '\0')
{
if (isprint(c) || isspace(c))
putchar(c);
else
printf("\\x%.2X", c);
}
printf("]\n");
}
int main(void)
{
char buffer[2048];
stty_raw();
atexit(stty_sane);
set_handler(SIGALRM, alarm_handler);
set_handler(SIGHUP, other_handler);
set_handler(SIGINT, other_handler);
set_handler(SIGQUIT, other_handler);
set_handler(SIGPIPE, other_handler);
set_handler(SIGTERM, other_handler);
alarm(10);
size_t i = 0;
int c;
while (i < sizeof(buffer) - 1 && !alarm_recvd && (c = getch()) != EOF)
{
if (c == sane.c_cc[VEOF])
break;
if (c == sane.c_cc[VERASE])
{
if (i > 0)
i--;
}
else
buffer[i++] = c;
}
buffer[i] = '\0';
dump_string("Data", buffer);
return 0;
}
Сборник:
gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes -fno-common tensec53.c -o tensec53
Нет ошибок (или предупреждений, но предупреждения конвертируются в ошибки).
#undef
удаляет любое макроопределение sigemptyset()
, оставляя компилятору вызов фактической функции. Стандарт C требует, чтобы это работало (§7.1.4 ¶1). В macOS используется макрос #define sigemptyset(set) (*(set) = 0, 0)
, и GCC небезосновательно жалуется на то, что «правый операнд выражения запятой не имеет эффекта». Альтернативный способ исправить это предупреждение — проверить возвращаемое значение из sigemptyset()
, но это, возможно, более глупо, чем макрос. (Да, я недоволен этим!)sane
записывается значение атрибутов терминала при запуске программы — оно устанавливается вызовом tcgetattr()
в stty_raw()
. Код гарантирует, что sane
установлен перед активацией любого кода, который будет вызывать sttr_sane()
.stty_sane()
сбрасывает атрибуты терминала в нормальное состояние, которое действовало при запуске программы. Он используется atexit()
, а также обработчиками сигналов.stty_raw()
получает исходные атрибуты терминала, делает их копию, изменяет копию, чтобы отключить каноническую обработку (дополнительные сведения см. в разделе Канонический и неканонический ввод терминала) и устанавливает измененные атрибуты терминала.volatile sig_atomic_t
, вызвать signal()
с номером сигнала или вызвать одну из функций выхода. POSIX намного мягче — см. Как избежать использования printf() в обработчике сигнала?Подробнее.alarm_handler()
игнорирует дальнейшие сигналы тревоги и записывает, что он был активирован.other_handler()
игнорирует дальнейшие сигналы того же типа, сбрасывает атрибуты терминала в нормальное состояние и завершает работу со статусом, используемым для сообщения о том, что программа была завершена сигналом (см. POSIX shell Статус выхода для команд).getch()
считывает один символ из стандартного ввода, сопоставляя ошибки с EOF
. Приведение гарантирует, что возвращаемое значение будет положительным, как это делает getchar()
.set_handler()
использует sigaction()
для установки обработки сигнала. Использование signal()
в обработчиках сигналов немного лениво, но адекватно. Это гарантирует, что бит SA_RESTART
не установлен, поэтому, когда сигнал прерывает системный вызов, он возвращается с ошибкой, а не продолжает работу.dump_string()
записывает строку с любыми непечатаемыми символами, кроме пробелов, которые сообщаются как шестнадцатеричный escape.main()
настраивает терминал, обеспечивает сброс состояния терминала при выходе (atexit()
и вызовы set_handler()
с аргументом other_handler
) и устанавливает сигнал тревоги на 10 секунд.'\b'
, иногда delete '\177'
) и символ EOF и обрабатывает их соответствующим образом, в противном случае добавляя ввод в буфер.dump_string()
для вывода того, что было введено.timer_create()
, timer_delete()
, timer_settime()
(и, возможно, timer_gettime()
и timer_getoverrun()
), которые принимают значения struct timespec
для значений времени. Если они недоступны, вы можете использовать вместо них устаревшие функции setitimer()
и getitimer()
. Шаг timer_create()
позволяет вам указать, какой сигнал будет отправлен по истечении таймера — в отличие от alarm()
и setitimer()
, которые отправляют заранее определенные сигналы.Этот код доступен в моем репозитории SOQ (вопросы о переполнении стека) на GitHub в виде файла tensec53.c
в подкаталоге src/so-7450-7966.
Я бы добавил к анализу одну вещь: наличие сигнала тревоги — это то, что разблокирует read()
звонок в getch()
. Я бы также установил MIN
и/или TIME
вместо того, чтобы использовать специальные символы терминала по умолчанию.
@root — спасибо; Я сделал некоторый акцент на роли alarm()
, а также добавил информацию о времени долей секунды для будильника с помощью timer_settime()
и др. (или, используя устаревшую функцию, с помощью setitimer()
).
Проверка, является ли c EOF ->
getchar
и подобные функции никогда не возвращаютEOF
дляstdin
Это потому, чтоgetchar()
возвращаетint
, а неchar
. Втискивание возвращаемого значения вchar
лишает возможности обнаруживатьEOF
. Вам нужно заменитьchar c;
наint c;
.