Yakuake + dtach vs Screen + urxvt
Screen 无疑是每一个经常使用终端的用户的必备工具。它拥有许多非常实用的功能:
- 管理会话,可以 detach 一个会话,让它在后台运行,并在其他时间/地点 attach 原来的会话。这对于经常远程登录的用户来说非常方便。登录以后,运行一些程序, Screen 会保证在你注销以后程序仍然能继续运行,并且下次登录进来可以 attach 到原来的终端会话上 (也许那里正有一个运行了几个月的 Emacs 在等着你) 。
- 多标签功能。 Screen 可以为一个用户管理多个会话,并且每个会话里面都可以有许多“子会话”,就像许多 Tab 一样,而 Screen 强大的可定制功能让它可以真正显示一个“标签栏”,这正弥补了许多终端模拟器没有多标签功能的缺点。而且这些标签都是在 Screen 会话的管理下的,在另外的地方 attach 到这个会话上会得到同样的一系列标签。并且可以通过 Shell 里面的 Escape Sequence 来动态更改标签的标题。非常方便。
- 屏幕分割、历史存储、搜索以及复制粘贴。这些功能在一个 X 下的终端模拟器里面可以很轻松地完成,不过如果通过远程登录或者在一个纯字符界面下, Screen 就帮了大忙了。
- 快捷键、命令以及其他功能。
这里是一个典型的 Screen 会话的截图。这是我很喜欢的 rxvt-unicode 和 Screen 的搭配。
rxvt-unicode (urxvt) 本身是一个非常不错的程序,同时,它还有一个不错的功能,只要开启 secondaryScroll
URxvt.secondaryScroll: true
Screen 的历史就可以进入 urxvt 的历史里,换句话说,既可以使用 Screen 的回滚功能来查看历史,也可以使用 urxvt 提供的可以用鼠标拖拽的滚动条来查看历史。不过这样也有一些不方便的地方,因为 Screen 里面是可以有多个 tab 的,在 Screen 里面切换 tab 对于 urxvt 来说是不可见的 (听说 eterm 能和 Screen 交互,把 screen 的 tab 用 eterm 自己的标签栏表现出来,不过我没有尝试过) ,所以多个 tab 的历史会混杂进同一个 urxvt 的历史。这也是我尝试 yakuake + dtach 来代替 Screen 的一个直接原因。
Yakuake 是 KDE 下的一个基于 Konsole 的非常 Cool 的一个终端程序。它平时隐藏起来,当你按下快捷键 (默认是 F12
) 时从屏幕顶端滑下。非常方便,有种呼之即来,挥之即去的感觉。当然这并不是我选择 Yakuake 的主要原因,因为我使用 jump-or-exec
可以在 urxvt 上实现类似的功能 (事实上我为很多程序如 Emacs 、 QTerm 和 Firefox 等都配备了 jump-or-exec 的功能,因为我只需要它们的一个实例就够了) ,虽然不及 Yakuake 那么 Cool 。
Yakuake 基于 Konsole ,也就有了 Konsole 的许多优点:多标签、动态更改编码、字体以及颜色配置等。另外,新版本的 Yakuake 还支持屏幕分割功能。历史回滚和复制粘贴这种基本功能自然也不用说了。这些都实现了 Screen 提供的那些功能,唯一的例外就是会话管理。本来作为一个笔记本用户,很少从别的地方远程登录到自己的笔记本上,而且我也绝大部分时间都使用 X 界面,会话管理并不是必要。不过有时候会做一些奇怪的事情有可能让 X 崩溃掉,如果没有会话管理的话,X 崩溃掉了终端里面的程序自然要跟着退出了。正好有一个叫做 dtach 的程序,专门实现了 Screen 的 detach 和 attach 的功能,而抛弃了 Screen 的其他的“累赘”的功能。
现在看来 Yakuake 和 dtach 搭配起来好像是完美的 Screen + urxvt 的替代品一样。dtach 并不像 Screen 那样管理会话和历史,所以所有终端历史进入 Yakuake 的历史里面,使用 Yakuake 的滚动条来回滚查看。不过也由于 dtach 不管理会话,不能像 Screen 那样维护一个多标签。本来可以让 Yakuake 在每次新开 tab 的时候执行 dtach 新启动一个会话,但是 attach 原有的会话有些麻烦,始终不如 Screen 那样方便。后来我仔细想了想,其实执行重要的任务 (就是 X 崩溃了也要继续运行的) 的话,三个 tab 就够了。
于是我只要在启动的时候加载三个由 dtach 管理的会话即可,如果 shell 不够用了,我可以开更多的 tab ,它们不由 dtach 管理,我只要记住重要的任务在 dtach 会话下运行即可。事实上大部分时候包括重要的和不重要的工作,三个 shell 都够用了。
我使用这样一个脚本来启动 Yakuake
#!/bin/sh if [ -z "$1" ] then DIR=$PWD else DIR=$1 fi if dcop | grep -q yakuake then # yakuake running dcop yakuake DCOPInterface slotAddSession dcop yakuake DCOPInterface slotRunCommandInSession "cd $DIR" else yakuake # load my dtach sessions while ! dcop | grep -q yakuake do sleep 0.5 done dcop yakuake DCOPInterface slotRunCommandInSession "export DTACH=dtach && dtach -A ~/.dtach/term-0 /bin/zsh" sleep 0.5 dcop yakuake DCOPInterface slotSetSessionTitleText "[kid]" dcop yakuake DCOPInterface slotAddSession dcop yakuake DCOPInterface slotRunCommandInSession "export DTACH=dtach && dtach -A ~/.dtach/term-1 /bin/zsh" sleep 0.5 dcop yakuake DCOPInterface slotSetSessionTitleText "[kid]" dcop yakuake DCOPInterface slotAddSession dcop yakuake DCOPInterface slotRunCommandInSession "export DTACH=dtach && dtach -A ~/.dtach/term-2 /bin/zsh" dcop yakuake DCOPInterface slotSetSessionTitleText "[kid]" dcop yakuake DCOPInterface slotSelectSession 0 fi # show yakuake if [ "false" = `dcop yakuake 'yakuake-mainwindow#1' shown` ] then dcop yakuake DCOPInterface slotToggleState fi
如果 Yakuake 已经在运行了,则打开一个 tab , cd
到指定目录或当前目录 (这非常有用,用这个脚本替换掉 konsole 的话,就可以在 konqueror 文件管理器里面按 F4
来快速在 Yakuake 里面开一个新 tab 并转到该目录了) ,如果没有运行则启动它,并加载三个默认的 dtach 会话。其中与 Yakuake 的交互通过 dcop 来进行。KDE 提供的 dcop 让我们可以很轻松地在脚本里面调用 KDE 程序提供的复制功能。我把这个脚本链接到 ~/.kde/Autostart/
下面,在 KDE 启动的时候自动启动它。
Konsole 同样允许通过 Shell 里面的 escape sequence 来改变标题栏和标签栏。Yakuake 继承了更改标签栏的功能,但是由于 Konsole 内部实现的一些原因 (Konsole 正要经历巨大修整,参见 Konsole 大整修) Yakuake 不能使用 Konsole 的那种方法来设置标签标题。不过可以 hack 一下在更改标题的同时更改标签栏标题,下面是针对 Yakuake 2.8 的 patch :
diff -ur src/main_window.cpp src.orig/main_window.cpp --- src/main_window.cpp 2007-06-01 00:29:06.000000000 +0800 +++ src.orig/main_window.cpp 2007-06-01 00:10:27.000000000 +0800 @@ -342,8 +342,6 @@ connect(session, SIGNAL(destroyed(int)), this, SLOT(slotSessionDestroyed(int))); connect(session, SIGNAL(titleChanged(const QString&)), this, SLOT(slotUpdateTitle(const QString&))); - connect(session, SIGNAL(titleChanged(int)), this, SLOT(slotUpdateTitle(int))); - widgets_stack->addWidget(session->widget()); sessions_stack.insert(session->id(), session); @@ -783,10 +781,6 @@ title_bar->setTitleText(title); } -void MainWindow::slotUpdateTitle(int id) -{ - slotRenameSession(id, sessions_stack[id]->title()); -} void MainWindow::slotIncreaseSizeW() { diff -ur src/main_window.h src.orig/main_window.h --- src/main_window.h 2007-06-01 00:12:58.000000000 +0800 +++ src.orig/main_window.h 2007-05-06 08:32:58.000000000 +0800 @@ -217,8 +217,7 @@ void slotUpdateSize(); void slotUpdateSize(int new_width, int new_height, int new_location); void slotUpdateTitle(const QString& title); - void slotUpdateTitle(int id); - + void slotIncreaseHeight(); void slotDecreaseHeight(); void slotSessionDestroyed(int id = -1); Only in src: Makefile.am Only in src: Makefile.in diff -ur src/session.cpp src.orig/session.cpp --- src/session.cpp 2007-06-01 00:13:48.000000000 +0800 +++ src.orig/session.cpp 2007-05-06 08:32:58.000000000 +0800 @@ -395,7 +395,6 @@ { session_title = title; emit titleChanged( session_title); - emit titleChanged( session_id); } } diff -ur src/session.h src.orig/session.h --- src/session.h 2007-06-01 00:14:11.000000000 +0800 +++ src.orig/session.h 2007-05-06 08:32:57.000000000 +0800 @@ -72,8 +72,8 @@ signals: void destroyed(int id); void titleChanged(const QString&); - void titleChanged(int); - + + private: void createInitialSplits(SessionType); void split(QWidget* active_terminal, Orientation o);
之后在 Shell 的提示符里面做点手脚就自动更新标签栏了。我用的是 Zsh ,下面的配置可以在标签栏标题里面显示当前目录。如果是在一个 dtach 管理的会话里,则用方括号“[]”括起来 (在前面的启动 Yakuake 的脚本里面设置了 DTACH
环境变量用于识别是否在 dtach 会话里) :
if [[ -z "$DTACH" ]]; then P_TITLE=$'%{e]0;%1/a%}' else P_TITLE=$'%{e]0;[%1/]a%}' fi
有 Zsh 提供的功能,可以在执行某条命令之前先做一些动作,比如更新标签栏以显示出当前正在执行的命令:
CMD=${1[(wr)^(*=*|sudo|-*)]} if [[ -z "$DTACH" ]] then echo -n "e]0;$CMDa" else echo -n "e]0;[$CMD]a" fi
下面是我的 Zsh 的提示符的完整配置:
#!/bin/zsh autoload colors zsh/terminfo if [[ "$terminfo[colors]" -ge 8 ]]; then colors fi if [[ "$TERM" = "dumb" ]] then prompt='%(?..(%?%))%n@%1/ %(!.#.$) ' else local P_GREEN="%{$terminfo[bold]${fg[green]}%}" local P_CLEAR="%{$terminfo[sgr0]%}" local P_ERROR="%{$terminfo[bold]${fg[red]}%}%(?..(%?%))$P_CLEAR" local P_USER="%(!.${bg[green]}${fg[black]}ROOT.%n)$P_CLEAR@" local P_DIR="%1/" local P_PRO=" %(!.#.$) " local P_TITLE if [[ "$TERM" = "screen" ]]; then P_TITLE=$'%{ek%1/e\%}' elif [[ "$TERM" = "xterm" ]]; then if [[ -z "$DTACH" ]]; then P_TITLE=$'%{e]0;%1/a%}' else P_TITLE=$'%{e]0;[%1/]a%}' fi else P_TITLE="" fi prompt="$P_ERROR$P_USER$P_GREEN$P_DIR$P_CLEAR$P_PRO$P_TITLE" fi preexec () { local CMD=${1[(wr)^(*=*|sudo|-*)]} if [[ "$TERM" == "screen" ]]; then echo -n "ek$CMDe\" elif [[ "$TERM" == "xterm" ]]; then if [[ -z "$DTACH" ]] then echo -n "e]0;$CMDa" else echo -n "e]0;[$CMD]a" fi fi }
如下是一个效果截图。
我已经用了大约一个周了,感觉非常好,几乎从原来的 Screen + urxvt 平滑过渡过来。不过我也并不能说哪种方案更好。如果你绝大部分时间都是在 X 下渡过 (特别是,如果你使用 KDE) ,而且不经常远程登录的话,推荐使用 Yakuake + dtach 获得更好的用户体验。否则,还是推荐使用 Screen ,因为它具有更强大的会话管理能力。