3
14
2017
21

我的 zsh 提示符

这是我用了多年的 zsh 提示符。

My zsh prompt

右提示符比较简单,先说。

首先,这个右提示符是 zsh 才支持的,不是 hack 左提示符来的哦。

我的右提示符显示的是(提示符打印出来时的)时间。在有后台任务时,会在左边以黄色显示出后台任务的数量,增加些许后台默默工作的进程的存在感啦。

截图中可以看到,只有最后一行才显示了右提示符(以至于我截图都得 hack 一下)。我使用了setopt transient_rprompt,这样 zsh 会清掉旧的右提示符,就不会影响复制了。以前每次复制时都带上一堆空格然后几个时间,折行之后根本没法看,后来才发现体贴的 zsh 已经有这么个选择了。

另外,在输入命令到右提示符时,右提示符会自动消失,以免和命令混淆。都说了很体贴的哦~

左边,是一个两行的提示符。之所以做成两行,是为了保持命令的起始位置不会因为提示符的长度变化而变化,每次输入新命令的时候,光标都在同一列,易读好找。我就不明白,那些坚持 bash 默认提示符的人是怎么坚持下来的,用着用着不知道自己光标去哪里了……对了,zsh 在输出提示符时,会保证它从终端最左边那一列开始输出。如果上一行不完整,zsh 会打印一个反色的「%」来表示(截图里 ^C 那里就有一个)。

蓝色「>>> 」是学 Python 的,但是使用了蓝色以免和 Python 混淆。如果是 root 用户,则显示红色的「### 」以警示。这个比较刺眼,所以就尽量不用 root 跑 shell 啦。

第一行开头是命令序号,就是历史记录里有多少条命令。每执行一条命令它就会加一,空行或者 Ctrl-C 放弃的不算。其实没什么用的样子。

然后是一个用于标识不同机器的名字。比如这里 lilywork 表示我正在我的工作机上。我家里那个系统里不会显示这个。这个信息可以通过ZSH_PS_HOST变量来设置,比如一般可以设置成$(hostname)。GitLab 之前的提示符里大概没有这个吧。

再就是最后一条命令的状态码($?)。如果命令成功就不显示,否则显示一个红色的数字,以提示上条命令出错了。所以说了嘛,我没法理解坚持使用 bash 及其默认提示符的人……

然后是缩短过的当前目录。~tmp是我的临时目录,有名字(hash -d tmp=....)的。但是它不会缩短中间路径的名字,反正我在它下边写命令,不用担心路径太长。不过我不建议深入探索 nodejs 的模块树,显示好几行的路径并不好看的。

最后一项又是可选的,git 当前分支。这个功能是我自己写的,不是 zsh 自带的那个,是异步显示的哦~忙着干活呢,不能在这种小事上浪费时间、中断思绪嘛。并且还可以通过设置来排除一些目录,比如访问特别慢的远程目录,比如已经死掉很久的 Wuala。

显示的信息不多,也一点都不华丽,但十分有用呢。

介绍完毕,提示符的定义我这里就不写啦。代码都在这里:https://github.com/lilydjwg/dotzsh

Category: shell | Tags: zsh linux
6
17
2016
7

Linux 作业控制实践

事情的起因是这样子的。

有一个非常常用的调试工具叫 strace。输出的信息是纯文本,一大片看起来累。在 Vim 里可以给它高亮一下,就好看多了。再加上各种搜索、清理,以及非常赞的 mark.vim 插件,用起来就舒服多了!

然而我并不想每次都让 strace 写到文件里然后再拿 Vim 去读,因为还得记着清理那些文件。如果数据量不大的话,直接通过管道传给 Vim 多好。

于是有了如下 zsh 函数:

(( $+commands[strace] )) && strace () { (command strace "$@" 3>&1 1>&2 2>&3) | vim -R - }

效果是达到了,但是这样子要中断 strace 的话,得去另一个终端里去 kill。按 Ctrl-C 的话,SIGINT 也会被发给 Vim,导致 Vim 显示空白。

所以嘛,得把 Vim 放到一个单独的进程组里,这样就不会在 Ctrl-C 的时候收到 SIGINT 了。但是,Vim 还得用终端啊。

一开始,我用自制的 expect.py 模块,给 Vim 分配了一个新的终端。这样子 Ctrl-C 好用了。然后我发现 Ctrl-Z 不好用了……

Ctrl-Z 还是挺方便的功能,临时需要执行个命令,不用开新的 shell(以及 ssh),直接按一下 Ctrl-Z,完事之后再回来,多好啊!就跟 zsh 的 Alt-q 一样方便好用呢。

