简体   繁体   中英

How to detect if a command is interactive or not in linux

I want to save the output for specific subprocesses for later use. For this I use tee to show the output to stdout and a logging file. However when it comes to interactive commands like ncdu or htop or something like that it of course can't write that properly into a file. Therefore I want to be able to know if a command is interactive or not either before running it (which is not possible I think) or whilst reading the messed up logging file of such a command. I would assume that there is something that an interactive program writes to stdout that a normal command wouldn't and that would allow me to distinguish the two.

The "messed up output" is the output intermixed with terminal control sequences, most commonly ANSI escape codes .

For binaries that rely on Curses for terminal output, running the command with the TERM environment variable unset or set to an empty string, causes the command to fail. For example,

$ env TERM= htop
Error opening terminal: unknown.

This is because these standard libraries examine the TERM environment variable, to find the corresponding entry in the terminal database. (The tput utility uses that database too, so you can use tput clear to clear the terminal, tput reset to reset the terminal to a default state (useful if something garbles the terminal mode), and so on.)

To find out if command is compiled with ncurses/curses or terminal info database ( terminfo support, you can run eg ldd command | grep -qe 'libn*curses' -e 'libtinfo' && echo Yes || echo No . However, this does only tells whether these commands properly support different terminals, not whether they require a terminal to work.

Then there are also scripts and tools, like ls --color , which do not use curses or terminfo support, and instead emit ANSI control sequences directly. (In the case of ls , the sequences defined in the LS_COLORS environment variable are used, but only if the output is to a terminal. As mentioned by mmeisson in a comment to the original question, this is trivial to detect using eg isatty(STDOUT_FILENO) .)

In the case of htop , we cannot even use the dumb terminal (set TERM to dumb, before running the command, ie env TERM=dumb htop </dev/null &>output ), because htop renders the task list as all spaces! Very annoying. At least with top ( env TERM=dump top </dev/null &>output ) one gets nice, unadorned ASCII output.

The vanilla terminal might be sufficient. Before executing the child process or command, set TERM=vanilla and COLUMNS=80 , for example env TERM=vanilla COLUMNS=80 htop </dev/null &>output . However, although you now get more output from htop , newlines are still missing (because htop uses cursor movements exclusively, and the vanilla terminal does not have those).

There might be another terminal that works for you, say, one where all escape sequences generated would be easy to detect and filter out, but is complex enough for eg htop output to be complete. I don't know one, though. You could create one, and add it to one of the terminfo databases (say, as /etc/terminfo/e/easy ). (You can list all terminfo files your machine has using ls -1 {/etc,/lib,/usr/share}/terminfo/?/* | sed -e 's|^.*/||g' | sort . Mine has over 2700.)

There is a distinct, "proper" solution for this type of problem, but it isn't easy.

Instead of using just pipes to the subprocesses, you use a proper pseudoterminal interface, pty , specifically UNIX98 pseudoterminals via posix_openpt() , grantpt() , unlockpt() , and ptsname() . However, the master side – your process – must then behave like a real terminal, and handle all the control sequences it advertises it supports (by having the TERM environment variable set to the terminal type).

There are some terminal emulator libraries and code you can reuse for your own project; the ones that immediately come to my mind are VTE (which uses a GTK+ GUI widget for the terminal display) and xterm itself; the ctlseqs.txt in xterm sources is an excellent list of the actual sequences used in xterm variants. Another useful project is GNU screen .

Essentially, your program then becomes the terminal the command uses, and can provide the input to the command, and performs all changes the command wants to do on the terminal display. All you need to do is record those changes somehow. (This is often called scraping , but as the pseudoterminal master, you really just decide how you wish to store the terminal contents: as a movie-like playback, a single screen of some size, or some other way.)

Overall, I'd say the best option would be to use TERM=xterm or variant, or perhaps TERM=ansi , and just filter out or replace some/most/all of the escape sequences to get the output you want. Replace the "move cursor to" commands to newlines, followed by the proper number of spaces if the column is greater than 1 (leftmost column); and "clear screens" with one or more newlines. It's not perfect, but it should be doable as a simple state machine (that reads the terminal output character by character, and emits the filtered output, using standard <stdio.h> functions), and work well enough for most purposes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM