问题
Is it possible to get the output of a command - for example tar
- to write each line of output to one line only?
Example usage:
tar -options -f dest source | [insert trickery here]
and the output would show every file being processed without making the screen move: each output overwrites the last one. Can it be done?
Edit: we seem to have a working answer, but lets take it further: How about doing the same, but over 5 lines? You see a scrolling output that doesn't affect the rest of the terminal. I think I've got an answer, but I'd like to see what you guys think.
回答1:
Replace the newlines with carriage returns.
tar -options -f dest source | cut -b1-$(tput cols) | sed -u 'i\\o033[2K' | stdbuf -o0 tr '\n' '\r'; echo
Explanation:
cut -b1-$(tput cols)
: Truncates the output of tar if it is longer than the terminal is wide. Depending on how little you want your terminal to move, it isn't strictly necessary.sed -u 'i\\o033[2K'
: Inserts a line blank at the beginning of each line. The-u
option to sed puts it in unbuffered mode.stdbuf -oL sed 'i\\033[2K'
would work equally as well.stdbuf -o0 tr '\n' '\r'
: Usestr
to exchange newlines with carriage returns. Stdbuf makes sure that the output is unbuffered; without the\n
's, on a line buffered terminal, we'd see no output.echo
: Outputs a final newline, so that the terminal prompt doesn't eat up the final line.
For the problem your edit proposes:
x=0;
echo -e '\e[s';
tar -options -f dest source | while read line; do
echo -en "\e[u"
if [ $x gt 0 ]; then echo -en "\e["$x"B"; fi;
echo -en "\e[2K"
echo -n $line | cut -b1-$(tput cols);
let "x = ($x+1)%5";
done; echo;
Feel free to smush all that onto one line. This actually yields an alternative solution for the original problem:
echo -e '\e[s'; tar -options -f dest source | while read line; do echo -en "\e[u\e2K"; echo -n $line | cut -b1-$(tput cols); done; echo
which neatly relies on nothing except VT100 codes.
回答2:
Thanks to Dave/tripleee for the core mechanic (replacing newlines with carriage returns), here's a version that actually works:
tar [opts] [args] | perl -e '$| = 1; while (<>) { s/\n/\r/; print; } print "\n"'
Setting $|
causes perl to automatically flush after every print
, instead of waiting for newlines, and the trailing newline keeps your last line of output from being (partially) overwritten when the command finishes and bash prints a prompt. (That's really ugly if it's partial, with the prompt and cursor followed by the rest of the line of output.)
It'd be nice to accomplish this with tr
, but I'm not aware of how to force tr
(or anything similarly standard) to flush stdout.
Edit: The previous version is actually ugly, since it doesn't clear the rest of the line after what's been output. That means that shorter lines following longer lines have leftover trailing text. This (admittedly ugly) fixes that:
tar [opts] [args] | perl -e '$| = 1; $f = "%-" . `tput cols` . "s\r"; $f =~ s/\n//; while (<>) {s/\n//; printf $f, $_;} print "\n"'
(You can also get the terminal width in more perl-y ways, as described here; I didn't want to depend on CPAN modules though.
回答3:
tar -options -f dest source | cut -b1-$(tput cols) | perl -ne 's/^/\e[2K/; s/\n/\r/; print' ;echo
Explanations:
| cut -b1-$(tput cols)
This is in order to make sure that the columns aren't too wide.- (In perl -ne)
s/^/\e[2K/
This code clears the current line, erasing 'old' lines. This should be at the start of the line, in order to ensure that the final line of output is preserved and also to ensure that we don't delete a line until the next line is available. - (In perl -ne)
s/\n/\r/
Thetr
command could be used here of course. But once I started using perl, I stuck with it
PS To clarify: There are two distinct 'line-width' problems. Both must be solved. (1) We need to clear the lines, so that a short line isn't mixed up with older, longer lines. (2) If a line is very long, and is wider than the current terminal width, then we need to trim it.
来源:https://stackoverflow.com/questions/8748076/make-output-of-command-appear-on-single-line