<![CDATA[EverET.org]]> 2016-05-12T19:42:37+08:00 http://everet.org/ Octopress <![CDATA[ET 的利器们]]> 2016-05-12T17:06:00+08:00 http://everet.org/liqi 介绍一下你自己和所做的工作

程序猿一名,刚毕业就去了广州一家老牌游戏公司呆了些时间,后来出来到创业公司,第一家创业公司倒闭了,现在在第二家。

平时主要开发服务端程序,用Python,有时也写写Android,用Java和C++(反正都是Linux嘛)。

你都在使用哪些硬件?

笔记本是顶配的13寸的Macbook Pro,因为以前写C++每次编译都是痛苦的过程,所以买了顶配的CPU和内存。Time is money.

键盘是HHKB1,显示器自己买了DELL U2713HM,大屏用起来还是挺爽的,特别是用Lightroom处理照片的时候。

平时经常用笔写日记,觉得三菱的UB-150写起来还是很顺滑~ 自从用了就不想再用啥真彩晨光了。

软件呢?

Dev

主力软件还是被称为伪装成编辑器的操作系统——Emacs2,平时开发,编辑文件,写文章,都可以在Emacs里面完成。

CLI下面,主要是zsh和tmux+teamocil。zsh还是比bash强很多,特别是配合了oh-my-zsh后,就很顺手了。

tmux是一个牛逼的窗口管理器,又可以保存session,无论在服务器上面,还是在本地,都是提高工作效率的好帮手。

teamocil是用来辅助tmux的,可以定义好规则,快速组织开发环境。

常用

浏览和调试网页还是用Chrome,插件装了很多,主要推荐几个Sexy Undo Close Tab,Lastpass和印象笔记剪藏。

Evernote主要用来收集信息和整理信息,平时啥资料都往里面塞,有强大的搜索功能可以快速找到需要的资料。里面现在已经塞了1w多条笔记。

网盘还是用Insync,用来同步Google Drive。

Mac

Mac上有挺多好用的软件,

  • Manico:快速切换应用程序,加了快捷键Ctrl + [1,2,3,4 … 0 ]来切换程序,然后程序都固定在指定的桌面,所以切换还挺方便。
  • Karabiner:用来快速切换键盘布局,有时候连接了HHKB,就切换成默认的模式,有时用笔记本键盘,就用它快速把笔记本键盘布局切换成类似HHKB的布局。
  • popclip:神器。
  • Bartender:隐藏多余的顶部的图标。
  • Parallels Desktop:性能不错的虚拟机,主要用来跑同花顺。
  • Alfred:这个那么经典就不用介绍了。
  • Scroll Reverser:鼠标插入到Mac后,中键的滚动就和Windows和Linux上面反过来了,有些不习惯,就用它把滚动方向反过来了。

你最理想的工作环境是什么?

空旷的办公室,空荡荡的大桌子,大显示器,落地窗外加美好的风景。

当然还有最重要的——牛逼的小伙伴们。

你平时获得工作灵感的方式有哪些?

刷知乎,看微博,逛社区,当然还要经常线下和朋友交流交流。

推荐一件生活中的利器给大家

之前买了AKG的大耳机,发现在办公室基本没法用,于是狠下心来,下单买了主动降噪的Bose QC15(因为之前有在太古汇体验了一下Bose的主动降噪,挺震撼的-。-)。

Bose QC15,这个墙裂推荐,在嘈杂的办公室可以可以给你一片安静的天地欣赏音乐。

End

本文参与了「利器社群计划」,发现更多创造者和他们的工具:http://liqi.io/community/


PermaLink: http://everet.org/liqi.html
Tags: Emacs ]]>
<![CDATA[shadowsocks的多用户配置]]> 2014-11-02T12:04:00+08:00 http://everet.org/shadowsocks Shadowsocks作为一个开源的番羽土啬工具,还是非常不错的。如果我们在大陆外有自己的服务器,那么可以使用Shadowsocks就可以很方便地获得一个可靠的socks5代理。

一台服务器其实就可以开多个代理用,虽然可以多人使用同一个端口密码,但是感觉这样管理起来并不妥当,例如我想看看谁在使用代理,都无法区分。于是多用户就是必须的了。

咋一看官方文档,好像是没有多用户的配置,仔细看,其实还是可以做到的。

我们可以开多个端口,每个端口使用不一样的密码。我们假如把端口看做用户名,那么就可以有多用户啦!

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    "timeout": 600,
    "method": "aes-256-cfb",
    "port_password":
    {
        "40001": "password1",
        "40002": "password2",
        "40003": "password3"
    },
    "_comment":
    {
        "40001": "xiaoming",
        "40002": "lilei",
        "40003": "mike"
    }
}

这样,我们可以给每个不同的人,不同的端口和密码,就可以看到区分不同的人了。

然后我们写个脚本,使用比较简单的方法来加一个监控,每分钟统计一下,看看有谁在使用,然后记个log。

port-ip-monitor.shlink
1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
#
# File: port-ip-monitor.sh
#
# Created: Wednesday, August 27 2014 by Hua Liang[Stupid ET] <et@everet.org>
#

filename="port-ip-monitor.log"
regex="400[0-2][0-9]"  # monitor 40000-40029

date +"[%Y-%m-%d %H:%M:%S]" >> $filename
netstat -anp | egrep $regex | grep -E "tcp.*ESTABLISHED" | awk '{print $4, $5}' | cut -d: -f2 | sort -u >> $filename

修改crontab,加上:

port-ip-monitor.shlink
1
* * * * * (cd /var/log/ && /root/projects/port-client-ip-monitor/port-ip-monitor.sh)

然后我们在/var/log/port-ip-monitor.log便可以看到使用日志了。

如下

port-ip-monitor.shlink
1
2
3
4
5
6
7
8
9
10
11
12
[2014-11-02 11:04:01]
40001 119.129.165.181
40008 119.129.254.224
40013 219.130.239.3
[2014-11-02 11:05:01]
40001 119.129.165.181
40008 119.129.254.224
40013 219.130.239.3
[2014-11-02 11:06:01]
40001 119.129.165.181
40008 119.129.254.224
40013 219.130.239.3

PermaLink: http://everet.org/shadowsocks.html
Tags: Python, Shadowsocks ]]>
<![CDATA[使用Supervisor简化进程管理工作]]> 2014-11-02T10:26:00+08:00 http://everet.org/supervisor 这篇东西想写很久了,拖延症晚期患者-.-,今天终于下决心把它写了吧。

很久很久之前,在思考如何部署基于Tornado的服务,就和郑纪一起找到了一个Tornado的好伙伴——Supervisor。

Supervisor,简单来说,就是一个Python写的进程管理器。不仅仅可以用来管理进程,还可以用来做开机启动。

我在服务器上面有几个服务:

  1. 基于Tornado的短链接服务163.gs和很久木有更新的通讯录5txl.com
  2. node.js的Ghost Blog。
  3. 还有 番羽土啬 用的shadowsocks。

需求是,对于这些服务能够做到如下:

  1. 重启机器后,能够自启动。
  2. 平时有个方便的进程查看方式。
  3. 能够有个方便的方式重启进程。

虽然我们自己写启动脚本,但是其实还是挺烦的,特别是,我要开多个同样的服务,就要写几份几乎一样的启动脚本,这个十分之冗余。

庆幸的是,Supervisor1可以解决这些问题,安装好Supervisor,仅需要为Supervisor弄份启动脚本,便可以一劳永逸。

安装

非常熟悉的安装方式:sudo pip install supervisor,便可以拥有Supervisor,如果没有启动脚本,可以从这里下载一份,放置到/etc/init.d/下面便可。

配置

我们可以看到启动脚本中,其实默认写了一个启动参数-c /etc/supervisord.conf,这里我们可以通过Supervisor附送的贴心的小脚本生成默认的配置文件echo_supervisord_conf > /etc/supervisord.conf

我们可以根据需要修改里面的配置。我这里,每个不同的项目,使用了一个单独的配置的文件,放置在/etc/supervisor/下面,于是修改/etc/supervisord.conf,加上如下内容:

1
2
[include]
files = /etc/supervisor/*.conf

修改完后,我们便可以将项目的配置文件命名为.conf放置在/etc/supervisor/下面即可。

这里有个163.gs站点的配置文件163.gs.conf,使用了virtualenv,启动了两个Tornado进程。

163.gs.conflink
1
2
3
4
5
6
7
8
9
10
11
[program:163gs]
numprocs = 2
numprocs_start = 8850
user = projects
process_name = 163gs-%(process_num)s
directory = /home/projects/163.gs/
command = /home/projects/163.gs/env/bin/python /home/projects/163.gs/main.py --port=%(process_num)s
autorestart = true
redirect_stderr = true
stdout_logfile = /var/log/supervisor/163gs.log
stderr_logfile = /var/log/supervisor/163gs-error.log

更多详细的配置可以围观supervisor官方文档

使用

放置完配置文件后,我们使用service supervisor restart启动一下服务(如果是刚刚安装完,那么还没有supervisor进程,如果已经启动了,那么跳过这步)。

当我们需要管理进程的时候,使用supervisor的控制程序连接服务便可以很方便的查看经常状态和管理进程了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@  ~  # supervisorctl
163gs:163gs-8850                 RUNNING   pid 2929, uptime 15 days, 23:35:21
163gs:163gs-8851                 RUNNING   pid 2930, uptime 15 days, 23:35:21
163gs_redis                      RUNNING   pid 2924, uptime 15 days, 23:35:21
5txl:5txl-8070                   RUNNING   pid 2927, uptime 15 days, 23:35:21
5txl:5txl-8071                   RUNNING   pid 2928, uptime 15 days, 23:35:21
ghost                            RUNNING   pid 2923, uptime 15 days, 23:35:21
shadowsocks_me                   RUNNING   pid 2925, uptime 15 days, 23:35:21

supervisor> help

default commands (type help <topic>):
=====================================
add    clear  fg        open  quit    remove  restart   start   stop  update
avail  exit   maintail  pid   reload  reread  shutdown  status  tail  version

supervisor> help reread
reread                  Reload the daemon's configuration files

对于使用,直接输入help,就可以看到常用的命令,至于命令是啥意思,可以直接help那个命令,就可以看到解释了。

升级Supervisor

升级Supervisor也是非常简单的,使用pip install --upgrade supervisor既可以更新程序,然后使用service supervisor restart重启一下,就可以升级完成。

我在Github有我的服务器配置supervisor_conf,有兴趣可以看看。

好,打完收工。


  1. http://supervisord.org/


PermaLink: http://everet.org/supervisor.html
Tags: Python, Supervisor ]]>
<![CDATA[又一SB入手HHKB Pro2]]> 2014-09-08T23:28:00+08:00 http://everet.org/hhkb 受各路大神的影响,终于还是入手了传说中的程序员的加血光环HHKB。因为像GNU之父、Emacs之父Richard Stallman1,C++之父Bjarne Stroustrup2等大神都在使用。

入手数日,觉得布局是在太赞了,感觉完全为自己量身定做的键盘啊。配合Fn,手基本都不用动,就可以做到其他键盘的功能,例如F1-F12、上下左右、还有一堆功能按键,都是在主键区,十分高效。

而且在Mac下,HHKB直接可以控制声音、屏幕亮度,非常方便。

这里还是非常感谢当年入职的要求,每个人TT全字符(字母、数字、特殊符号)打字要到60WPM才能转正,所以现在都完全不用看键盘。

于是就入手一个白无刻,可以自行脑补其中的按键,而且自己随意修改按键映射也不会造成歧义。

另外收集了一些网上遇到的HHKBer:

  1. 高見龍
  2. X lambda

PermaLink: http://everet.org/hhkb.html
]]>
<![CDATA[无痛修改Octopress文章链接]]> 2014-08-21T21:33:00+08:00 http://everet.org/octopress-change-links 我的Blog的文章的链接本来是类似http://everet.org/2013/02/thinking-of-emacs.html这样的,不过觉得发布的时间戳加到url中,对老文章的SEO不利。所以决定将其去掉,改为http://everet.org/thinking-of-emacs.html

另一个是我想缩短下文章url的长度。

不过缩短url会遇到两个大问题,第一个是原来发出去的原来的文章链接会404,第二个是评论系统Disqus是根据文章url来作为评论的标识符。

不过好在是有无痛的解决方案,我们来各个击破。

首先我们要去掉时间戳,这个非常容易,修改_config.yml

_config.yml diff
1
2
- permalink: /:year/:month/:title.html
+ permalink: /:title.html

这样就可以将让Octopress生成文章的时候去掉时间戳,只保留文章标题。

不过如果此时就止步的话,原来的链接访问都会404,而且评论全部都会不见了。所以我们需要做一些处理。

301重定向原url

HTTP Code的301的意思是301 Moved Permanently

我的Blog是用Nginx来服务Blog的文件,我们可以让Nginx在访问原来的url的时候,301重定向到新的地址。我们加上以下配置,通过正则表达式匹配,找出文章标题,然后重写url。

nginx配置
1
2
3
location ~* ^/\d+/\d+/.+\.html$ {
    rewrite ^/\d+/\d+/(.+\.html)$ /$1 permanent;
}

然后我们访问旧的文章链接的时候,就会301重定向到新的文章地址了。这样旧的文章地址就不会404了。

Disqus的评论

刚刚说到Disqus的评论是根据文章url来标识的,我们改了url,评论就会不见了。不过好在Disqus的Admin设置有个爬虫,Discussions–>Tools–>Start Crawler,他可以根据301重定向自动更新原来的评论的标志,也就是新的url也可以看到之前url的评论了。

打完收工!


PermaLink: http://everet.org/octopress-change-links.html
Tags: Nginx, Octopress ]]>
<![CDATA[Emacs随想]]> 2014-08-21T16:05:00+08:00 http://everet.org/thinking-of-emacs By:Stupid ET

Emacs在1975年就诞生了,想必比现在绝大多数程序员都要老。现在最新的Emacs已经是24.3.50.7,为了获取最新的特性,我的Emacs都是自己编译最新的开发版(在24.3正式版出了后就使用正式版了,正式版更为稳定)。Emacs其实是一个Lisp解释器,有着和Lisp纠缠不清的关系,想这与Richard Stallman本人和MIT人工智能实验室有些许关系。Emacs许多逻辑都是用elisp写的。所有的配置也都是用elisp编写。

0x01 初见

想起很久很久以前,第一次因为久闻Emacs大名打开了Emacs,看到这界面,不禁吐槽,这不就是一个Notepad吗?完全看不出这货竟然被尊称为「伪装成编辑器的操作系统」啊。

而且发现完全可以像用Notepad一样使用Emacs-.-(想想第一次打开Vim的童鞋们可能连输入都发现没反应)。

鉴于众大牛都推荐Emacs,我还是决定尝试一下Emacs。刚开始完全无法忍受着C-[pnbf]的上下左右操作方式,找了一下发现有个Evil1插件,装上它Emacs立即拥有Vim的按键绑定,于是Emacs就这么变成了Vim!于是之后的一年我都是这么用的Emacs。

0x02 相识

我曾经是一个忠实的Vim用户(当然现在还是),不过后来接触了Emacs后就欲罢不能,其中一个重要的原因是Lisp。用Emacs可以让我们有一个很好的机会去学习和使用Lisp。

我们在Emacs中键入M-x ielm就可以打开Emacs Lisp交互解释器。可见,在Emacs中接触到Lisp是多么的容易2

于是随着时间的推移,更多地把玩,开始逐渐对Emacs刮目相看,发现Emacs真的是「伪装成编辑器的操作系统」。于是调教Emacs成了茶余饭后的消遣活动。

0x03 两个按键

Ctrl

作为一个被称为「Ctrl到死」的编辑器的用户,我们需要好好思考一下Ctrl这个按键。

Emacs的很多按键都需要配合Ctrl,而Ctrl在US键盘的左下角,按起来手形会比较纠结。所以强烈推荐将CtrlCaps Lock对调。当然如果你有HHKB3就不需要自己去切换了,因为已经物理支持了。于是Ctrl就到了左手小拇指的位置,按起来就非常舒适了。

Meta

在Emacs中的M(Meta)就是我们的Alt,因为Alt在空格的旁边,所以我们很容易就用左手大拇指按下。而一个非常常用的快捷键M-x,我们可以用左手大拇指同时按下这两个按键。就不用将整个手掌卷起来按。对于Mac用户,因为Mac的键盘Alt在普通键盘的Win键的位置,所以需要调换一下:

1
2
(setq mac-option-modifier 'super)
(setq mac-command-modifier 'meta)

0x04 扩展包管理

Emacs里面的扩展多到不计其数,我们如果自己手工安装扩展或者升级扩展是一件非常麻烦的事情。

对于debian或者ubuntu用户,大家是不是非常喜欢apt-get,只需要敲一条命令,你所需要的软件唰唰唰地就装好了。在Emacs中,我们也有这种便利的包管理——el-get4

自从有了el-get,我就再也没有自己去手工下载升级那些软件包了,而且,el-get可以很好的处理软件包的依赖。例如我们安装Python的智能补全插件jedi5,就只需要键入M-x el-get-install jedi,就可以装好jedi、epc、ctables等等,而且升级也非常方便。

另外,在el-get的帮助下,我们可以很容易将我们的Emacs配置用版本控制器进行管理,而不必将那些插件也纳入版本控制。在没有el-get的情况下,我们虽然可以通过忽略文件来忽略那些插件的文件,但是我们如果在一台全新的计算机将配置clone下来的时候,就没有了我们之前安装的插件了。

而有了el-get后,这些问题都得到了解决。el-get会在新计算机中自动为我们下载并安装好我们的插件。我用el-get管理了挺多插件,可以围观el-get配置

0x05 效率

作为Emacs User,使用效率也是十分重要。Emacs经过扩展后,可以比原版高效非常非常多。

移动

Ace Jump

我们可以围观一下这个screencast for AceJump。虽然说Vim的移动速度非常的快,但是Emacs加上Ace Jump之后,光标的移动速度完全不亚于Vim。

switch-window

C-x o估计是最常用的快捷键之一了,可以跳到其他window。不过如果在window特别多的时候,狂按C-x o估计就是比较让人蛋碎了。于是我们可以借助switch window让C-x o变得更加便捷。在window数目大于等于3个的时候,switch window就会给window标上“1,2,3……”,然后可以通过“1,2,3……”来选择window。

windmove

想要更加随心所欲地在window间移动,可以借助windmove6就可以通过上下左右移动了。我设了这些快捷键来移动:

1
2
3
4
(global-set-key (kbd "C-S-j") 'windmove-down)
(global-set-key (kbd "C-S-k") 'windmove-up)
(global-set-key (kbd "C-S-h") 'windmove-left)
(global-set-key (kbd "C-S-l") 'windmove-right)

快速移动到特定字符

在Vim中,我们可以按下f或者是t加上某个字符快速定位到光标之后的字符,这个非常的有用。

go to char
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defun my-go-to-char (n char)
  "Move forward to Nth occurence of CHAR.
Typing `my-go-to-char-key' again will move forwad to the next Nth
occurence of CHAR."
  (interactive "p\ncGo to char: ")
  (let ((case-fold-search nil))
    (if (eq n 1)
        (progn                            ; forward
          (search-forward (string char) nil nil n)
          (backward-char)
          (while (equal (read-key)
                        char)
            (forward-char)
            (search-forward (string char) nil nil n)
            (backward-char)))
      (progn                              ; backward
        (search-backward (string char) nil nil )
        (while (equal (read-key)
                      char)
          (search-backward (string char) nil nil )))))
  (setq unread-command-events (list last-input-event)))
(global-set-key (kbd "C-t") 'my-go-to-char)

通过这个,我们可以通过C-t加上指定字符向后跳,后者C-u C-t向前跳。比如C-t w w w w …就一直往后跳到后续的w处。类似于Vim中的fw;;;…

快速编辑

Sublime Text中引以为傲的多光标编辑,Emacs装个multiple-cursors.el就可拥有。

Multiple Cursors让Emacs轻松就可以像Vim的C-v列编辑,还有可以更高级的多光标编辑。可以看视频:Episode 13: multiple-cursors

标记Mark

标记,估计是最常用的功能之一。默认的Set Mark命令是C-@或者是C-SPC,前者太难按(Ctrl-Shitf-2),后者和输入法冲突了。于是我们可以将其重新绑定为M-SPC,这样用两个大拇指就可以轻松Set Mark了。

重新设置mark set
1
2
(global-unset-key (kbd "C-SPC"))
(global-set-key (kbd "M-SPC") 'set-mark-command)

对于标记一块区域,像Vim中,我们可以vi(vi"等等就可以标记括号、或者引号里面的内容了。在Emacs中,我们可以享有这种便捷。我们需要借助expand-region7,具体可以围观视频:Emacs Rocks! Episode 09: expand-region

而标记一个函数,可以用C-M-h。往后标记C-M-SPC

window管理

分割

对于非常常用的window分割快捷键默认为

  • C-x 1:关闭其他window
  • C-x 2:在下面分割一个window出来
  • C-x 3:在右边创建一个window
  • C-x 0: 关闭当前window

这些都有点不太直接,我们将其重新绑定到更快的按键上:

1
2
3
4
(global-set-key (kbd "M-1") 'delete-other-windows)
(global-set-key (kbd "M-2") 'split-window-below)
(global-set-key (kbd "M-3") 'split-window-right)
(global-set-key (kbd "M-0") 'delete-window)

这样就缩短了分割window的键程,畅快许多。

对调buffer

如果我们有两个window,有时我们想切换左右或者上下window里面的内容,就可以通过以下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
(defun transpose-buffers (arg)
  "Transpose the buffers shown in two windows."
  (interactive "p")
  (let ((selector (if (>= arg 0) 'next-window 'previous-window)))
    (while (/= arg 0)
      (let ((this-win (window-buffer))
            (next-win (window-buffer (funcall selector))))
        (set-window-buffer (selected-window) next-win)
        (set-window-buffer (funcall selector) this-win)
        (select-window (funcall selector)))
      (setq arg (if (plusp arg) (1- arg) (1+ arg))))))
(global-set-key (kbd "M-9") 'transpose-buffers)

更多window布局配置可以围观window-setting.el

快速eval lisp

我们经常会修改配置文件,一种让配置生效的方法是关闭Emacs,然后重新打开它。不过这也太慢了,如果不是到了迫不得已的情况,我们不需要如此大动干戈来使我们的修改生效。

我们在很多人的博客中都会看到一种方法,M-x eval-buffer,嗯,(eval-buffer)可以重新解释运行一遍当前buffer的内容。在挺多时候还是挺有用的。

不过我们有时候仅仅只是修改了一小段配置,完全没有必要eval整个buffer。于是我们就可以eval一个局部。

eval-region
1
2
(define-key emacs-lisp-mode-map (kbd "C-x C-r") 'eval-region)
(define-key lisp-interaction-mode-map (kbd "C-x C-r") 'eval-region)

然后,我们选择一个区域后,就可以用C-x C-r来eval一个选区了。

加速打开Emacs:Daemon

Emacs使用Client/Server可以大大提高新Emacs的开启速度,所有的client共用server来处理数据。

先启动Server

首先添加一个开机启动,这个会启动一个Emacs Daemon进程。可以让其他emacsclient连接到server来编辑。

emacs --daemon

Client

Terminal

编辑~/.bashrc,加上

1
2
alias ec='emacsclient -t -a=""'
alias se='SUDO_EDITOR="emacsclient -t" sudo -e'

然后,就可以通过ec filename来用emacs编辑文件。即使是有大量配置文件,开启速度也绝对不亚于Vim。因为client不加载任何配置,只是直接连到server。

Desktop

修改快捷方式的的command为

1
emacsclient -c -a=""

不过,我觉得Desktop的Emacs不需要连接Daemon,因为Desktop的Emacs只需要开一次就好了。Daemon会有一些奇葩的问题,例如session似乎就没法保存。

使用Daemon主要是为了提高开启速度,这个在Terminal中经常开关Emacs编辑文件时就很重要。而在Desktop就开一次的情况就显得没什么了。

录制

开始录制宏,用C-x (; 结束录制宏,用C-x )

使用

C-x e来使用宏。可以利用C-u来重复使用100次这个宏,即命令C-u 100 C-x eC-x e e e ...将宏重复。

保存宏

  1. 为宏命名:M-x name-last-kbd-macro
  2. 在配置文件的某个地方输入M-x insert-kbd-macro
  3. 然后就可以通过M-x调用了。

更多效率插件

Helm

Helm可以方便地帮助我们找到想要的buffer、文件。而且看上去也很酷。

smex

smex提供了更好的M-x体验。它让M-x变成了像ido一样,可以实时提供补全。让M-x更加快速。

session

配合desktop可以保存我们上一次的工作状态。也就是重新打开Emacs的时候,会变成上次关闭的状态。当然也可以分项目保存不同的session,这样就和IDE保存工作区一样了。

undo-tree

Emacs的undo非常诡异,只有undo,没有redo。如果要redo,那只有undo undo。

不过这样的设计,让Emacs的撤销变得异常强大。Emacs可以帮你所有的修改记录都保存下来,我们可以肆意地修改、undo完修改,各种修改,我们都可以回到曾经的状态。这个是其他编辑器难以做到的。

undo-tree可以将所有的状态用树状结构绘制出来。然后我们轻松地可以找到我们需要的状态,甚至可以diff不同的状态。

我们按C-x u可以进入undo-tree-visualizer-mode, 然后 pn 上下移动,在分支之前 bf 左右切换,t 显示时间戳,d 打开diff,选定需要的状态后,q 退出。这是主要的操作,其它的自己摸索好了…… 8

更多

Emacs中还有很多很多非常有用的插件,我这里有一些我在用的。

Minimap, 这个也是Sublime Text中另一个经常被提起的特性,Emacs装一个minimap也即可拥有。不过说实话,这个功能感觉貌似没啥意义。

还有些比较好用的:

  1. anzu,这个让搜索、替换变得更友好。
  2. helm-swoop,直观方便。
  3. ag,比ack更快的搜索工具。
  4. goto-last-change,快速切换到上一个编辑的位置。
  5. org-mode,这个不用说,Emacs中超强大的文本编辑mode,编辑完可以导出各种格式例如pdf、html、word等。我的毕业论文和平时很多东西都是用org-mode写的。我也用org-mode写些wiki

0x06 UI定制

Emacs的默认界面,见文章第一张图,看起来非常挫。不过幸运的是,Emacs具有超强的可定制性,于是我们可以随心所欲地调整UI。

标题栏

就可以显示当前项目名,当前编辑文件的完整路径。

1
2
3
4
(setq frame-title-format
      (list "[" '(:eval (projectile-project-name)) "]"
      " ψωETωψ ◎ "
      '(buffer-file-name "%f" (dired-directory dired-directory "%b"))))

顶部tab栏

这个需要装一个叫做tabbar的插件,装完后,就有了顶部的tab栏。具体颜色和形状的配置可以围观:my-tabbar.el

左边的行号

没错,不像Vim里面直接set nu就可以开启行号,Emacs的行号还需要装个插件来实现。不过庆幸的是,Emacs 22之后都自带了linum插件,只要启用就可以有行号了。

1
2
(require 'linum)
(global-linum-mode t)

底部的mode-line

底部的一条显示了很多信息的东西叫做mode-line。默认的非常简单,我们可以对其进行定制。如果比较懒,可以直接用emacs-powerline插件就可以有一个漂亮的powerline。

不过我还是比较喜欢自己定制。

依次显示文件名、当前编辑状态、滚动进度,当前mode,使用何种版本控制以及分支,最后是当前时间。

具体可以围观:my-ui.el,里面有详细的配置。

0x07 eshell

eshell是用lisp实现的shell,具有跨平台、支持tramp、与Emacs水乳交融等等优点。

对于如此常用的功能,我们赶紧绑定一个好用快捷键:

(global-set-key [f4] 'eshell)

alias

我们可以在lisp中定义alias,也可以专门为eshell定义alias。

Alias in lisp
1
2
(defalias 'll '(eshell/ls "-l"))
(defalias 'ff 'find-file)

或者在~/.emacs.d/eshell/alias中定义我们在eshell中的alias:

alias
1
2
3
4
alias l ls $*
alias la ls -a $*
alias gp git push
alias gs git status

Eshell有个挺好的教程:A Complete Guide to Mastering Eshell

0x08 TRAMP

TRAMP的全写为:Transparent Remote (file) Access, Multiple Protocol。在TRAMP的帮助下,我们可以很容易做到无缝编辑远程文件。

详细阅读:Emacs Tramp 详解

0x09 文件管理dired

dired是Emacs自带的文件管理系统,能够和Tramp无缝配合使用。

默认的dired看上去显示太多东西了,我们可以通过dired的扩展来定制我们需要显示的东西。

我们用el-get很容易就安装好dired扩展”dired-details”和”dired-details+“。然后就可以定制dired显示内容了。默认会隐藏掉不那么常用的信息。如下:

当按下)就可以显示详细信息了。

dired最强大之处,在于可以直接修改这个buffer里面的内容就可以直接对文件进行修改了,就可以很方便地批量重命名。我们按C-x C-q(dired-toggle-read-only)进入Wdired mode编辑模式,然后就可以像平时编辑文本一样编辑这个buffer,当完成后按C-c C-c(wdired-finish-edit)保存到磁盘,就可以完成批量修改文件名了。

0x0A 调试Emacs扩展

有时候Emacs的插件会出现各种问题,我们就需要进行调试了。

如果我们希望在出现错误的时候能够自动进入调试模式,那我们可以M-x toggle-debug-on-error

如果有时候Emacs卡住没有反应,但是按C-g能够恢复的话,那说明可能是进入了死循环。我们可以在之前打开M-x toggle-debug-on-quit,然后在我们按C-g的时候就可以调试当前正在运行的elisp。

那有时候我们需要在某个elisp函数设置断点的话,我们可以通过M-x debug-on-entry [funcname]来为某个函数设置断点。取消断点可以通过M-x cancel-debug-on-entry

在我们进入调试模式的时候,按d可以单步,q退出,e执行lisp。

详细:Debugger Commands

0x0B Development

autocomplete

autocomplete9可以为我们提供类似常用IDE一样的弹出式提示。而补全来源也可以轻松的自定义,是一个非常好的补全框架。

Yasnippet

Yasnippet是一个非常强大的模板替换扩展,可以轻松自己定制模板,而且模板还可以嵌入lisp进行逻辑处理,非常强大。

可以围观视频:Emacs Rocks! Episode 06: Yeah! Snippets!

Python

作为一个Python程序员,拥有一个好用的Python开发环境是非常的重要的。

相信大家在网上找到的Emacs Python IDE搭建靠的都是PyMacs和Rope。Rope是一个很强的库,不仅可以用来补全,还可以用来重构等等。

不过Rope的补全速度有些缓慢,于是我们可以求助一个更强更迅速的补全库jedi10,这是一个异步的补全库,不会阻塞编辑。而且jedi已经对autocomplete有很好的兼容了。安装仅需要M-x el-get-install jedi

Lisp

Emacs本来就是一个很好的elisp开发环境。不过Lisp里面的括号非常非常多,很容易就被搞晕了,一个有效的括号高亮显得非常重要。

达到上述逐级高亮的效果,需要借助highlight-parentheses,autopair。然后编写下述代码11

括号高亮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(add-hook 'highlight-parentheses-mode-hook
          '(lambda ()
             (setq autopair-handle-action-fns
                   (append
                    (if autopair-handle-action-fns
                        autopair-handle-action-fns
                      '(autopair-default-handle-action))
                    '((lambda (action pair pos-before)
                        (hl-paren-color-update)))))))

(define-globalized-minor-mode global-highlight-parentheses-mode
  highlight-parentheses-mode
  (lambda ()
    (highlight-parentheses-mode t)))
(global-highlight-parentheses-mode t)

其他开发辅助插件

像Emacs中的开发辅助插件还有很多,比较重型的有cedet(Collection of Emacs Development Environment Tools)、ecb(Emacs Code Browser)等等,都是有效的工具,可以帮助我们提高工作效率。另外还有挺多小工具。

  1. magit,在Emacs中方便友好地使用git。
  2. git-gutter可以实时显示当前文件的diff。而且可以快速跳到相对于上一次commit修改的部分。
  3. projectile,超好用的项目辅助工具,可以快速在当前项目搜索、打开文件、编译等等。
  4. emacs-helm-gtags12,可以配合gtags快速跳转到定义等。
  5. rainbow-mode,能够把css、html等文件中的颜色直接显示出来。
  6. nginx-mode,可以语法高亮nginx的配置文件。
  7. httpcode,速查http状态码

Git-timemachine,可以直接查看文件的不同版本历史:

0x0C 更多Emacs的用途

编辑Chrome的文本框

对于平时的生活,Emacs可以编辑Chrome里面的内容,例如有时需要发段代码:Chrome Edit With Emacs

写Markdown

对于用Markdown写Blog,Emacs甚至可以直接截图、插入图片:Screenshot and Image Paste in Emacs When Writing Markdown

插入完图片后,可以直接在Emacs中预览Markdown中的图片:让Emacs显示Markdown中的图片,这个绝对让其他编辑器望尘莫及。

字符画

Emacs的Artist Mode13可以直接画字符画,按下M-x artist-mode就可以用鼠标画字符画图了,

字符画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
   +--------------------------------------------+
   |    +-------+                +--------+     |
   |    |       |                |        |     |
   |    +-------+    -----       +--------+     |
   |.               (     )                   ..|
   |...              -----.                   . |
   +--------------------X-----------------------+
               ----/   / \        \-----
          ----/       /  |              \---
       --/           /    \
                   -/      \
                  /         \
                 /          |
                /            \

当然像上个网,煮个咖啡,听个音乐,聊个天,收发个邮件,Emacs都可以轻松做到。

0x0D 帮助

如果在使用Emacs的过程中遇到什么问题,可以求助于Emacs强大的帮助系统。

C-h t

打开 Emacs 的入门教程,

C-h k

让Emacs告诉你某个快捷键是什么作用。首先按下C-h k,然后按下我们的快捷键。就可以打开帮助了,于此同时,我们还可以看到我们的按键的是如何表示的。

C-h b

查看按键绑定

C-h K

注意这次是大写的 K 。对于 Emacs的一些内部命令,除了Elisp源代码中提供的文档以外,还有一个专门的 Info 文档进行了系统的介绍。C-h K 就是定位到 Info 文档中描述该命令的位置。

C-h f

查看某个函数的文档。建议绑定一个快捷键,这样我们把光标放到某个函数的上面,一按快捷键就可以打开这个函数的文档了。

C-h v

查看某个变量的文档。

C-h m

当开当前mode的帮助。这里挺详细的对于当前可用快捷键的描述。

… C-h

当我们不能完整记得某些快捷键的时候,可以按下前缀后,再按下C-h。就可以看到以这个前缀开始的快捷键有哪些。

C-h a 更模糊的查找

有些时候我们只知道一个关键字,这个时候可以用 C-h a 来通过正则表达式来查找命令名。Emacs 会列出所有匹配的命令以及一个简短的文档,并可以通过点击链接定位到该命令的详细文档。

请个快捷键导师

Emacs中有个快捷键导师,可以在你需要的时候,提示你,可以围观:Emacs中的快捷键导师

0x0E Misc

一些有趣的快捷键

  • C-M-h 标记一个函数定义
  • C-h C-a about-emacs
  • C-h C 查看当前文件的编码
  • C-u M-! date 插入当前时间
  • C-u M-= 计算整个缓冲区的行数、字数和单词数
  • C-x <RET> f utf-8 (set-buffer-file-coding-system),设置当前buffer的文件的编码
  • C-x C-+ and C-x C-- to increase or decrease the buffer text font size
  • C-x C-q 开关read-only-mode,在dired-mode中可以进入修改模式,可以批量修改文件名。
  • C-x C-t 交换两行。可以用来调整python中import
  • M-x sort-lines 排序选中行。
  • C-x C-v or M-x find-alternate-file 重新打开当前文件,在高亮后者插件出了bug可以用这个命令重新加载。
  • C-x z 重复上一条命令。可以一直按z不断执行,非常方便!
  • M-& 异步运行一个shell命令
  • M-: 运行一句lisp
  • M-@ mark-word,连续按连续mark单词。
  • M-g M-g linenum 跳到某行,同vim中的[linenum]G
  • M-h 标记一段
  • M-x dig
  • M-x ifconfig
  • M-x ping
  • M-x telnet
  • M-z 删除到某个字符,同Vim的df
  • C-u M-! date 插入当前时间
  • C-q C-i 插入tab
  • M-x list-colors-display 显示Emacs所有的颜色,方便我们来进行配色

当然还有很多很多,就不再列了。

中文输入法

在英文版的系统里面,一般情况下Emacs可能打不开中文输入法,此时我们需要修改LC_CTYPE环境变量就好了。

可以在~/.profile最后加上一句

字符画
1
export LC_CTYPE="zh_CN.UTF-8"

字节码编译

将我们的el配置编译成字节码,可以加快Emacs的加载速度,特别是在配置文件特别多的时候。

我们去到我们的el配置文件目录,打开dired,然后输入% m来调用dired-mark-files-regexp,然后输入.el来标记所有的配置文件,然后按B调用dired-do-byte-compile,然后就可以把一个目录下面的el一次性编译成elc。或者也可以直接C-u 0 M-x byte-recompile-directory一次性编译一个目录及其子目录。

不过这样就会带来一个问题,就是如果我们修改了配置后,还是需要重新编译的。这里在ErgoEmacs14找到了自动重新编译的配置,就是在保存文件的时候检查当前是否为emacs-lisp-mode,如果是,那么就编译它。这样我们修改配置的时候,就会自动重新编译了。

1
2
3
4
5
6
7
8
9
;; http://ergoemacs.org/emacs/emacs_byte_compile.html
(defun byte-compile-current-buffer ()
  "`byte-compile' current buffer if it's emacs-lisp-mode and compiled file exists."
  (interactive)
  (when (and (eq major-mode 'emacs-lisp-mode)
             (file-exists-p (byte-compile-dest-file buffer-file-name)))
    (byte-compile-file buffer-file-name)))

(add-hook 'after-save-hook 'byte-compile-current-buffer)

0x0F 终

写了好几天(一年多了),发现还有挺多东西没写的,Emacs博大精深,还需要自己慢慢摸索。这里纯当为我的记忆做个快照,让以后的我可以看到在2013年初时,Emacs在我眼中的形象。

如果你阅读到了这里,非常感谢你的耐心,感谢你看完了我如此长篇的唠叨。祝你在2013年效率大大提高~

如果你有兴趣,可以浏览一下我的Emacs配置文件

最后,我也不想挑起Vim和Emacs无谓的口水战。其实无论是Vim还是Emacs,它的好用程度完全取决于使用者的配置能力,所以好不好用,完全看个人。

0x10 有趣的Emacs知识分享

  1. http://whattheemacsd.com/
  2. http://emacsrocks.com/
  3. http://planet.emacsen.org/
  4. https://github.com/emacs-tw/awesome-emacs
  5. 当然还有我的Blog:http://everet.org/tag/emacs/

0x11 Update

2013-02-09

第一版。

2014-08-20

更新字节码编译 diff

2014-08-21

更新大量新内容 diff


PermaLink: http://everet.org/thinking-of-emacs.html
Tags: Emacs, Lisp ]]>
<![CDATA[Emacs中的快捷键导师]]> 2014-08-19T21:54:00+08:00 http://everet.org/guide-key Emacs中有很多很多快捷键,多到有时候自己都不知道自己有什么快捷键。

例如org-mode1就有非常多的快捷键,特别是有时候有的快捷键比较长,按了前面的部分,就忘了后面的,虽然可以按C-h m来看看当前mode有什么快捷键,不过还是挺麻烦的。

直到遇到了guide-key2,它可以在我们按快捷键的,给我们提供指导。如下图,我在org-mode中,按下C-c后,不记得C-c后有什么快捷键,然后此时guide-key就弹出提示了。

具体配置如下(更多的可以围观我的el-get配置),可以专门针对一个mode设置提示前缀,更可以高亮其中的一些选项,非常不错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(guide-key-mode 1)  ; Enable guide-key-mode
;; (setq guide-key/idle-delay 0.1)

(defun guide-key/my-hook-function-for-python-mode ()
  (guide-key/add-local-guide-key-sequence "C-c")
  (guide-key/add-local-highlight-command-regexp "rope-")
  (guide-key/add-local-highlight-command-regexp "py-")
  (guide-key/add-local-highlight-command-regexp "python-"))
(add-hook 'python-mode-hook 'guide-key/my-hook-function-for-python-mode)

(defun guide-key/my-hook-function-for-org-mode ()
  (guide-key/add-local-guide-key-sequence "C-c")
  (guide-key/add-local-guide-key-sequence "C-c C-x")
  (guide-key/add-local-highlight-command-regexp "org-"))
(add-hook 'org-mode-hook 'guide-key/my-hook-function-for-org-mode)

(setq guide-key/guide-key-sequence
      '("C-x r" "C-x 4" "C-x 5" "C-c p"
  (org-mode "C-c C-x")
  (outline-minor-mode "C-c @")
  (markdown-mode "C-c C-a")
  ))
(setq guide-key/recursive-key-sequence-flag t)


PermaLink: http://everet.org/guide-key.html
Tags: Emacs ]]>
<![CDATA[git时光机]]> 2014-08-19T09:06:00+08:00 http://everet.org/git-timemachine 昨天从Planet Emacsen1发现一个Emacs插件,git-timemachine,看名字就和苹果的Timemachine一样,也确实是可以像时光机一样浏览文件。

操作非常简单:

  1. M-x git-timemachine进入Timemachine
  2. p上一个版本
  3. n下一个版本
  4. w或者W复制当前版本的hash
  5. q退出。


PermaLink: http://everet.org/git-timemachine.html
Tags: Emacs, Git ]]>
<![CDATA[解决Macbook盖上屏幕后不会睡眠]]> 2014-07-12T10:32:00+08:00 http://everet.org/macbook-prevent-sleep 很久以前,就听到许多人说,用Macbook都是从来不关机,平时都是直接合上屏幕塞到包里。于是我也这样了,不过后来突然发现,塞到包里第二天早上起来开机的时候,就发现Macbook已经关机了。重新开机的时候,就提示系统没有正常关机。

晚上有时候回到家里,将Macbook拿出来,就发现温度非常高。看上去合上盖子后,并没有sleep。

想起很久之前,我都是直接合上屏幕就走,不过后来突然就出现了合上屏幕塞包里后,过热关机。这个是为什么呢?难道是我升级系统后,系统出了什么bug?

这个问题我纠结了挺久的,问了其他人,都没有同样的问题,于是以为是我的硬件出了问题。隔了很久,终于找到了问题所在。

有时候,我用pmset的输出是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@  ~  % pmset -g
Active Profiles:
Battery Power           -1*
AC Power                -1
Currently in use:
 standbydelay         10800
 standby              1
 halfdim              1
 hibernatefile        /var/vm/sleepimage
 darkwakes            0
 disksleep            10
 sleep                1
 autopoweroffdelay    14400
 hibernatemode        3
 autopoweroff         1
 ttyskeepawake        1
 displaysleep         2
 acwake               0
 lidwake              1

有时候,输出是这样,可以看到sleep那行(sleep prevented by coreaudiod)。也就是coreaudiod阻止sleep。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@  ~  % pmset -g
Active Profiles:
Battery Power           -1*
AC Power                -1
Currently in use:
 standbydelay         10800
 standby              1
 halfdim              1
 hibernatefile        /var/vm/sleepimage
 darkwakes            0
 disksleep            10
 sleep                1 (sleep prevented by coreaudiod)
 autopoweroffdelay    14400
 hibernatemode        3
 autopoweroff         1
 ttyskeepawake        1
 displaysleep         2
 acwake               0
 lidwake              1

然后看看assertion,这个时候,PreventUserIdleSystemSleep这个为1,就是不会sleep了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@  ~  % pmset -g assertions
14-7-12 GMT+811:19:57
Assertion status system-wide:
   BackgroundTask                 0
   PreventDiskIdle                0
   ApplePushServiceTask           0
   UserIsActive                   1
   PreventUserIdleDisplaySleep    0
   InteractivePushServiceTask     0
   PreventSystemSleep             0
   ExternalMedia                  0
   PreventUserIdleSystemSleep     1
   NetworkClientActive            0
Listed by owning process:
   pid 72(hidd): [0x0000000a000001b1] 01:08:21 UserIsActive named: "com.apple.iohideventsystem.queue.tickle"
        Timeout will fire in 91 secs Action=TimeoutActionRelease
   pid 323(coreaudiod): [0x0000000100000281] 00:04:07 NoIdleSleepAssertion named: "com.apple.audio.'AppleHDAEngineOutput:1B,0,1,1:0'.noidlesleep"
No kernel assertions.

于是看看什么程序用到声音,只有chrome里面开了douban.fm,不过此时没有在播放音乐。然后关闭douban.fm的tab,然后PreventUserIdleSystemSleep就变回0了。

看来就是估计我每天听douban.fm惹的祸。

于是继续尝试在chrome里面随便打开一个视频网站看视频,发现此时PreventUserIdleSystemSleep又变成1了。

看来应该只要有会播放声音的flash在,就会阻止Macbook睡眠。

另外,想itune,GarageBand等程序开着的时候,也可能会阻止sleep。所以,以后盖屏幕前,先处理掉这些程序,以免发生意外,要不然过热多几次,不知道Macbook会不会就烧坏了-.–

找到问题了唉,看来以后小心为妙。


Update:

2014-07-27

后来发现,有时候打开虚拟机,PreventSystemSleep会变为1,此时合上屏幕后也不会睡眠。所以这个时候,最好还是把虚拟机关掉。

后来发现,即便是关掉了所有会阻止睡眠的程序,没插电而且确定已经睡眠,发现放一个晚上后,还是会自动开机而且会很热,而且掉了很多电。

在shell中输入syslog -k Sender kernel -k Message Req Wake,看到茫茫多的

1
Jul 24 03:29:00 Stupid-ET kernel[0] <Debug>: Wake reason: ?

就是每分钟都Wake一次。仔细看日志,每次都是 sleep 3个小时后,就会开始不停地Wake up,然后reason: ?。不知道为啥,可能Mac OS有bug。那么现在如何解决呢?

现在的主要问题就是:sleep 3个小时后,就不能再自动sleep了。3个小时 = 3600 * 3 = 10800秒。

我们在看pmset的时候,有行standbydelay 108001。看上去可能在尝试写hibernation image的时候出错,导致无法sleep。

1
standbydelay specifies the delay, in seconds, before writing the hibernation image to disk and powering off memory for Standby.

于是先将该值调大,调到7天sudo pmset -a standbydelay 604800,然后就不会醒来了。


PermaLink: http://everet.org/macbook-prevent-sleep.html
Tags: Mac ]]>
<![CDATA[Python中使用signal带来的怪异问题]]> 2014-07-10T18:40:00+08:00 http://everet.org/python-signal-global-var 今天智平在群里发了一个signal的考题考大家,就是下面程序是什么输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import signal
import sys

count = 0

def signal_handler(signum, frame):
    global count
    count = -1
    print 'sig', count

def main():
    signal.signal(signal.SIGALRM, signal_handler)
    signal.setitimer(signal.ITIMER_REAL, 1, 1)
    global count
    while True:
        for line in sys.stdin:
            count += 1
            print count

if __name__ == "__main__":
    main()

然后执行tail -f /home/logs/nginx/access.log | python a.py

问输出是 1,2,3,4,5,-1,6,7,8… 这样, 还是1,2,3,4,5,-1,0,1,2…

我们经过测试,是第一种情况。非常奇葩。

伟大的大神叠哥说道:

count += 1 有 read count、plus 1、store count 三步,会不会是因为实现上的什么原因,那个signal_handler总是插入到这三个操作中间去执行了?

因为,发现在count += 1 前面加一条无意义的赋值语句,结果就符合预期了。就输出1,2,3,4,5,-1,0,1,2…

1
2
3
4
5
while True:
for line in sys.stdin:
x = count
count += 1
print count

为啥加上x = count,输出就变了呢?我觉得大神叠哥一语道出了问题的本质,于是想去求证一下是否真的如此:signal_handler总是插入到这三个操作中间去执行。

我们来看字节码

我们首先来看看signal_handler的字节码:

1
2
3
4
5
6
7
8
9
10
11
In [8]: dis.dis(signal_handler)
  3           0 LOAD_CONST               1 (-1)
              3 STORE_GLOBAL             0 (count)

  4           6 LOAD_CONST               2 ('sig')
              9 PRINT_ITEM
             10 LOAD_GLOBAL              0 (count)
             13 PRINT_ITEM
             14 PRINT_NEWLINE
             15 LOAD_CONST               0 (None)
             18 RETURN_VALUE

然后我们再看看两个程序main()的字节码。这里列出字节码主要供下一节参考使用,现在可以不用太仔细看。

非预期的程序

我们看看输出1,2,3,4,5,-1,6,7,8…的字节码

1
2
3
4
5
6
7
8
def main():
    signal.signal(signal.SIGALRM, signal_handler)
    signal.setitimer(signal.ITIMER_REAL, 1, 1)
    global count
    while True:
        for line in sys.stdin:
            count += 1
            print count
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
In [4]: dis.dis(main)
  2           0 LOAD_GLOBAL              0 (signal)
              3 LOAD_ATTR                0 (signal)
              6 LOAD_GLOBAL              0 (signal)
              9 LOAD_ATTR                1 (SIGALRM)
             12 LOAD_GLOBAL              2 (signal_handler)
             15 CALL_FUNCTION            2
             18 POP_TOP

  3          19 LOAD_GLOBAL              0 (signal)
             22 LOAD_ATTR                3 (setitimer)
             25 LOAD_GLOBAL              0 (signal)
             28 LOAD_ATTR                4 (ITIMER_REAL)
             31 LOAD_CONST               1 (1)
             34 LOAD_CONST               1 (1)
             37 CALL_FUNCTION            3
             40 POP_TOP

# while True:
  5          41 SETUP_LOOP              45 (to 89)
        >>   44 LOAD_GLOBAL              5 (True)
             47 POP_JUMP_IF_FALSE       88

# for line in sys.stdin:
  6          50 SETUP_LOOP              32 (to 85)
             53 LOAD_GLOBAL              6 (sys)
             56 LOAD_ATTR                7 (stdin)
             59 GET_ITER
        >>   60 FOR_ITER                21 (to 84)
             63 STORE_FAST               0 (line)
# count += 1
  7          66 LOAD_GLOBAL              8 (count)
             69 LOAD_CONST               1 (1)
             72 INPLACE_ADD
             73 STORE_GLOBAL             8 (count)
# print count
  8          76 LOAD_GLOBAL              8 (count)
             79 PRINT_ITEM
             80 PRINT_NEWLINE
             81 JUMP_ABSOLUTE           60  # 这里回到偏移60的指令
        >>   84 POP_BLOCK
        >>   85 JUMP_ABSOLUTE           44
        >>   88 POP_BLOCK
        >>   89 LOAD_CONST               0 (None)
             92 RETURN_VALUE

预期输出的程序

我们再来看看1,2,3,4,5,-1,0,1,2…的程序的字节码:

1
2
3
4
5
6
7
8
9
def main():
    signal.signal(signal.SIGALRM, signal_handler)
    signal.setitimer(signal.ITIMER_REAL, 1, 1)
    global count
    while True:
        for line in sys.stdin:
            x = count
            count += 1
            print count
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
In [6]: dis.dis(main)
  2           0 LOAD_GLOBAL              0 (signal)
              3 LOAD_ATTR                0 (signal)
              6 LOAD_GLOBAL              0 (signal)
              9 LOAD_ATTR                1 (SIGALRM)
             12 LOAD_GLOBAL              2 (signal_handler)
             15 CALL_FUNCTION            2
             18 POP_TOP

  3          19 LOAD_GLOBAL              0 (signal)
             22 LOAD_ATTR                3 (setitimer)
             25 LOAD_GLOBAL              0 (signal)
             28 LOAD_ATTR                4 (ITIMER_REAL)
             31 LOAD_CONST               1 (1)
             34 LOAD_CONST               1 (1)
             37 CALL_FUNCTION            3
             40 POP_TOP
# while True:
  5          41 SETUP_LOOP              51 (to 95)
        >>   44 LOAD_GLOBAL              5 (True)
             47 POP_JUMP_IF_FALSE       94
# for line in sys.stdin:
  6          50 SETUP_LOOP              38 (to 91)
             53 LOAD_GLOBAL              6 (sys)
             56 LOAD_ATTR                7 (stdin)
             59 GET_ITER
        >>   60 FOR_ITER                27 (to 90)
             63 STORE_FAST               0 (line)
# x = count
  7          66 LOAD_GLOBAL              8 (count)
             69 STORE_FAST               1 (x)
# count += 1
  8          72 LOAD_GLOBAL              8 (count)
             75 LOAD_CONST               1 (1)
             78 INPLACE_ADD
             79 STORE_GLOBAL             8 (count)
# print count
  9          82 LOAD_GLOBAL              8 (count)
             85 PRINT_ITEM
             86 PRINT_NEWLINE
             87 JUMP_ABSOLUTE           60
        >>   90 POP_BLOCK
        >>   91 JUMP_ABSOLUTE           44
        >>   94 POP_BLOCK
        >>   95 LOAD_CONST               0 (None)
             98 RETURN_VALUE

增加调试代码

有了程序的字节码后,我们需要打印一下程序的执行流程,来深入看看究竟发生了啥事。我们可以先下载Python 2.7.3的源代码回来,然后修改:

首先打印处理的字节码,我们打开Python/ceval.c,在下面PyEval_EvalFrameEx中的swtich (opcode),前面加上printf("[opcode: %d]\n", opcode);来打印处理的opcode。

然后我们修改一下signalmodule模块,在处理signal的时候,打印一条log。我们打开Modules/signalmodule.c,在其中的PyErr_CheckSignals里面的result = PyEval_CallObject(Handlers[i].func, arglist);语句前加上printf("-> call signal handler\n");,这样就可以在调用signal handler的时候打印一条log。

然后就是编译Python了。编译完,我们就用编译好的Python来做实验!Yeah~

打印程序执行流程

接下来,我们就执行这怪异的程序,我们先来看看输出1,2,3,4,5,-1,6,7,8…这个不符合预期的程序的字节码处理流程1

非预期版本的执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[opcode: 113 JUMP_ABSOLUTE]
[opcode: 93 FOR_ITER]

[opcode: 116 LOAD_GLOBAL]
[opcode: 100 LOAD_CONST]
[opcode: 55 INPLACE_ADD]
[opcode: 97 STORE_GLOBAL]

[opcode: 116 LOAD_GLOBAL]
[opcode: 71 PRINT_ITEM]
[opcode: 72 PRINT_NEWLINE]
[opcode: 113 JUMP_ABSOLUTE]

[opcode: 93 FOR_ITER]
[opcode: 116 LOAD_GLOBAL]   # 将count压入栈

-> call signal handler
[opcode: 100 LOAD_CONST]    # 这里进入到了signal_handler
[opcode: 97 STORE_GLOBAL]   # count = -1
[opcode: 100 LOAD_CONST]
[opcode: 71 PRINT_ITEM]
[opcode: 116 LOAD_GLOBAL]
[opcode: 71 PRINT_ITEM]
[opcode: 72 PRINT_NEWLINE]
[opcode: 100 LOAD_CONST]
[opcode: 83 RETURN_VALUE]

[opcode: 100 LOAD_CONST]   # 回到main
[opcode: 55 INPLACE_ADD]   # 将之前栈顶的count+1,存到全局中的count
[opcode: 97 STORE_GLOBAL]

[opcode: 116 LOAD_GLOBAL]
[opcode: 71 PRINT_ITEM]
[opcode: 72 PRINT_NEWLINE]
[opcode: 113 JUMP_ABSOLUTE]

经过几十秒后,我们可以看到,除了第一次外,后续的signal handler,都在同一个地方被调用的:

1
2
3
4
[opcode: 93 FOR_ITER]
[opcode: 116 LOAD_GLOBAL]   # 将count压入栈

-> call signal handler

这里解释器刚刚将全局的count压入栈,就切换到signal handler处理。

所以这里可以出现问题的原因就很明显了。在main()中,已经将全局的count压入栈,而切换到signal_handler中,修改了全局的count为-1,也是无意义的。因为切回到main()的时候,是操作的是栈顶部的count副本,栈顶+1后,将栈顶的值又写入全局的count中,就把signal_handler设置的count=-1给覆盖了。所以就会一直递增下去。

可预测版本的执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[opcode: 113 JUMP_ABSOLUTE]
[opcode: 93 FOR_ITER]

[opcode: 116 LOAD_GLOBAL]
[opcode: 125 STORE_FAST]
[opcode: 116 LOAD_GLOBAL]
[opcode: 100 LOAD_CONST]
[opcode: 55 INPLACE_ADD]
[opcode: 97 STORE_GLOBAL]
[opcode: 116 LOAD_GLOBAL]
[opcode: 71 PRINT_ITEM]
[opcode: 72 PRINT_NEWLINE]
[opcode: 113 JUMP_ABSOLUTE]

[opcode: 93 FOR_ITER]
[opcode: 116 LOAD_GLOBAL] # 将全局count压入栈顶

-> call signal handler
[opcode: 100 LOAD_CONST]   # 进入signal handler
[opcode: 97 STORE_GLOBAL]
[opcode: 100 LOAD_CONST]
[opcode: 71 PRINT_ITEM]
[opcode: 116 LOAD_GLOBAL]
[opcode: 71 PRINT_ITEM]
[opcode: 72 PRINT_NEWLINE]
[opcode: 100 LOAD_CONST]
[opcode: 83 RETURN_VALUE]

[opcode: 125 STORE_FAST]   # 回到main(), x = 栈顶
[opcode: 116 LOAD_GLOBAL]  # 又将全局count压入栈,此时的全局的count是-1
[opcode: 100 LOAD_CONST]  # 继续压入1
[opcode: 55 INPLACE_ADD]  # 栈顶的两个值求和,count+1
[opcode: 97 STORE_GLOBAL] # 将栈顶的值写到全局count中,此时全局count为0

[opcode: 116 LOAD_GLOBAL]
[opcode: 71 PRINT_ITEM]
[opcode: 72 PRINT_NEWLINE]
[opcode: 113 JUMP_ABSOLUTE]

我们可以看到,返回main()后,x是旧值,不过在count += 1的时候,又重新从全局字典中读取了count,所以此时的count是正确的。

于是这就解释了上述两程序的怪异行为。

新的疑问

于是这里又引入了一个新的疑问,为什么每次signal handler都在同一个指令后触发?嗯,这个我们就需要去看看Python解释器的实现。我们首先看看signal是怎么触发的。

signal

我们打开Modules/signalmodule.c,这个是signal模块的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
static void
trip_signal(int sig_num)
{
    Handlers[sig_num].tripped = 1;
    if (is_tripped)
        return;
    /* Set is_tripped after setting .tripped, as it gets
       cleared in PyErr_CheckSignals() before .tripped. */
    is_tripped = 1;
    Py_AddPendingCall(checksignals_witharg, NULL);
    if (wakeup_fd != -1)
        write(wakeup_fd, "\0", 1);
}