于是就想还是不开 pty 了。直接子进程放新组里跑。这样 Vim 在尝试向终端输出时会收到 SIGTTOU 信号,因为它不是前台进程组。找了一下,用 tcsetpgrp 就可以把指定进程组放到前台了。然后发个 SIGCONT 让可能已经停下来了的 Vim 继续。

然后,当 Vim 收到 SIGTSTP 而停止的时候,我的程序该怎么知道呢?搜了一下,原来这种情况下也会收到 SIGCHLD 的!我以前一直以为只有子进程退出才会收到 SIGCHLD 啊……然后是一个关于 SIGCHLD 的坑,之前在 pssh 里看到过的,这次没有及时想到:不给 SIGCHLD 注册信号处理器时是收不到 SIGCHLD 的!不过诡异的是,我的这个程序有时却能够收到——在我使用 strace 跟踪它的时候……

于是,当 Vim 收到 SIGTSTP 时,把我们自己设置成前台进程组,然后给自己发一个 SIGTSTP 也停下来好了。令人意外的是,后台进程在调用 tcsetpgrp 时竟然也会收到 SIGTTOU。不过没关系,忽略掉就好了。

当用户 fg 时,就再把 Vim 设置成前台进程,并给它一个 SIGCONT 让它继续就好了。

最终的成品 vimtrace 在这里我的 zsh 配置是这样子的:

if (( $+commands[vimtrace] )); then
  (( $+commands[strace] )) && alias strace='vimtrace strace'
  (( $+commands[ltrace] )) && alias ltrace='vimtrace ltrace'
else
  (( $+commands[strace] )) && strace () { (command strace "$@" 3>&1 1>&2 2>&3) | vim -R - }
  (( $+commands[ltrace] )) && ltrace () { (command ltrace "$@" 3>&1 1>&2 2>&3) | vim -R - }
fi

后记:

strace 有时候还是会改变进程的行为的。这种时候更适合用 sysdig。Arch 刚刚更新的 sysdig 版本已经修正了崩溃的问题了~不过 Vim 对 sysdig 的输出就不像 strace 那样有好看的语法着色了。

其实我当时用 systemtap 来看信号发送情况更方便一些。不过那个需要内核调试符号,几百M的东西,装起来累啊……

Category: Linux | Tags: linux 终端 Python zsh
7
26
2015
1

一个简单的 zsh 模块

曾经,我让 Awesome 收养孤儿进程,以保持一个清晰的进程树。后来我又想让 zsh 也做这个 prctl 系统调用,免得子进程 fork 之后跑太远。比如 Wine 跑起来就好多个服务进程,如果不能把它们全部关掉的话,再启动另一个版本的 Wine 会出问题的。而当我启动好些个不同版本的 Wine 环境之后,只看到 Awesome 下边挂了一堆 Wine 的进程,却不知道哪些属于哪个 Wine 环境的了。

zsh 本身并不支持做这个调用,不过如同 Python 和 Lua 一样,zsh 也可以通过共享库来扩展功能。不同的是,zsh 模块是没有文档的……好在 zsh 源码里提供了一个 example 示例模块。把它改改就有了以下代码:

把这两个文档保存到 zsh 源码目录的Src/Modules下,可能还需要编辑一下config.modules文件,然后编译就可得 subreap.so 文件。把这个文件放到/usr/lib/zsh/$ZSH_VERSION/zsh/subreap.so然后就可以用了:

zmodload zsh/subreap
subreap

模块加载之后,多了个subreap内建命令。不带参数即调用prctl(PR_SET_CHILD_SUBREAPER, 1),这样不管其子进程怎么 fork,都会在此 zsh 的进程树之下。使用subreap -u来取消这个设置。

如果你不想编译而又是 Linux 64 位系统,可以试试我编译好的版本:下载地址, 签名, SHA1: 09eb1cc9ebf6ec1e681641c0a60f57425cbb1e8c。

Category: Linux | Tags: linux zsh C代码
2
19
2014
3

zsh 异步生成提示符

为什么要异步?当然是因为慢了。比如 Arch 核心仓库 git 版挺大的,第一次进去时显示个 git 分支名要等好一会儿。今天在 zsh-users 列表中看到 Bart Schaefer 给出了一个使用 coprocess 的解决方案,眼前一亮,立即照葫芦画瓢给自己的 zsh 用上了。以下是整个提示符设置部分的代码:

if [[ -n $commands[git] ]]; then
  _nogit_dir=()
  for p in $nogit_dir; do
    [[ -d $p ]] && _nogit_dir+=$(realpath $p)
  done
  unset p

  typeset -g _current_branch= vcs_info_fd=
  zmodload zsh/zselect 2>/dev/null

  _vcs_update_info () {
    eval $(read -rE -u$1)
    zle -F $1
    exec {1}>&-
    zle reset-prompt
  }

  _set_current_branch () {
    cwd=$(pwd -P)
    for p in $_nogit_dir; do
      if [[ $cwd == $p* ]]; then
        return
      fi
    done

    setopt localoptions no_monitor
    coproc {
      _br=$(git branch --no-color 2>/dev/null)
      if [[ $? -eq 0 ]]; then
        _current_branch=$(echo $_br|awk '{if($1 == "*"){print "%{\x1b[33m%} (" substr($0, 3) ")"}}')
      fi
      # always gives something for reading, or _vcs_update_info won't be
      # called, fd not closed
      typeset -p _current_branch
    }
    disown %{\ _br
    exec {vcs_info_fd}<&p
    # wait 0.1 seconds before showing up to avoid unnecessary double update
    # precmd functions are called *after* prompt is expanded, and we can't call
    # zle reset-prompt outside zle, so turn to zselect
    zselect -r -t 10 $vcs_info_fd 2>/dev/null
    zle -F $vcs_info_fd _vcs_update_info
  }

  typeset -gaU precmd_functions
  precmd_functions+=_set_current_branch
  setopt PROMPT_SUBST
fi

[[ -n $ZSH_PS_HOST && $ZSH_PS_HOST != \(*\)\  ]] && ZSH_PS_HOST="($ZSH_PS_HOST) "

E=$'\x1b'
PS1="%{${E}[2m%}%h $ZSH_PS_HOST%(?..%{${E}[1;31m%}%?%{${E}[0m%} )%{${E}[32m%}%~\$_current_branch
%(!.%{${E}[0;31m%}###.%{${E}[1;34m%}>>>)%{${E}[0m%} "

比较坑的是使用chpwd_functions的话只能在目录改变时显示一次,再随便执行个什么命令分支提示就没了。又想到目录不改变的时候分支也可以变化(切换分支了嘛),所以使用precmd_functions,每次显示提示符前(单纯的重绘除外)都执行一次。另外,为了避免每次显示提示符时都明显地分为两步干扰视线,所以在那个_set_current_branch函数里等了 0.1 秒,超时才会不管分支名显示先继续了。

2014年2月24日更新:注意,直到 zsh 5.0.5(就是当前最新版本)有个 bug,在显示提示符之后、用户输入之前,上述代码会经常出现「忙等待」的情况浪费 CPU。这里有个补丁可以修复。

Category: shell | Tags: linux Git zsh
11
14
2013
2

zsh 按 shell 参数移动

很早以前,我就想,在命令比较长的时候,M-fM-b按单词移动太慢了,特别是遇到长的 URL 或者文件名的时候。用鼠标吧,选择文本又比较麻烦了。所以很希望按 shell 参数来移动的功能,甚至尝试自己写过,但是因为对 zsh 了解太少,终究移动不正常。

昨天夜读 zsh 手册时才发现,原来,我曾见过这个功能的背影。

文档 26.6.1 节(「User Contributions」->「ZLE Functions」->「Widgets」)第一个,讲的是「bash-style word functions」。之前我也在哪里看到过,但是不知道其实这家伙支持好几种风格。使用以下配置就可以把 ZLE 里原来的「单词」概念变成 shell 解析出来的参数了:

autoload -Uz select-word-style
select-word-style shell

但是,我不想替换掉默认的,而是使用另外的键来这样子移动。研究了下代码,最终弄出来了:

# move by shell word {{{2
zsh-word-movement () {
  # see select-word-style for more
  local -a word_functions
  local f

  word_functions=(backward-kill-word backward-word
    capitalize-word down-case-word
    forward-word kill-word
    transpose-words up-case-word)

  if ! zle -l $word_functions[1]; then
    for f in $word_functions; do
      autoload -Uz $f-match
      zle -N zsh-$f $f-match
    done
  fi
  # set the style to shell
  zstyle ':zle:zsh-*' word-style shell
}
zsh-word-movement
unfunction zsh-word-movement
bindkey "\eB" zsh-backward-word
bindkey "\eF" zsh-forward-word
bindkey "\eW" zsh-backward-kill-word

只绑了M-BM-FM-W这三个含大写字母的组合键。其它-match函数的功能以后用到时再加好了。

Category: shell | Tags: zsh shell
9
16
2013
64

为 Kindle 交叉编译 Zsh 和 Python 3.3

一些天前,根据加州旅客的文章《kindle paperwhite越狱更换屏保》获得了自己的 Kindle Paperwhite 的 root 权限,就开始想着给它编译些东西了。恰巧小虾也在玩交叉编译,而且是 Python,于是自己也照着编译。

交叉编译 Zsh

交叉编译 Python 比编译 zsh 要难不少,所以,先简要说一下 zsh 等能够「无障碍交叉编译」的使用 Autotools 构建系统的软件是怎么编译的。

首先,弄清楚几个概念。「host」是指程序要运行的目标平台,「build」是指编译该程序的平台,而在编译编译器时会遇到的「target」则指的是其生成的文件所运行的平台。(参见CLFS 构建手册

其次,得找个交叉编译器。有些发行版可能仓库里就有。但是 Arch 没有,所以我还是用的 zshaolin 的这个工具链。下回来解压了,把它的bin目录加到$PATH里。Zsh 里可以这么写:

path+=/path/to/arm-dyne-gcc_64bit-x-arm7a-21jan12/bin

然后就可以去 zsh 源码目录下开始编译啦。还是那三步,只是参数有些不一样而已:

mkdir build-arm && cd build-arm
../configure --host=arm-dyne-linux-gnueabi --enable-multibyte --enable-pcre --with-term-lib='ncursesw'
make
make DESTDIR=/kindle/software/zsh-5.0.2 install

且慢!我指定了--enable-pcre,却没有去检查自己是否有这个库。可能是 bug 吧,zsh 的 configure 脚本并没有检测到我其实没有 pcre 的 ARM 版库文件,于是它接着在很多检测过程中加入了-lpcre,导致检测结果与实际不符(比如它认为我的目标系统上没有setpgid()函数)。编译安装 pcre 到工具链的 sysroot 下后再次编译通过。

再编译一些需要的库

所以你看,使用 Autotools 构建系统的软件交叉编译起来挺容易的,加上需要的--host参数就好了。其它诸如 file、ncurses、readline 也是这么编译安装的。下边说说那些比较「个性」的软件的编译参数。它们基本上都必须在源码目录编译。

首先是 zlib:

CHOST=arm-dyne-linux-gnueabi ./configure
make && make DESTDIR=/kindle/software/zlib-1.2.8 install

TLS/SSL 很有用!OpenSSL:

CC=arm-dyne-linux-gnueabi-gcc LD=arm-dyne-linux-gnueabi-ld AR=arm-dyne-linux-gnueabi-ar RANLIB=arm-dyne-linux-gnueabi-ranlib ./Configure shared linux-armv4
make
make INSTALL_PREFIX=/ldata/media/temp/kindle/software/openssl-1.0.1e install_sw

注意最后不是make install哦,那个会安装一堆不需要的文档的。

对了,我好像忘记说了,以上软件make install之后的操作

  1. 删除文档,比如 rm -rf share/man
  2. 复制到工具链的 sysroot 下,命令:
    tar c . | tar xv -C /path/to/arm-dyne-gcc_64bit-x-arm7a-21jan12/arm-dyne-linux-gnueabi/sysroot
    
  3. 给二进制文件们减肥啦(以下命令需要 zsh;非 zsher 请自行使用合适的 find 命令代替):
arm-dyne-linux-gnueabi-strip **/*(*)

另外再附上 ncurses 的配置命令好了:

../configure --host=arm-dyne-linux-gnueabi --with-shared --with-normal --without-debug --without-ada --enable-widec --enable-pc-files --prefix=/usr/local

是的,我把软件都安装到/usr/local了。在编译 Python 3.3 时的事实表明,这是给自己找麻烦……

编译 Python 啦

好了,准备工具完毕,我们来真正开始编译 Python 啦

按照小虾的文章,首先修改Modules/Setup.dist文件,把需要的模块去掉注释。一定要把最后的xxsubtype给注释掉!因为它只是很无聊的示例模块……

除了要安装模块对应的程序库外(比如要 curses 模块就得先安装 ncurses 等),还要注意一点:如果开启 readline 支持的话,把它后边的-ltermcap删掉!

开始配置了:

mkdir build-arm && cd build-arm
echo ac_cv_file__dev_ptmx=yes > config.site
echo ac_cv_file__dev_ptc=no >> config.site
export CONFIG_SITE=config.site
../configure --host=arm-dyne-linux-gnueabi --build=arm --enable-shared --disable-ipv6

根据实际 ssh 过去的结果,我的 Kindle 有/dev/ptmx但是没有/dev/ptc,所以往config.site文件里写上那么两句。没办法,交叉编译时脚本不知道目标系统里是否有这两个设备文件。当然,你都设置成no也是没什么问题的。

--build=arm这系统纯粹是 Python 的这个配置脚本要求的,和之前所说的常见使用方法不一样的。IPv6 支持需要的某函数配置脚本找不到,那就禁用掉好了。

配置完成,开始 make?

根据所开启的模块支持不同,在编译过程中很有可能地,你会遇到Parser/pgen无法执行的问题。它被编译成 ARM 版了,当然无法执行了!解决方案是这样子的:

修改pyconfig.hSIZEOF_LONG为正确值(比如 64 位 x86 下是8)。如果已经是对的就不要动了。然后重新生成个本地可运行的pgen

rm Parser/*
make CC=gcc Parser/pgen

接着把pyconfig.h改回去。然后,为了避免pgen被重建,我们让 make 认为pyconfig.h没有被修改过:

touch -t 200001010000 pyconfig.h

继续编译!

如果又出来了架构不对的情况,删除刚刚编译pgen时编译出来的目标文件吧:

rm Parser/*.o
touch -t 210001010000 Parser/pgen

touch pgen 的原因是,不能让 make 又把它编译成本地不能运行的 ARM 架构的了。

架构不对的目标文件可能还有一些,按照错误提示删掉就好了。

最后,要生成 Python 的可执行文件啦!很可能地,你会遇到类似这个的错误(我自己这里的错误信息已经没啦,下边这个由加州旅客提供):

libpython3.3m.a(timemodule.o):在函数‘py_process_time’中:
/home/jiazhoulvke/Python-3.3.2/./Modules/timemodule.c:1076:对‘clock_gettime’未定义的引用
/home/jiazhoulvke/Python-3.3.2/./Modules/timemodule.c:1082:对‘clock_getres’未定义的引用

查阅clock_getres的 man 文档得知:

Link with -lrt (only for glibc versions before 2.17).

于是,复制 make 最后执行的那条链接命令,在后边加上lrt吧。如果crypt没有定义的话,还要加上-lcrypt

终于可以安装啦

接下来,当然是把程序安装到 Kindle 上啦!首先执行个make DESTDIR=xxx install安装到某个目录,然后进去清理下吧:

arm-dyne-linux-gnueabi-strip **/*(*)
cd lib/python3.3
# 删除所有你不想要的模块,比如测试代码(`test`,不是`unitest`哦)、tk/idle,
# 还有 distutils 里一堆乱七八糟的东东

# 删除 Python 源码和 pyc 文件,我们只要 pyo 文件就好啦=w=
rm **/*.pyc?
# 在 zip 文件里 Python 可不认 __pycache__……
perl-rename 's=__pycache__/([^.]+).cpython-33.pyo$=\1.pyo=' **/*.pyo
rmdir **/*(/)
zip -9r ../python33.zip .

然后,把生成的python33.zip放到 Kindle 的/usr/local/lib目录下,Python 二进制文件也放到对应的位置。记住,库文件要使用 tar 而非 scp 来传输!像这样子:

tar c libz.so* | ssh kindle tar xv -C /usr/local/lib

其实不少库 Kindle 上已经有了,比如这里的 zlib。不过很奇怪,使用系统自带的 zlib 运行 Python 时会报如下警告:

python3: /usr/lib/libz.so.1: no version information available (required by /usr/local/lib/libpython3.3m.so.1.0)

另一个我发现无关紧要的警告是让你设置PYTHONHOME环境变量的:

Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]

其实不设置也是没关系的。

哦对了,现在我们的 Python 应该还跑不起来的吧!有可能缺少一点库文件的哦!把之前编译生成的对应的库文件全部拿tar扔到/usr/local/lib下吧。再说一遍,使用scp传输的话软链接会变成其指向的文件,浪费掉 Kindle 上宝贵的存储空间!

库文件扔进去之后,首先确认/etc/ld.so.conf里已经包含了/usr/local/lib,然后执行下ldconfig

终于,我们的 Python 在 Kindle 上跑起来啦!

后记,及下载链接

这个是我编译的 Python 的文件大小:

-rwxr-xr-x 1 root root 5.4K Sep 15 00:15 /usr/local/bin/python3.3
-r-xr-xr-x 1 root root 4.0M Sep 15 00:15 /usr/local/lib/libpython3.3m.so.1.0
-rw-r–r– 1 root root 2.2M Sep 15 00:43 /usr/local/lib/python33.zip

主要支持特性有:SSL、readline、ncurese、zlib、中日编码集、Unicode 数据库等。最后再放百度网盘下载链接(包括好些东东哦)。

最后我要说一句,Kindle 才是真正的 Linux 啊!编译起来如此方便!还各种常见库(包括 glibc、zlib、OpenSSL、GTK 2)都有。想之前给 Android 编译点东西得砍掉多少特性啊!又有多少软件死活编译不成功 :-(

PS: Kindle 虽然也用 Java 的,但是它有 X Window,还有 GTK 2 以及 Awesome 窗口管理器哦~可惜它的 Awesome 没开启 D-Bus 支持。

更新:lxml

今天(2013年9月17日),成功编译了 Python 最著名的 XML 处理模块——lxml。编译方法是,指定CC环境变量,复制python3 setup.py build时出错的那两条编译命令并修改,编译出来目标文件存起来。将CC指定为自己的脚本来「生成」它想要的文件。链接时手动改命令链好就行。

因为有 .so 文件,lxml 不能打成 zip 包。因此我直接将*.pyc*.pyo文件连同.so文件一同复制到 Kindle 的/usr/local/lib/python3.3/lxml下。由于版本不匹配,需要把libxml2.so.2.9.1文件也传到 Kindle 中去,覆盖了 Kindle 中旧版本的库文件,希望不会有问题

Category: Linux | Tags: python zsh 交叉编译 kindle
7
3
2013
7

手动保存/读取 zsh 历史记录

关于历史记录,zsh 有很多选项。我的配置是:

HISTFILE=~/.histfile
HISTSIZE=10000
SAVEHIST=10000

# 不保留重复的历史记录项
setopt hist_ignore_all_dups
# 在命令前添加空格,不将此命令添加到记录文件中
setopt hist_ignore_space
# zsh 4.3.6 doesn't have this option
setopt hist_fcntl_lock 2>/dev/null
setopt hist_reduce_blanks

最多保留一万行不重复的历史记录。对其的读取和保存没做额外的配置,因此 zsh 会在启动时读取一次,在退出时保存一次。这样,如果同时开了多个 zsh,它们不会共享启动后的历史记录项,因为还没有写到文件中去。

其实是有选项来方便在多个 zsh 中及时共享历史记录的:

setopt SHARE_HISTORY

但是这样的话,每次显示提示符时 zsh 均会读取一次历史记录,而每当新的历史记录产生时 zsh 都会写入一次。磁盘 I/O 太频繁了,我不喜欢。我只需要在我想的时候,能够手动保存和读取历史记录就可以了。读过长长的文档,发现fc可以做到这点:

# 读取历史记录
fc -IR
# 保存历史记录
fc -IA

-I表示「incremental」,只有新的项目被处理。-R是读取,而-A是写入。千万不要用-IW,这样会丢失原有的历史记录。

Category: shell | Tags: zsh
5
8
2013
3

编译 Android 版 zsh,以及 strace

说明一下,示例中使用的是 zsh 语法。bash 用户的话——先在电脑上用上 zsh 再考虑给手机装吧 ^_^

在网上能够找到 zshaolin 这么个包含 zsh 的 Android 包,但是它是收费的。于是,在成功编译了不少 Android 的东东后, 我决定自己编译个 zsh。

首先,把 Android NDK 放到 $PATH 里来:

path+=/opt/android-ndk/toolchains/arm-linux-androideabi-4.7/prebuilt/linux-x86/bin

其次是./configure命令。其中那个LDFLAGS的路径里有我之前编译的 ncurses。注意这里必须指定使用 ncurses 库,否则会莫名其妙地失败。使用 Android 自己的 Bionic 这个 C 库的话,就只好禁用多字节字符的支持了。

另外,ncurses 编译的时候记得禁用 C++ 支持。

LDFLAGS=-L/ldata/media/temp/android/installed_binaries/lib \
  CC='arm-linux-androideabi-gcc --sysroot=/opt/android-ndk/platforms/android-14/arch-arm' \
  ./configure --host=arm-linux-gnu --prefix=/system --with-term-lib=ncurses \
  --disable-multibyte --bindir=/system/xbin

然后做点小修改,将Src/init.cTIOCSETD相关的两处代码注释掉,不然程序会卡在这个ioctl调用上。还要把所有_mktemp函数调用换掉。在config.h中注释掉HAVE__MKTEMP宏的定义即可。

接下来是编译。完成之后修改最后一行链接代码,将 ncurses 静态链接进去:

arm-linux-androideabi-gcc --sysroot=/opt/android-ndk/platforms/android-14/arch-arm \
  -L/ldata/media/temp/android/installed_binaries/lib -rdynamic \
  -o zsh main.o `cat stamp-modobjs` -ldl -lm -lc /ldata/media/temp/android/installed_binaries/lib/libncurses.a

然后make install安装,把生成的zsh可执行文件弄到手机上任何你喜欢放可执行文件的地方,share目录弄到配置的目录里去(我这里是/system)。或者我猜设置fpath变量也行?

我第一次编译成功的 zsh 没有去掉TIOCSETDioctl调用,于是启动时卡在那里一动也不动了。要调试这种情况当然是 strace 了。但很不幸的是,strace 用到了太多 Bionic 不支持的宏定义、结构体成员等。后来我换了 zshaolin 使用的这个工具链静态链接了个 strace,终于可以用了。

最后,我编译好的二进制文件下载链接。要注意的是,此 zsh 并不支持多字节字符(如中文)(但是人家 Kindle 上的就支持呢)。

2014年3月8日更新:如果遇到任务控制(job control)时无法将后台任务切换到前台,打印出「unknown signal」的问题,可将Src/signal.c:435处的WCONTINUED改成WSTOPPED(注意不是WIFSTOPPED),即:

--- Src/signal.c
+++ Src/signal.c
@@ -432,7 +432,7 @@
          */
 #if defined(HAVE_WAIT3) || defined(HAVE_WAITPID)
 # ifdef WCONTINUED
-# define WAITFLAGS (WNOHANG|WUNTRACED|WCONTINUED)
+# define WAITFLAGS (WNOHANG|WUNTRACED|WSTOPPED)
 # else
 # define WAITFLAGS (WNOHANG|WUNTRACED)
 # endif

这个方案是我通过 strace 手机上的 busybox 的 sh 学来的 =w= 新编译的 zsh 5.0.5 可以由此下载

Category: Linux | Tags: linux Android zsh 交叉编译
3
19
2012
20

zsh 命令行编辑技巧三则

zsh 的命令行编辑使用的是 Zsh Line Editor(Zle),功能比 readline 强大不少,只是大量好用的功能都深埋于文档中,难得见识到。最近在看A User's Guide to the Z-Shell,虽然内容有些旧了,但依旧很有用。

首先说一点,以下内容均假定使用的是 Emacs 式键绑定。

暂停当前命令的编辑,先执行点其它命令。这个功能叫push-line,默认绑定在Alt-q。另有一个叫做push-line-or-edit的 widget,我把它绑过来了:

bindkey "\eq" push-line-or-edit

push-line widget 会将当前命令行上的内容放到一个栈上,显示一个新的提示符让你来执行点别的东西。比如刚写了一个长命令的一半,却发现当前目录不对。怎么办呢?readline 里我只好先Ctrl-u,执行之后再Ctrl-y粘贴回来。偶尔会找不到之前 kill 的内容。在 Zsh 里,按下Alt-q,当前命令暂存起来,你可以执行点别的命令,再显示命令提示符时,之前 push 走的命令内容会 pop 回来。而且这个操作是可以嵌套的,因为这是一个

push-line-or-edit widget 多了个 or-edit 后缀。当输入一个if或者for这样的命令时,你可以写成多行,zsh 会自动判断出你的命令尚未写完,显示$PS2提示符。这时,如果想修改之前的某一行怎么办呢?push-line-or-edit widget 会把这些行命令变成一个不带有$PS2提示符的多行命令,默认键绑定中,使用Ctrl-p/n或者方向键移动即可。这个就是 zsh 的多行编辑能力。如果你喜欢使用 zsh 编辑的话,可以试试zed这个运行于 zsh 中的简单文本编辑器:

autoload zed
zed some_small_text_file

按顺序执行若干条历史记录中的命令。比如我读取 3G 网卡短信使用如下的命令序列:

gnokii --smsreader
gnokii --getsms SM 0 end -f sms
smsmboxproc < sms > sms.mbox
mutt -f sms.mbox

如果使用Ctrl-r搜索历史的话,每条命令都搜索岂不麻烦?所以有了accept-line-and-down-history这个 widget,默认绑定于Ctrl-o。先在历史记录里找到第一条需要的命令,按下Ctrl-o,命令执行后,历史记录中的下一条就会出现了。然后接着按Ctrl-o,直到需要执行的命令序列到达最后一条,这次该按Enter了。

最后一个,你是不是经常往命令行上粘贴网址?是的话,你应该知道,网址得用引号括起来,以防止有些字符被 shell 解释了。zsh 带了个功能,可以检测出当前输入的是否是 URL,如果是的话就自动转义那些特殊字符。这样往命令行上粘贴 URL 时就不需要事先打好引号了。使用如下命令启用:

autoload -U url-quote-magic
zle -N self-insert url-quote-magic
Category: shell | Tags: zsh shell
8
24
2011
22

改变终端下的光标颜色,包括 screen 和 tmux!

曾经在Ubuntu中文论坛里看到一个改变光标颜色的方法,用光标颜色来指示是在 Vim 的普通模式还是插入模式下(因为 gnome-terminal 不支持使用转义序列改变光标形状)。Vim Wiki 上的 tip

if &term =~ "xterm\|rxvt"
  silent !echo -ne "\e]12;HotPink\007"
  let &t_SI="\e]12;RoyalBlue1\007"
  let &t_EI="\e]12;HotPink\007"
  autocmd VimLeave * :!echo -ne "\e]12;green\007"
endif

可惜它不适用于当时我正在使用的 screen。现在我改用 tmux 了,偶然改变TERM变量测试的时候,发现光标颜色竟然改变了——虽然还附带一些“不良反应”。我想到:一定有办法来正确地改变光标颜色的!

于是求助于 Google,很快找到了这个,有了用于 screen 的转义序列。不过依旧不适用于 tmux。把“tmux”也加到关键词里再搜,终于找到了这个。根据这个帖子,screen 和 tmux 比 xterm 多出来的那些字符序列是告诉 screen 或者 tmux 把其中的字符序列直接发送到终端模拟器处理。

于是,我的 vimrc 又可以更新了:

let color_normal = 'HotPink'
let color_insert = 'RoyalBlue1'
let color_exit = 'green'
if &term =~ 'xterm\|rxvt'
  exe 'silent !echo -ne "\e]12;"' . shellescape(color_normal, 1) . '"\007"'
  let &t_SI="\e]12;" . color_insert . "\007"
  let &t_EI="\e]12;" . color_normal . "\007"
  exe 'autocmd VimLeave * :!echo -ne "\e]12;"' . shellescape(color_exit, 1) . '"\007"'
elseif &term =~ "screen"
  if !exists('$SUDO_UID')
    if exists('$TMUX')
      exe 'silent !echo -ne "\033Ptmux;\033\e]12;"' . shellescape(color_normal, 1) . '"\007\033\\"'
      let &t_SI="\033Ptmux;\033\e]12;" . color_insert . "\007\033\\"
      let &t_EI="\033Ptmux;\033\e]12;" . color_normal . "\007\033\\"
      exe 'autocmd VimLeave * :!echo -ne "\033Ptmux;\033\e]12;"' . shellescape(color_exit, 1) . '"\007\033\\"'
    else
      exe 'silent !echo -ne "\033P\e]12;"' . shellescape(color_normal, 1) . '"\007\033\\"'
      let &t_SI="\033P\e]12;" . color_insert . "\007\033\\"
      let &t_EI="\033P\e]12;" . color_normal . "\007\033\\"
      exe 'autocmd VimLeave * :!echo -ne "\033P\e]12;"' . shellescape(color_exit, 1) . '"\007\033\\"'
    endif
  endif
endif
unlet color_normal
unlet color_insert
unlet color_exit

因为 tmux 的TERM变量和 screen 的一致,所以得使用TMUX变量来判断是在 tmux 里还是在 screen 里。

最后,说下指定颜色的方法。可以使用和 HTML 中一样的#rrggbb甚至简写#rgb,也可以使用颜色名。这里有个 xterm 的颜色名表。

2011年8月25日更新:

写了个 zsh 函数:

if [[ $TERM == xterm* ]] || [[ $TERM == *rxvt* ]]; then # {{{2 设置光标颜色
  cursorcolor () { echo -ne "\e]12;$*\007" }
elif [[ $TERM == screen* ]]; then
  if [[ -n "$TMUX" ]]; then
    cursorcolor () { echo -ne "\ePtmux;\e\e]12;$*\007\e\\" }
  else
    cursorcolor () { echo -ne "\eP\e]12;$*\007\e\\" }
  fi
fi
Category: Linux | Tags: vim 终端 screen zsh tmux

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com