Suppose I have a ncurses program which does some job on curses screen, and finally print something to stdout. Call this program c.c, compiled to a.out.
I expect cat $(./a.out) first fire up ncurses, after some action, a.out quits and print c.c to stdout, which is read by cat, and thus print content of file c.c.
#include <stdio.h>
#include <ncurses.h>
int main() {
initscr();
noecho();
cbreak();
printw("hello world");
refresh();
getch();
endwin();
fprintf(stdout, "c.c");
return 0;
}
I also expect ./a.out | xargs vim, ls | ./a.out | xargs less to work.
But when I type ./a.out | xargs vim, hello world never shows up. The command seems not executed in order, vim does not open c.c.
What is the correct way to make a ncurses program to work with other linux utils?
Pipes use the standard output (stdout) and standard input (stdin).
The simplest way - rather than using initscr, which initializes the output to use the standard output, use newterm, which allows you to choose the file descriptors, e.g.,
newterm(NULL, stderr, stdin);
rather than
initscr();
which is (almost) the same as
newterm(NULL, stdout, stdin);
By the way, when you include <ncurses.h> (or <curses.h>), there is no need to include <stdio.h>.
If you wanted to use your program in the middle of a pipe, that is more complicated: you would have to drain the standard input and open the actual terminal device. But that's another question (and has already been answered).
Further reading:
initscr, newterm, endwin, isendwin, set_term, delscreen -
curses screen initialization and manipulation routines
ncurses works by writing a bunch of ansi escapes to stdout, which the terminal will interpret. You can run ./a.out > file and then inspect the file to see what you're actually writing. It'll be immediately obvious why programs are confused:
$ cat -vE file
^[(B^[)0^[[?1049h^[[1;24r^[[m^O^[[4l^[[H^[[Jhello world^[[24;1H^[[?1049l^M^[[?1l^[>c.c
The correct way of doing this is to skip all the graphical/textual UI parts when you detect that stdout is not a terminal, i.e. it's consumed by a program instead of a user:
#include <unistd.h>
#include <stdio.h>
#include <ncurses.h>
int main() {
if(isatty(1)) {
// Output is a terminal. Show stuff to the user.
initscr();
noecho();
cbreak();
printw("hello world");
refresh();
getch();
endwin();
} else {
// Output is consumed by a program.
// Skip UI.
}
fprintf(stdout, "c.c");
return 0;
}
This is the canonical Unix behavior.
If you instead want to force your UI to be shown regardless, you can draw your UI on stderr.
Related
I have a C program which waits for user's input
#include <stdio.h>
int main()
{
getchar();
return 0;
}
Now I run it and input some Chinese characters like 测试测试. Then now I click backspace, I found I cannot erase these characters completely(some blank remained)
I found termios has a flag setting IUTF8, but why it doesn't work?
UPDATE ON 2022/12/31:
I am trying to describe my question more detailed, I have a program like this
Now I run it and enter some Chinese characters(without Enter key)
Then I keep clicking Backspace key(until nothing can be erased any more), but half of the content still display on my screen. It's so abnormal, how can I make the erase perform well?
I know it is a stupid question for you. I just want to make it more comfortable when typing some UTF8 characters(like Chinese characters).
I found the shell can handle this well, how can I do to make my program perform the same?
By the way, this is my locale output
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=zh_CN.UTF-8
LC_TIME=zh_CN.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=zh_CN.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=zh_CN.UTF-8
LC_NAME=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
LC_TELEPHONE=zh_CN.UTF-8
LC_MEASUREMENT=zh_CN.UTF-8
LC_IDENTIFICATION=zh_CN.UTF-8
LC_ALL=
Use GNU readline to provide a shell-like interface, with Tab autocompletion, correct input handling, et cetera.
To compile the following example program, make sure you have the libreadline-dev package installed. The readline library needed to run the program will already be installed, because so many applications that are installed by default require it already.
// SPDX-License-Identifier: CC0-1.0
// Compile using
// gcc -Wall -O2 $(pkg-config --cflags readline) example.c $(pkg-config --libs readline) -o example
#define _GNU_SOURCE
#include <stdlib.h>
#include <locale.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <stdio.h>
int main(void)
{
char *line;
setlocale(LC_ALL, "");
while (1) {
line = readline(NULL); // No prompt
// Input line is in 'line'; exit if end of input or empty line.
if (!line || *line == '\0')
break;
// Do something with 'line'
// Discard the dynamically allocated line
free(line);
}
return 0;
}
When using the GNU readline library, the library takes over the standard input, and handles character deletion (and many other things) at the terminal (termios) level. It works absolutely fine with file and pipe inputs as well, and is what e.g. bash shell uses for interactive input.
fgetc returns a different value for the enter key after
calling libreadline's rl_callback_handler_install(). It changes from line feed (\n) to carriage return (\r).
How is this possible? I've read the source but could not figure out what mechanism is used to achieve this.
Also, but less important, is this feature or a bug?
// compile with gcc -o main.o main.c -lreadline
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
static void foo_rl_callback(char *line)
{
// do stuff
}
static void get_enter_key(void)
{
printf("press enter!\n");
printf("fgetc=%d\n", fgetc(stdin));
}
int main(int argc, char *argv[])
{
printf("readline: %s\n", rl_library_version);
get_enter_key();
rl_callback_handler_install(NULL, foo_rl_callback);
get_enter_key();
rl_callback_handler_remove();
get_enter_key();
return 0;
}
output (assuming user only presses the enter key):
readline: 8.1
press enter!
fgetc=10
press enter!
fgetc=13
press enter!
fgetc=10
I'm not 100% sure but I believe this is being done by prepare_terminal_settings (there are several versions of that function; the link goes to the version that should be being used on any system shipped in the past 15 years or so).
This function uses tcsetattr to twiddle a whole bunch of flags that control the behavior of a Unix terminal or pseudo-terminal. In particular, it turns the ICANON bit off, which means, among many other things, that U+000D CARRIAGE RETURN coming down the serial line is not converted to U+000A LINE FEED anymore.
While readline is active, you should be using only the readline API to interact with the terminal, not fgetc(stdin).
This is my program code:
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
void function() {
srand(time(NULL));
while(1) {
int n = rand();
printf("%d ", n);
//sleep(1);
}
}
int main() {
pid_t pid;
pid = fork();
if (pid == 0) {
function();
}
}
With the sleep line commented out (as in the code above) the program works fine (i.e. it prints a bunch of random numbers too fast to even see if they are actually random), but if I remove the comment the program doesn't print anything and exits (not even the first time, before it gets to the sleep), even though it compiles without warnings or errors with or without the comment.
but if I remove the comment the program doesn't print anything and exits
It does not print, but it does not really exit either. It will still be running a process in the background. And that process runs your infinite while loop.
Using your code in p.c:
$ gcc p.c
$ ./a.out
$ ps -A | grep a.out
267282 pts/0 00:00:00 a.out
$ killall a.out
$ killall a.out
a.out: no process found
The problem is that printf does not really print. It only sends data to the output buffer. In order to force the output buffer to be printed, invoke fflush(stdout)
If you're not flushing, then you just rely on the behavior of the terminal you're using. It's very common for terminals to flush when you write a newline character to the output stream. That's one reason why it's preferable to use printf("data\n") instead of printf("\ndata"). See this question for more info: https://softwareengineering.stackexchange.com/q/381711/283695
I'd suspect that if you just leave your program running, it will eventually print. It makes sense that it has a finite buffer and that it flushes when it gets full. But that's just an (educated) guess, and it depends on your terminal.
it prints a bunch of random numbers too fast to even see if they are actually random
How do you see if a sequence of numbers is random? (Playing the devils advocate)
I believe you need to call fflush(3) from time to time. See also setvbuf(3) and stdio(3) and sysconf(3).
I guess that if you coded:
while(1) {
int n = rand();
printf("%d ", n);
if (n % 4 == 0)
fflush(NULL);
sleep(1);
}
The behavior of your program might be more user friendly. The buffer of stdout might have several dozens of kilobytes at least.
BTW, I could be wrong. Check by reading a recent C draft standard (perhaps n2176).
At the very least, see this C reference website then syscalls(2), fork(2) and sleep(3).
You need to call waitpid(2) or a similar function for every successful fork(2).
If on Linux, read also Advanced Linux Programming and use both strace(1) and gdb(1) to understand the behavior of your program. With GCC don't forget to compile it as gcc -Wall -Wextra -g to get all warnings and debug info.
Consider also using the Clang static analyzer.
(running on a mac) My C.sublime-build file looks like this:
{
"cmd" : ["gcc -Wall -g $file_name -o ${file_base_name} && ./${file_base_name}"],
"selector" : "source.c",
"shell": true,
"working_dir" : "$file_path"
}
and I have a simple program with the following code:
#include <stdio.h>
#include <unistd.h>
int main ( int argc, char *argv[] ) {
printf("hi\n");
fork();
printf("bye\n");
return 0;
}
and sublime will execute it and give me
hi
bye
hi
bye
while executing from the shell gives me the correct result,
hi
bye
bye
why is this happening?
According to ISO C:
Standard input and standard output are fully buffered, unless they
refer to a terminal device, in which case, they are line buffered.
When you're using ST3, it does not refer to a terminal device so it is fully buffered. It means hi\n and bye\n will be stored in buffer zone and fork()will copy them to child process. Then both of them will be output twice.
When you're using the shell, you're using a terminal device and it is line buffered. During thr execution, hi\n will be output firstly and buffer zone is flushed due to the \n. Then bye\n is send to buffer zone and will be output twice.
It may be that when sublime executes it that stdout, for whatever reason, is not using line buffered output but fully buffered output instead. So, when you fork() the child, the "hi\n" still resides on the child's FILE too. The output of both is only flushed when the programs exit and they both print the same output.
I have a c program running. I want to make the program sleep for certain period say 5 sec. I want this sleep to be induced from a text file containing command "sleep(5)". I want to pass this file through redirection operator (<) while executing the program
say ./a.out < samplefile.txt
This samplefile.txt contains sleep(5) command in it. I tried the above scenario but the c program is reading it as stream of characters like s,l,e,e,p which is not our intention.
could some one please help me on this issue.
You may make your program read the commands from the text file, parse them and behave as the commands say.
Instead of parsing, you may use the environment variables.
In your program, call to getenv in any place requires configuration. When calling to the program, make sure the environment is set with the required variables, or use this notation:
VAR1=VALUE1 VAR2=VALUE2 ./a.out
I'm not very clear what you want to achieve, but this may be a prompt.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char buf[512];
scanf("%s", buf);
if (!strcmp(buf, "sleep(5)")) {
printf("sleep...\n");
sleep(5);
}
printf("over\n");
return 0;
}