其中,当signal触发的时候,我们看到,Python解释器不是马上调用我们的signal handler,而是调用Py_AddPendingCall,将我们回调加到pendingcalls中,然后让主线程在适当的时机调用。最后会调用到PyErr_CheckSignals,然后在里面根据不同的signal选择不同的handler来调用。

什么时候signal handler被调用?

刚刚我们看到,signal触发的时候,会调用Py_AddPendingCall会事件处理加到pendingcalls中(Py_AddPendingCall除了会将函数放到pendingcall队列中,还会将_Py_Ticker设置为0,以通知主线程尽快执行pendingcalls。)。那么pendingcalls中的函数什么时候调用呢?

我们首先来到Python解释器的核心函数PyEval_EvalFrameEx(在Python/ceval.c),看到其中有调用Py_MakePendingCalls,这个里面调用了pendingcalls。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
PyObject *
PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
    // ...

    for (;;) {
        if (--_Py_Ticker < 0) {
            _Py_Ticker = _Py_CheckInterval;
            Py_MakePendingCalls();
        }

    fast_next_opcode:
        opcode = NEXTOP();

        switch (opcode) {
        PREDICTED_WITH_ARG(FOR_ITER);
        case FOR_ITER:
            // ....
            PREDICT(STORE_FAST);
            // ...
            continue;
        PREDICTED_WITH_ARG(STORE_FAST);
        case STORE_FAST:
            v = POP();
            SETLOCAL(oparg, v);
            goto fast_next_opcode;

        // case ...
        }
    } /* main loop */

    // ...

    return retval;
}

也就是每次循环开的时候,当_Py_Ticker < 0的时候,就会调用Py_MakePendingCalls,里面会调用我们pending的signal handler。

那么为啥都是那个时刻调用呢?

我可以告诉你,这个真的是纯属巧合。不过为啥在下面三条指令后,触发signal handler的概率那么高呢?

1
2
3
[opcode: 93 FOR_ITER]       ; PREDICT(STORE_FAST); goto fast_next_opcode; 下面还有一条STORE_FAST指令,不过没打印出来
[opcode: 116 LOAD_GLOBAL]   # 取出全局变量
-> call signal handler

如果我们仔细去看PyEval_EvalFrameEx主循环中指令的解释,会看到其中会有优化。其中就有PREDICTfast_next_opcode2,当PREDICT下一条指令成功的时候,会直接goto到下一条指令的处理代码,就不会回到主循环的开头。而fast_next_opcode,也跳过了延迟任务的检查。

而我们上面三个指令的执行,是通过加速,中间都是直接goto执行下一条,一直没有回到主循环开始,所以就没有机会检查_Py_Ticker和执行Py_MakePendingCalls

也就是说,这三条指令执行的过程中,是可以当做一个整体,中间是不会有机会执行Py_MakePendingCalls,所以只有当执行完LOAD_GLOBAL的时候,才会回到循环开头。

而这段连续的时间,因为sys.stdin迭代器的操作相对非常长,所以signal在这段时间触发设置_Py_Ticker为0的概率最大。所以就造成了signal handler好像都在同一个地方执行的假象。

再验证!

既然是巧合,那么就是说,输出1,2,3,4,5,-1,6,7,8…的程序,其实还是有可能在signal handler成功重置计数咯?

于是我将第一个程序放置在那里跑,跑了好久,当计数记到40多万的时候,终于计数重置为0了。也就是说,之前能够连续计数,这个纯属巧合,只是偶然signal_handler都在同一个错误的地方执行。

不过智平童鞋竟然能够恰好写出如此神奇的代码,说明rp十分之高!!

因为时间仓促,语言可能有些混乱,还请见谅。

最后,还是非常佩服伟大的叠哥大神一语道破天机!!


  1. 输出的字节码指令是数字,还需要另外翻译一下,如果你用Emacs,可以使用这个gist,会自动翻译上面输出的opcode。如果不是,可以自己根据源码中的opcode.py修改一下。

  2. 更多解释可以自己看源码和《Python源码剖析》


PermaLink: http://everet.org/python-signal-global-var.html
Tags: Python ]]>
<![CDATA[Wordpress防止暴力破解]]> 2014-07-06T20:10:00+08:00 http://everet.org/wordpress-login-brute-force-attack 很久之前突然linode给发邮件说,CPU使用率超过阈值,然后报警了。登陆上去发现有的Wordpress进程CPU占用率特别高,看了一下access log发现几乎所有请求都在访问/wp-login.php。也就是有人在暴力破解。

当时在想,暴力破解Wordpress好处多多,只需要破解了一个Wordpress,在其装入一个恶意插件,然后自动去破解其他Wordpress站点,就可以像蠕虫一样蔓延开来。于是当时就加了个请求速率限制,这里记录一下。

防御

