内容简介:tl;dr: We look at how Zsh and Fish is able to indicate a missing terminating linefeed in program output when the Unix programming model precludes examining the output itself.Most shells, including bash, ksh, dash, and ash, will show a prompt wherever the p
tl;dr: We look at how Zsh and Fish is able to indicate a missing terminating linefeed in program output when the Unix programming model precludes examining the output itself.
Most shells, including bash, ksh, dash, and ash, will show a prompt wherever the previous command left the cursor when it exited.
The fact that the prompt (almost) always shows up on the familiar left-most column of the next line is because Unix programs universally cooperate to park the cursor there when they exit.
This is done by always making sure to output a terminating linefeed \n
(aka newline):
vidar@vidarholen-vm2 ~ $ whoami vidar vidar@vidarholen-vm2 ~ $ whoami | hexdump -c 0000000 v i d a r \n
If a program fails to follow this convention, the prompt will end up in the wrong place:
vidar@vidarholen-vm2 ~ $ echo -n "hello world" hello worldvidar@vidarholen-vm2 ~ $
However, I recently noticed that zsh
and fish
will instead show a character indicating a missing linefeed, and still start the prompt where you’d expect to find it:
vidarholen-vm2% echo -n "hello zsh" hello zsh% vidarholen-vm2% vidar@vidarholen-vm2 ~> echo -n "hello fish" hello fish⏎ vidar@vidarholen-vm2 ~>
If you’re disappointed that this is what there’s an entire blog post about, you probably haven’t tried to write a shell. This is one of those problems where the more you know, the harder it seems ( obligatory XKCD ).
If you have a trivial solution in mind, maybe along the lines of if (!output.ends_with("\n")) printf("%\n");
, consider the following restrictions*:
- Contrary to popular belief, the shell does not sit between programs and the terminal. The shell has no ability to intercept or examine the terminal output of programs.
- The terminal programming model is based on teletypes (aka TTYs), electromechanical typewriters from the early 1900s. They printed letter by letter onto paper, so there is no memory or screen buffer that can be programmatically read back.
Given this, here are some flawed ways to make it happen:
-
The shell could use pipes to intercept all output, and relay it onto the terminal. While it works in trivial cases like
whoami
, some programs check whether stdout is a terminal and change their behavior, others go over your head and talk to the TTY directly (e.g.ssh
‘s password prompt), and some use TTY specificioctl
s that fail if the output is not a TTY, such as querying window size or disabling local echo for password input. -
The shell can
ptrace
the process to see what it writes where. This has a huge overhead and breakssudo
,ping
, and other commands that rely on suid. -
The shell can create a pseudo-tty (pty), run commands in that, and relay information back and forth much like
ssh
orscript
does. This is an annoying and heavy-handed approach, which in its ultimate form would require re-implementing an entire terminal emulator. -
The shell can use ECMA-48 cursor position reporting features:
printf '\e[6n'
on a supported terminal will cause the terminal to simulate user input on the form^[[y;xR
wherey
andx
is the row and column. The shell could then read this to figure out where the cursor is. These kinds of round trips are feasible, but somewhat slow and annoying to implement for such a simple feature.
Zsh and Fish instead have a much simpler and far more clever way of doing it:
$COLUMN-1
This solution is very simple because it only requires printing a fixed string before every prompt, but it’s highly effective on all terminals.
Why?
Let’s pretend our terminal is 10 columns wide and 3 rows tall, and a canonical program just wrote a short string with a trailing linefeed:
[vidar ] [| ] [ ]
The cursor, indicated by |
, is at the start of the line. This is what would happen in step 1 and 2:
[vidar ] [% |] [ ]
The indicator is shown, and since we have written exactly $COLUMN
characters, the cursor is after the last column. Step 3, a carriage return, now moves it back to the start:
[vidar ] [|% ] [ ]
The prompt now draws over the indicator, and is shown on the same line:
[vidar ] [~ $ | ] [ ]
The final result is exactly the same as if we had simply written out the prompt wherever the cursor was.
Now, let’s look at what happens when a program does not output a terminating linefeed:
[vidar| ] [ ] [ ]
The indicator is shown, but this time the spaces in step 2 causes the line to wrap all the way around to the next line:
[vidar% ] [ | ] [ ]
The carriage return moves the cursor back to the start of the next line :
[vidar% ] [| ] [ ]
The prompt is now shown on that line, and therefore doesn’t overwrite the indicator:
[vidar% ] [~ $ | ] [ ]
And there you have it. A seemingly simple problem turned out harder than expected, but a clever use of line wrapping made it easy again.
Now that we know the secret sauce, we can of course do the same thing in Bash:
PROMPT_COMMAND='printf "%%%$((COLUMNS-1))s\\r"'
* These same restrictions are reflected in several other aspects of Unix:
- While useful and often requested, there is no robust way to get the output of the previously executed command.
- It’s surprisingly tricky to take screenshots/dumps of terminals, and it only works on specific terminals.
- The phenomenon of background process output cosmetically trashing foreground processes is well known, and yet there’s no solution
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
企业应用架构模式
Martin Fowler / 人民邮电出版社 / 2009 / 79.00元
随着信息技术的广泛应用,系统需要处理的数据量越来越大,企业级软件开发已经渐成主流,而开发人员面临的困难与挑战也是显而易见的。更糟糕的是,这一领域的资料一直非常缺乏。 本书是软件开发大师Martin Fowler的代表作,采用模式的形式系统总结了业界多年积累的经验,被称为“企业级应用开发领域的圣经”,出版以来一直畅销不衰,至今仍然无可替代。作 者在精彩地阐述了企业应用开发和设计中的核心原则基础......一起来看看 《企业应用架构模式》 这本书的介绍吧!