这个最简单的办法就是让Wordpress的主人去装个插件,在登录的时候,加上验证码,就可以基本防止大部分自动破解的程序了。不过因为我只是托管别人的Wordpress,让别人装个插件还不如自己在Nginx前端做个请求访问限制。

首先在/etc/nginx/nginx.conf中的http block中配置一个zone,这个zone 4MB大,平均请求速率为每分钟5个请求,因为是个人Blog的登录,所以限制那么低速,也降低了别人暴力破解的速度,按照这种速度,一分钟只能尝试5个密码,如果密码不是很废的话,要破解出来还是费时的。

1
2
3
4
5
6
http {
    limit_req_zone $binary_remote_addr zone=one:4m rate=5r/m;
    limit_req_status 503; 
    
    ...
}

然后在站点配置文件中的server block中,加上:

1
2
3
4
5
location = /wp-login.php {
    limit_req   zone=one  burst=1 nodelay;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php5-fpm.kid.sock;
}

具体参数解释可以围观ngx_http_limit_req_module

这样在Nginx加上登录的请求速率控制,可以在一定程度上密码被降低被暴力破解的风险,而且也不依赖Wordpress的使用人员是否安装一些保护插件。


PermaLink: http://everet.org/wordpress-login-brute-force-attack.html
Tags: Nginx, Wordpress ]]>
<![CDATA[LG G Pad 8.3刷欧版Rom]]> 2014-07-06T11:37:00+08:00 http://everet.org/lg-v500-rom 之前1799买个台国行的LG G Pad 8.3(LG V500),对于这个品质的平板,觉得价格还是非常公道。不过发现国行的平板里面居然没有google apps,升级到4.4.2后,发现安装进去的google服务都是运行就闪退,非常恼火,最后决定还是刷个机吧。

首先是找了CyanogenMod的cm11的rom,然后如果需要刷rom就需要刷recovery了。

然后需要以下这些步骤:

  1. root
  2. 刷recovery
  3. 刷机

电脑里面需要装个adb(Android Debug Bridge),这个去Android官网下个就好了。

Root

我们去xda下个root用的包IOroot25r1。解压后,里面有个kk_root.zip。

然后插上手机,打开开发者模式,开启USB调试。在cmd(或者shell)里面输入以下

1
2
3
4
adb devices
adb reboot recovery
# 重启后,在手机的recovery中选择'apply update from adb'
adb sideload kk_root.zip

然后重启下手机就root好了。

Recovery

Root完后,我们就可以开始刷twrp(Team Win Recovery Project)的recovery了。我们先下载openrecovery-twrp-2.6.3.0-awifi.img,貌似只有2.6.3能够正常启动。

下载完毕后,我们得到一个img文件,此时,我们shell去到下载目录,将img推送到手机sdcard根目录:

adb push openrecovery-twrp-2.6.3.0-awifi.img /sdcard/

接着输入adb shell进去手机的shell,。

1
2
3
4
5
# 先切换到root
su
# 然后刷入recovery
dd if=/sdcard/openrecovery-twrp-2.6.3.0-awifi.img of=/dev/block/platform/msm_sdcc.1/by-name/recovery
exit

刷机

CM11

如果需要刷CM11,可以到cyanogenmod官网下载rom,记得最好下载snapshot,就是M7,M代表Milestone,相对是个稳定版,nightly玩玩还行,平时用就不建议了。不过cm11的rom不知道为啥,超级不稳定,经常用着用着就自己重启了,而且感觉上性能差一些。

CM11刷完后,还需要另外刷gapps,可以在这里下载Google_Apps

LG官方欧版ROM

对于官方rom,可以到xda下载LG-V500_KOT49I.V50020B_GB_Stock-BB-Rooted.zip,这个基于欧版的4.4.2的官方rom稍微修改的。自带了Google Apps。

开搞

下载完毕后,将rom推送到手机:

1
2
adb push LG-V500_KOT49I.V50020B_GB_Stock-BB-Rooted.zip /sdcard/
adb reboot recovery

然后进入到twrp,将手机wipe后,选择install LG-V500_KOT49I.V50020B_GB_Stock-BB-Rooted.zip,然后等一会就搞定了。

刷完后无限重启?

如果已经刷了导致无限重启,按照如下方法进行工厂重置1: 1. 将设备关机,注意是关机,不是关闭屏幕,确保设备没有连接到电脑; 2. 同时按住音量-键与电源键,当LG Logo出现时,放开电源键,仅仅放开电源键; 3. 现在你同时按住音量+键与电源键,注意,这时音量-键也是按住的; 4. 当出厂设置(Factory Reset)画面出现时,放开所有的按键;按照屏幕指示操作;

当中试过用LG的工具,刷官方港版的KDZ文件,不过不知道为啥,我选择了下载的rom,但是刷机的时候,工具还是自动下了个大陆的Rom回来,非常坑爹。有兴趣的同学可以按照下面的链接自己试试。


PermaLink: http://everet.org/lg-v500-rom.html
Tags: Android ]]>
<![CDATA[写了个短链接服务163.gs]]> 2014-05-13T19:30:00+08:00 http://everet.org/short-url-163-gs 昨天知道126.am短链接服务下线了,觉得挺可惜的,就决定利用163.gs做一个短链接服务。

于是就这么开搞了。

为了追求最高效地完成想法,语言当然首选Python,框架也直接就用了最喜欢的Tornado。数据库也直接使用了Redis。因为短链接这种业务还是非常适合使用key-value数据库来做。

数据存储

对于短链接,使用什么数据存储比较合适呢?

最简单的方法是当然是直接在内存里面搞个hash表,然后将数据塞到里面就ok了,这样速度当然是最快的,不过这样如果要多个进程或者多个机器的进程共享数据,就比较蛋疼了,而且这样数据保存和备份都比较麻烦。

另一种比较靠谱的方案是存到key-value数据库中,个人比较喜欢redis,所以就存到了redis里面。

短链接的生成

对于短链接的生成,最简单的方法当然就是直接搞个自增id,生成一个新的就加1(或者类似的方案),不过这样数据库中的链接总量不就暴露无遗了么?而且可以直接遍历出所有的链接,不过这应该不是啥问题。

觉得看上去比较酷的方案是随机生成一个字符串作为短链接。大家一般都是使用62进制。

26个小写字母、26个大小字母再加上10个数字,一个62个。然后我这里暂时用了5位。以后如果有需要再加到6位。

(26 x 2 + 10) ^ 5 = 916132832

虽然只是5位,但是可以表示916132832个域名。

冲突处理

如果随机生成出来的key和已有的冲突,那么就重新生成,直到找到一个空的槽。对于数据量比较小,桶比较空,产生冲突的概率应该极低。

重复的url

生成url前会查一查这个url是否已经生成过了,如果生成过了,那么就直接返回它的短链接。

所以这里要求我们不仅需要存储short_url => long_url的映射,还需要存储long_url => short_url的映射。

因为都直接存到一个redis db里面,所以前面加了前缀。例如

短链接到长链接的映射:id:iEw2H => url:http://EverET.org/。 长链接到短链接的映射:url:http://EverET.org/ => id:iEw2H

短链接的访问

例如在你访问http://163.gs/iEw2H这个短链接的时候,服务器返回301重定向,然后浏览器读Location头到指定网站。

1
2
3
4
5
6
7
8
@  ~/projects/shorturl git:(master) % curl -i http://163.gs/iEw2H
HTTP/1.1 301 Moved Permanently
Date: Tue, 13 May 2014 13:04:26 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 0
Connection: keep-alive
Location: http://EverET.org/
Server: TornadoServer/3.1.1

短链接生成接口

非常简单,直接POST到http://163.gs/short/即可,参数是url

curl -d "url=http://EverET.org" http://163.gs/short/

已知问题

因为生成没有加锁,所以可能出现的问题是如果创建短链接的并发很大的时候,可能会有极低概率出现2个不同的url申请,返回同一个短链接。 这个可以通过加锁来解决。这个日后有这个需要了再继续添加了。

另一个问题是目前只支持http协议,生成出来的短链接都是http协议的。

昨晚(2014-5-12)下班之后得知126.am下线了,我们部门貌似还是有些产品使用了126.am短链接服务,所以觉得挂着126、163的名号的短链接服务还是有一定存在价值,于是头脑发热花了3个半小时在163.gs做一个短链接服务(当然不是网易官方出品)(另外真是头脑发热,还在发烧,吃完退烧药就开始coding)。 –_–||

对于这个短链接服务,其实也不奢望会有多少用户,不过我还是会尽可能地保证它的可用性。

另外我也会继续改进,作为自己的产品、自己技术追求,没有deadline,没有脑残的需求方,可以好好雕琢里面的代码,按照自己的想法去做。

最后,代码当然是open source的 –> url-shorten


Update:

2014-05-14

发了第二天就发现163.gs被微博、微信和360屏蔽了,访问就提示存在安全隐患,个人的短网址服务就是被大厂鄙视。真是悲催…

2014-05-16

经过申诉,腾讯和360、微博解封了163.gs,腾讯电脑管家和360的申诉还是挺不错的,赞一个!


PermaLink: http://everet.org/short-url-163-gs.html
Tags: Python, Redis, Tornado ]]>
<![CDATA[SSH Forwarding导致的垂直越权]]> 2014-03-02T00:38:00+08:00 http://everet.org/ssh-forwarding-vertical-privilege-escalation ssh有个-A选项可以启用Agent Forwarding,而Agent Forwarding是一个非常有用的功能。让我们通过跳板机连上另一台服务器的时候,可以省去将私钥拷贝上去、省去我们再次在跳板机中输入passphrase的过程。通过Agent Forwarding,我们在server-1登录到server-2的时候,server-2会将challenge发送到server-1,然后server-1会将它发回到home-pc,然后home-pc的ssh-agent会将解密后的私钥用来验证,然后完成验证。这个链不管有多长,只要路径上一直保持打开Agent Forwarding,随后的级联登陆都不需要输入passphrase。1

我们man ssh就会看到下面一段。

1
2
3
4
5
6
7
8
-A   Enables forwarding of the authentication agent connection.  This can also be
     specified on a per-host basis in a configuration file.

     Agent forwarding should be enabled with caution.  Users with the ability to
     bypass file permissions on the remote host (for the agent's UNIX-domain socket) can
     access the local agent through the forwarded connection.  An attacker cannot obtain
     key material from the agent, however they can perform operations on the keys that
     enable them to authenticate using the identities loaded into the agent.

使用Agent Forwarding是挺方便的,但是有时候方便的代价就是安全。

我们一般什么时候需要使用Agent Forwarding呢?我觉得在迫不得已的时候再使用它,特别是在大家都有root权限的服务器,就更不应该使用它。正如它的man中的说明一般。

可能需要使用Forwarding的场景

我直接在服务器上写代码,需要pull&push代码

有时候,我们可能喜欢直接在服务器上面写代码,然后用ssh -A让git服务器从我们本地验证。这样我们就不用将私钥复制到服务器上面了。

线上正式机安装代码的时候需要从版本库clone/pull

正式机安装代码的时候需要从仓库拉去代码,需要验证。用Forwarding就不用在服务器放个私钥了。

我需要从一台服务器rsync到另一台服务器

之前和小口聊的时候,谈到有时需要从一台服务器rsync到另一台服务器。如果我们的私钥可以登录这两台服务器的话,那么用Forwarding的时候就可以直接从一台服务器rsync到另一台服务器了。

需要跳板机才能登陆到某些服务器

有时候有些服务器需要通过跳板机才能登录另一台服务器,所以需要Agent Forwarding。

风险

使用SSH Agent Forwarding是有挺大风险的,只要你有root,就可以冒充别人的身份。具体我们可以看Security Issues With Key Agents

我们可以看到agent的socket都放到/tmp下面

1
2
3
4
5
6
7
8
9
10
root@onlinegame:/# ls -l /tmp | grep "ssh-"
drwx------ 2 gz********uan gz********uan      4096  2 28 10:24 ssh-fFesHq2922
drwx------ 2 gu****78      gu****78           4096  2 28 10:15 ssh-mesWFXI787
drwx------ 2 w***i         w***i              4096  2 28 09:49 ssh-MiBNm30073
drwx------ 2 w***i         w***i              4096  2 28 09:44 ssh-PNXoa29541
drwx------ 2 w***i         w***i              4096  2 28 10:31 ssh-SNeGIB3896
drwx------ 2 w***i         w***i              4096  2 28 09:55 ssh-VTkib30511
drwx------ 2 su****a       su****a            4096  2 28 11:19 ssh-XpIKO15823
drwx------ 2 w***i         w***i              4096  2 28 10:02 ssh-ZjubC30883
drwx------ 2 su****a       su****a            4096  2 28 11:19 ssh-zsnXW15762

只要有root,通过SSH_AUTH_SOCK,就可以冒充任何用了forwarding的人。如下:

1
2
3
root@onlinegame:/# export SSH_AUTH_SOCK=/tmp/ssh-VTkib30511/agent.30511
root@onlinegame:/# git clone git@nanny.xxxx.com:w**/sa****.git
root@onlinegame:/# ssh -l *** -p 3xxx0 123.***.***.175

就这样,只要有比我们权限更高的用户forwarding,我们就可以越权访问我们更高权限的东西。

例如我们可以用别人的身份访问自己没有权限访问的版本库的代码,可以冒充别人提交代码留后门,这里想起来一个有人企图在Linux内核代码中留的牛逼的Linux后门2

或者更严重的,可以越权登录本来没有权限登陆的服务器,登陆后,我们仅仅需要在~/.ssh/authorized_keys加上我们的公钥,就可以直接用我们的私钥登录别人帐号了,而且authorized_keys这个也应该也没有人会经常去看,估计留了也很久不会有人发现。或者干脆直接丢一个rootkit3上去,相信大部分Linux服务器都装没有杀软,中上rootkit估计好几年都不会被人发现。

最后,很简单地我们就可以用别人的身份访问自己没有权限访问的版本库的代码,登录自己没有权限登录的服务器。这就导致了垂直越权了。

想起前段时间有童鞋因为将私钥放到服务器上面,被当安全事故通报了。不过感觉Forwarding更加的危险。因为表面Forwarding好像没有东西放到服务器上面,就认为是安全的。其实Forwarding上去可能会导致更危险的情况发生,例如老大Forwarding上去了,于是有root的人就可以变身老大了-_–||。

解决

在我们列举完了问题,那对于现有问题,有什么解决方案呢?

我直接在服务器上写代码,需要pull&push代码

对于这种情况,其实也不需要使用Forwarding,我们在本地写好代码,复制上服务器不就好了嘛。

如果不想自己复制,就可以使用让程序自动复制。如下,我们仅仅开始一个自动同步本地文件夹到服务器的脚本,然后就不需要理它了。我们在修改文件的时候,就会自动同步到服务器。

好吧,对于测试机,我一直都是这样在自己的电脑写代码,然后自动将项目代码同步到测试机,需要安装代码的时候,再去测试机运行安装脚本。

具体自动同步可以围观《无缝同步代码到服务器》。

线上正式机安装代码的时候需要从版本库clone/pull

对于这种情况,其实也没有必要使用Agent Forwarding

像Github,我们可以使用项目专属的deploy key来放到线上机器进行部署。这个key就算被人黑进服务器,复制走了,最坏的情况就是这个项目的代码被人复制走了,但是如果别人都可以读取到服务器中的key,那必然也可以读取到项目的代码了-_–||。不过Github的deploy key不仅仅是read only,还可以写,这就不太安全了。如果deploy key被人复制走了,就还可以写版本库,这就不太好了。

对于Nanny,熊熊提供了basic auth这种这种方式来读取版本库,可以为专门的项目项目专有的access token。

git clone http://gzuser:dc6xxxxxxxx5439@nanny.nxxxx.com/gzuser/project.git

然后我们就可以clone和pull了。这种访问貌似是只读的,所以还是挺不错的。

当然对于用access token clone出来的项目代码,外层文件夹需要将权限改为700,这样就只有自己和root能访问。避免随便一个人都可以用git remote -v看到我们的access token。这个安全性怎样都比forwarding高。因为能进到文件夹的人,本来就可以看到代码了,看到access token又何妨,反正只是该项目只读,大不了经常改改access token。

我需要从一台服务器rsync到另一台服务器

对于从一台服务器rsync到另一台服务器,其实完全可以配置一下rsync,添加些专门用来复制的用户就好了。

rsync服务器的配置

我们在/etc/rsyncd.conf中配置一个module。

/etc/rsyncd.conf
1
2
3
4
5
6
7
8
9
10
11
12
port = 7890

[datafolder]
comment = data rsync between servers
hosts allow = 2.2.2.2 3.3.3.3
path = /tmp/datafolder
uid = vagrant
gid = vagrant
auth users = et_user
secrets file = /etc/rsyncd.secrets
read only = false
list = yes

然后增加密码文件/etc/rsyncd.secrets

/etc/rsyncd.secrets
1
et_user:hellopassword
rsync客户端
/etc/rsyncd.secrets
1
rsync -av --delete --port=7890 --password-file=rsync_pass.txt . et_user@remoteserver::datafolder

这里,rsync的secret文件权限一定需要600,即仅自己可读取,否则会报错。

这样,我们就创建了et_user这个rsync的用户,两边使用密码来访问。所以即便是rsync也不需要forwarding。

需要跳板机才能登陆到某些服务器

这种情况暂时没有遇到过,暂时没有好的想法,对于安全的跳板机还是可以Forwarding过去的。

不过公用的测试机肯定就不是跳板机了,所以就没有必要ssh -A到非常不安全的公共测试机。

总结

SSH Agent Forwarding其实只要稍加利用,还是非常危险的一个漏洞,如果骇客黑进了公共测试机,在里面exploit拿到了root权限,而如果大家都Forwarding进到测试机,骇客就可以趁机冒充我们进入到我们能够进入的服务器,那么就会有大片的服务器沦陷了。所以还是请谨慎使用。


PermaLink: http://everet.org/ssh-forwarding-vertical-privilege-escalation.html
Tags: Hack, ssh ]]>
<![CDATA[无缝同步代码到服务器]]> 2014-03-01T19:38:00+08:00 http://everet.org/auto-deploy-to-server 有时候,用专门的测试机用来测试还是比较方便的,因为上面环境搭好了,而且QA和需求方也可以直接去到测试环境测试。于是就涉及到将代码安装到服务器的这个过程了。

对于代码需要安装在测试机上面,我们有两种方案,一种是直接在服务器写代码,这样写完后,想装的时候就直接运行安装脚本就可以把代码装好了。另一种是在本地写,然后将代码复制到服务器,再安装。

直接在服务器上写代码

对于直接在服务器上写代码,就挺方便的,不过要注意在服务器开个tmux或者screen,这样在突然断线后或者回家,还是可以恢复到之前的工作的环境。如果没有用tmux之类的,那么如果写代码写到一半给断开连接了,那就悲剧的到时重连的时候还需要恢复编辑文件。

在本地写复制到服务器

在本地写代码,我们可以用我们最爱的编辑器1。写完后,觉得好了,就将代码复制到服务器上面,然后去服务器安装代码。这样确实是可以行的,就是每次改点东西,还需要手动复制,就是挺麻烦的。

于是就想自动复制到服务器。那么何时触发复制呢?我觉得在我们修改了,就自动复制就好了。于是我想可以监控文件的变化,如果发生变化,那就调用rsync复制文件。

这里我写了脚本,可以用来监控项目文件夹,如果有变化,就自动复制到远程服务器:

For Mac

mac-auto-deploylink
1
2
3
4
5
6
7
#!/bin/sh

local=$1
remote=$2

cd "$local" &&
fswatch . "date +%H:%M:%S && rsync -aztH --exclude '#*' --exclude .git --exclude .svn --progress --rsh='ssh -p32200' . $remote"

For Linux

auto-deploy-by-rsynclink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh

src=$1
target=$2

echo "source" $src
echo "target" $target

inotifywait -mrq --timefmt '%d/%m/%y %H:%M' \
    --format  '%T %w%f'  \
    -e modify,delete,create  \
    --exclude "(#|\.git|\.svn)" ${src} | while read file
do
    cd ${src} && rsync -aztH --delete --progress --rsh='ssh -p32200' ${src} ${target}
    echo "."
done

所有的代码以及使用说明都在https://github.com/cedricporter/auto-deploy-by-rsync。 需要的同学可以clone下来试试。

使用

例如我们在Mac上面,可以在项目的文件夹根目录运行下面的命令。

auto-deploy-by-rsynclink
1
@ ~/git/vipbar-b2c % mac-auto-deploy . gzhualiang@dev35:~/git/`basename $(PWD)`

在上述例子中,mac-auto-deploy会自动检查文件修改,如果修改了就会使用rsync将当前文件夹同步到dev35这台服务器的~/git/vipbar-b2c目录中。只要在一开始打开就不用理他了,就只管写代码就好了,觉得需要安装的时候,就在服务器的shell运行一下安装脚本,项目就可以安装好了,就不再需要关注代码的复制了,就像在服务器写代码一样了。

在Linux中一样,使用另一个脚本就好了。

Screencast2


  1. 当然在服务器也可以用vim/emacs,不过像我配置的emacs会依赖许多外部程序,公共的测试服务器又不能随便装东西,在受限的服务器用阉割的Emacs还是挺不爽的,所以还是在本地用配好的环境开发是最舒服的。

  2. gif视频,是用Mac自带的QuickTime Player录的,然后用mov2gif将mov装换成gif。


PermaLink: http://everet.org/auto-deploy-to-server.html
Tags: Linux, Mac ]]>
<![CDATA[修改pip/setup.py的源]]> 2014-02-19T21:24:00+08:00 http://everet.org/python-pypi-source 今天在用setup.py安装我们项目代码的时候,发现在安装依赖包的时候,连接一直被墙了。

看到log输出是从https://pypi.python.org/simple/下载包的。

我想修改成douban的源。于是找了一下怎么设置源。

找到最多的是这个例子,首先我是在~/.pip/pip.conf里面写了

~/.pip/pip.conf
1
2
[global]
index-url = http://pypi.douban.com/simple

发现用pip安装东西,确实是从douban的源下载的,不过使用setup.py安装的时候,还是从默认的pypi.python.org下载的。

看来需要的是distutils的配置。找了一下,发现是可以通过~/.pydistutils.cfg来配置distutils的源1。如下:

~/.pydistutils.cfg
1
2
[easy_install]
index_url = http://pypi.douban.com/simple

然后就可以在setup.py安装依赖的时候使用豆瓣源了。

探索setup.py

为啥读的是~/.pydistutils.cfg这个文件呢?于是我决定去distutils源码的目录围观一下,grep pydistutils.cfg *后发现dist.py里面有个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def find_config_files(self):
    """Find as many configuration files as should be processed for this
    platform, and return a list of filenames in the order in which they
    should be parsed.  The filenames returned are guaranteed to exist
    (modulo nasty race conditions).

    There are three possible config files: distutils.cfg in the
    Distutils installation directory (ie. where the top-level
    Distutils __inst__.py file lives), a file in the user's home
    directory named .pydistutils.cfg on Unix and pydistutils.cfg
    on Windows/Mac; and setup.cfg in the current directory.

    The file in the user's home directory can be disabled with the
    --no-user-cfg option.
    """
    files = []
    check_environ()

    # Where to look for the system-wide Distutils config file
    sys_dir = os.path.dirname(sys.modules['distutils'].__file__)

    # Look for the system config file
    sys_file = os.path.join(sys_dir, "distutils.cfg")
    if os.path.isfile(sys_file):
        files.append(sys_file)

    # What to call the per-user config file
    if os.name == 'posix':
        user_filename = ".pydistutils.cfg"
    else:
        user_filename = "pydistutils.cfg"

    # And look for the user config file
    if self.want_user_cfg:
        user_file = os.path.join(os.path.expanduser('~'), user_filename)
        if os.path.isfile(user_file):
            files.append(user_file)

    # All platforms support local setup.cfg
    local_file = "setup.cfg"
    if os.path.isfile(local_file):
        files.append(local_file)

这里有写有加载啥,所以我们直接在setup.py的同目录放置一个setup.cfg,也可以达到同样的效果。

setup.cfg
1
2
[easy_install]
index_url = http://pypi.douban.com/simple

加上配置后python setup.py install安装的时候,依赖就会从douban的源下载了。

用setup.cfg的好处是,这样源的配置就可以跟着源码走了。以后在其他机器上面安装的时候也可以用到douban的源。


PermaLink: http://everet.org/python-pypi-source.html
Tags: Python ]]>
<![CDATA[Emacs分项目保存session]]> 2014-02-16T23:50:00+08:00 http://everet.org/emacs-project-manager 有时候我们在同时写多个不同的项目,这个时候,我们可能会打开多个不同的Emacs实例,然后在不同的Emacs实例中以项目为单位打开文件来编辑。

恢复关闭前的环境

desktop1和session2是Emacs用来保存会话用的,下次打开的时候可以恢复到上次一次关闭状态,还是非常方便的。例如一个项目打开了许多文件(Buffer),关闭之后,在下次打开的时候又可以恢复到之前的状态,就可以避免重新打开一堆buffer恢复工作状态的尴尬情况。这样我们的Emacs就像没有关闭过一样。

问题

这个时候问题就来了,如果我们开了多个Emacs分别在写多个不同的项目,这个时候怎么办呢?

默认情况下,Emacs会在~/.emacs.d下面创建.emacs.desktop文件,用来保存当前的环境。

很明显,如果有多个不同的项目,就会有多个不同的文件集合,所以就明显不能共用同一个.emacs.desktop

解决

解决这个问题的一个方法是,在每个不同的项目文件夹的根目录创建一个.emacs.desktop,然后保存自己这个项目的环境。

我们可以M-x desktop-change-dir,然后将目录切换到项目的路径,然后就可以加载和保存那个项目的.emacs.desktop了。

为了方便,我们可以设置快捷键来切换。

1
(global-set-key (kbd "C-x g d") 'desktop-change-dir)

然后我们就可以通过C-x g d,然后输入项目的路径,就可以加载那个项目之前的开发环境,就可以还原打开的buffer,历史等等。

对于有些常用的项目,我们可以设置固定的快捷键来快速加载它们。

1
2
3
4
5
6
7
8
9
10
11
12
(setq session-key-dir-map '(("c" "~/git/vipbar-b2c")
                            ("b" "~/svn/vipbar")
                            ("o" "~/octopress")
                            ))
(while session-key-dir-map
  (lexical-let* ((item (car session-key-dir-map))
                 (key (car item))
                 (path (car (cdr item))))
    (global-set-key (kbd (concat "C-x g g " key))
                    #'(lambda () (interactive) (desktop-change-dir path)))
    (setq session-key-dir-map (cdr session-key-dir-map)))
  )

这里我设置了C-x g g前缀,例如使用C-x g g o这个快捷键,我就加载~/octopress/.emacs.desktop,就可以恢复之前写博客时关闭环境。使用C-x g g c就可以打开vipbar-b2c这个项目之前关闭的时候保存的状态,就可以恢复到上次关闭Emacs的时候的状态。

其他

如果我们将.emacs.desktop放置到项目的根目录,对于版本控制器,我们就需要忽略这个文件了。

我们需要忽略的文件包括:.emacs.desktop.emacs.desktop.lock这两个文件。可以在全局忽略它们。

对于svn,可以编辑~/.subversion/config,在global-ignores后面加上.emacs.desktop .emacs.desktop.lock

对于git,可以在~/.gitignore里面加上

1
2
.emacs.desktop
.emacs.desktop.lock

然后git config --global core.excludesfile '~/.gitignore'这样全局忽略这两个文件。


PermaLink: http://everet.org/emacs-project-manager.html
Tags: Emacs, Lisp ]]>
<![CDATA[From Linux to Mac OS]]> 2014-02-03T16:10:00+08:00 http://everet.org/from-linux-to-mac 年前终于买了台新电脑,原来的联想Y550也用了4年半了,虽然换了ssd1,不过CPU还是有很大的瓶颈,Core P7350 2.0GHz,不知是不是CPU老化了,跑个浏览器都有些卡,虚拟机直接就没法跑了。而且电池之前换过新的,新的也只能用个1个小时,加上15.6寸又大又笨重,背出去十分辛苦,所以就咬咬牙,换了台便携一点的笔记本。这样去哪里都可以背上电脑。

本来想入手Thinkpad装Linux的,因为非常kde用起来非常方便,可定制也非常强,不过看到Thinkpad的性价比还差过Macbook,就决定入手13 rmbp了。就算mac os不好用也可以装一个linux。

拿到rmbp后,一直在惊叹屏幕的的惊艳,看东西十分锐利,可视角和色彩也非常满意。

刚开始用Mac OS有很多东西都是不习惯,不过很快就适应了。

我把Emacs,zsh的配置2拷过去基本上就可以直接使用了,而且高分屏看上去字体非常清晰。常用的软件用brew安装一下,就都回来了。Chrome和Firefox的配置登陆后就自动同步过来了,所以立马就恢复到了过去的工作环境,果然都是*nix家族,大部分程序基本都是兼容的。

GNU

原来Mac OS里面的像ls、sed等都是freebsd的,有的参数和gnu的不一样,用起来非常不爽,所以决定换回GNU的。可以用brew把GNU常用的命令装回来,替换掉原来的即可。具体可以参照这篇文章Install and Use GNU Command Line Tools on Mac OS X

Emacs

之前在emacsformacosx3下载的Emacs,发现在全屏后,万恶的工具栏又冒了出来,无论怎么都关不掉,后来发现是那个网站编译的Emacs不支持全屏。所以就用brew的Emacs,装了在全屏没有出现工具栏自动冒出来的情况。

1
2
3
brew install emacs --cocoa
# 在此之前需要把原来/Applications下面的Emacs.app改个名或者删除
sudo ln -s /usr/local/Cellar/emacs/24.3/Emacs.app /Applications -r

虚拟桌面

OS X的虚拟桌面有个让我比较蛋疼的地方是,如果一个程序全屏后,它会离开原来的桌面,然后创建一个新的虚拟桌面出来,这个让我非常的恼火。因为我之前都是把每个程序都固定放在一个桌面,然后用Super + [1-8]来切换,这个就可以迅速地去到自己想去到的程序那里。

现在暂时没有好的解决方案,所以就暂时不全屏了。

窗口切换

原来的窗口切换太坑爹了,不能在只一个虚拟桌面里面切换应用程序,所以找了witch4,这个又要$14美刀,太坑爹了。目前还在用试用版,到时再看看是不是要支持正版了。

效率工具

Alfred

Alfred5应该是Mac上最强的效率工具了,购买Powerpack后功能就会非常强大,而且有大量的workflow可以扩展Alfred。Alfred就像一个非常强大的前端,可以调用后端程序处理,然后返回结果。

具体可以看看知乎:zhihu

如果支持正版的话,建议购买Mega,这样以后可以免费升级,当时我买了Single License,现在后悔了。到时3.0又要花钱了。

BetterTouchTool

BetterTouchTool6可以为可以为触摸板定制手势的强大的工具,可以定制单指、双指、三指、四指甚至5指11指的手势,免费的。

其他

QQ

对于QQ的聊天记录,我把它从Windows里面直接复制到了/Users/cedricporter/Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ这个目录里面,然后重新打开Mac QQ,QQ就提示升级历史记录,然后就把聊天记录迁移到了Mac上面了。

这个是我从07年开始的聊天记录,坚持了7年了。

剪切

突然发现Mac下面Finder居然没有文件剪切,难道我要复制后把源文件删除?

后来发现原来是这样的:按Command+C复制,然后在目的文件夹按Command+Option+V剪切过去。

像苹果内置的日历和邮件客户端都非常好用,对于Google的服务支持的非常不错。赞一个。

而且有Evernote、ps、acdsee等等非常优秀的软件可以在Mac OS上面跑,这让我可以彻底离开Windows了。如果打机的话,决定还是买个xbox或者ps4回来玩了。还是比较喜欢高质量一些的游戏。

Octopress

Octopress安装很正常,就是lsi很有问题,它提示需要安装gsl。我使用brew install gsl然后gem install gsl后,一直提示:“Notice: for 10x faster LSI support, please install http://rb-gsl.rubyforge.org/”,但是gsl我是装了啊!

搞了很久我在octopress目录用ruby -e 'p $:'打印的PATH里面都没有gsl的path,但是真的已经装了啊。纠结了很久,最后在Gemfile里面加上了gem 'gsl', '~> 1.15.3',然后就可以了。

最后觉得,其实Mac OS的可定制性也是很强的,而且生态圈也非常健康,有许多好用的应用。不过基本上增加一个功能,都需要花钱去买软件来扩展。这个对比起Linux来说还是有些不习惯。例如有一个把顶部全局菜单那里的程序图标隐藏的程序bartender7,就这么简单的一个程序,就要96人民币,真是贵,而且这个功能不是应该系统内置的吗!?

不过Mac OS的好处是,相对于Linux的那些桌面,还是非常稳定的,不会说动不动出现一些非常诡异的问题。毕竟一个是由一堆收费软件构建成的,一个是由开源软件筑成的。

不过想想,程序员也是要吃饭的,所以大部分软件还是收费的质量会高些。


PermaLink: http://everet.org/from-linux-to-mac.html
Tags: Linux, Mac ]]>
<![CDATA[让Octpress文章里面的链接打开方式为新tab打开]]> 2014-01-01T20:19:00+08:00 http://everet.org/octpress-make-link-target-blank 本来在Octopress使用的链接是用markdown方式插入的链接[EverET.org](http://EverET.org),这样在看文章的时候点击link就会直接跳转到url那里了,我不想有这样的体验,所以决定给文章里面的url加上target="_blank",让用户点击的时候在新窗口打开链接。

这个在哪个做比较好呢?想了一会还是在前端做比较好。非常简单,在source/_includes/custom/head.html加上对http://everet.org/javascripts/link-target-blank.js,这个文件的引用即可:

<script src="http://everet.org/javascripts/link-target-blank.js" type="text/javascript"></script>

内容:

1
2
3
4
5
// Author: Hua Liang[Stupid ET]

$(function () {
    $("div.entry-content a[href^=http]").attr("target", "_blank");
});

就是将文章下面的a标签中以http开头的链接加上target="_blank"

为什么要判断以http开头呢?因为文章里面的链接还有脚注和目录的链接,这些链接其实不需要修改的。

Done!


PermaLink: http://everet.org/octpress-make-link-target-blank.html
Tags: Javascript, Octopress ]]>
<![CDATA[学习Android——环境搭建]]> 2014-01-01T17:09:00+08:00 http://everet.org/learn-android-start 年底了,部门里面开始了移动端开发的培训,有ios和Android的选,不过我这个屌丝没有mac,而且也挺喜欢Android的,所以就毫不犹豫地选择了Android。

我的一直都在用Ubuntu,Linux菜鸟用户。所以就在Ubuntu上面开发了,不过这个也让我遇到了一个诡异的问题,在后面再说了。

Android SDK

最开始当然是下载Android开发的sdk,直接去官网下载就好了,如果平时没有用Eclipse,可以下载ADT bundle,里面出了SDK外还自带了一个Eclipse以及相关的插件。

Java

在此之前,当然需要安装Java,虽然可以apt-get install openjdk7-jre就装好了Java,不过还是建议安装Oracle Java,这个之前在玩minecraft的时候,就感受过两者的速度差异。

至于如何安装Oracle Java,可以参看这篇文章:Linux 下安装配置 JDK7

下载sdk

我们可以打开sdk/tools/android这个,然后就打开了Android SDK Manager,在这里我们可以下载我们需要的sdk。

模拟器加速

x86 img

为了加速模拟器,非常建议下载Intel x86 Atom System Image,这个在Intel的CPU上面运行模拟器的速度会大大提高,这个是大牛宇哥推荐的,本来用ARM EABI v7a System Image挺卡的,公司的电脑开个机也要挺久的,现在用Atom仅仅需要不到10秒就可以打开一个4.2的模拟器进到桌面,运行也非常流畅。

kvm

另外,宇哥说如果CPU支持虚拟化技术,打开可以提高性能,一般主板默认会关闭,需要在BIOS打开。

打开CPU虚拟化后kvm就可以工作了,可以提高一定的性能。可以lsmod看看kvm有没有工作。

1
2
3
➜  ~  % sudo lsmod | grep kvm
kvm_intel             132891  0
kvm                   443165  1 kvm_intel

不过我的笔记本的渣渣CPU(Intel® Core™2 Duo Processor P7350)就不支持VT,于是开机还是挺慢的,不过还是比ARM开个8分57秒好多了。用x86的镜像仅仅1分30秒就可以开完机了。

gpu

我们在创建avd的时候,可以选上Use Host GPU,这样就可以有一定的加速。不过snapshot不可以和Use Host GPU同时使用,还是挺可惜的。

两者的区别可以看What is Snapshot and Use host GPU emulation option(s) for?

Linux下面的bug

Android课程的老师分享说在Linux下面有个Bug,会导致CPU 100%。我们可以在~/.android/avd/[avdname].ini加上hw.audioOutput=no,就可以解决CPU 100%的问题了。

kubuntu上面遇到的问题

adb

我在kubuntu 13.10 x64上面打开adb,直接报“No such file or directory” trying to execute linux binary,想起来之前有遇到过类似的问题,宇哥告诉我是因为缺了些so,一般情况都是64位系统里面缺了32位的一些库。

本来想安装ia32-libs,不过发现这个在ubuntu 13.10居然去掉了,不过还是有解决方案的1

1
2
sudo apt-get install libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5
sudo apt-get install lib32z1

Eclipse Crash

接着运行Eclipse,就一直crash,报的是

1
2
3
4
JVM segfaults; error log file contains

# Problematic frame:
# C [libgobject-2.0.so.0+0x19838] g_object_get_qdata+0x18

无论我用openjdk还是oracle java,都会crash,后来去找了发现原来是gtk的bug。暂时用下面的方法打开Eclipse就好了,具体见2

1
$ GTK2_RC_FILES=/usr/share/themes/Raleigh/gtk-2.0/gtkrc ~/android/adt-bundle-linux-x86_64-20131030/eclipse/eclipse

SDL

终于开了Eclipse后,启动模拟器报:SDL init failure, reason is: No available video device

发现又缺了个32位的库,sudo apt-get install libsdl1.2debian:i386装上3

无线adb

宇哥又介绍了一种无线adb,在手机安装ADB Konnect (wireless ADB),然后按照程序的提示用adb连接上手机就可以远程无线调试了。


PermaLink: http://everet.org/learn-android-start.html
Tags: Android ]]>