2012年12月6日星期四

终端神秘退出

这几天我的GNOME Terminal有时在敲命令时会退出。今天发现,在输入完命令和空格再按Tab后就会退出。这个问题必须要解决,否则根本无法正常工作。
  • 起初怀疑是GNOME Terminal更新中引入的bug,Google了一下没有相关的结果。
  • 我试验了Teminator和XTerm,也都有同样的问题。
  • 怀疑GNOME的快捷键设置出问题了,把Tab按键映射成了Ctrl-D。但是在Vim或者Gedit里面,Tab是可以正常输入的。
  • 重启了电脑,问题还是有。怀疑是不是本机Readline的设置有问题。登录到服务器上没有这个问题。证明了是本地的问题。
打开.bashrc看,最底下是最近加的一行:
set -e
为什么加这行呢?set -e是让Bash遇到错误(命令返回值非0)就退出,我一般在写Bash脚本的时候在最前面加上这一句。后来我想在.bashrc里面加上,以后的脚本不是都不用加了吗?没有多思考就加上了,结果导致了终端神秘退出的问题。

把这一行注释掉问题就解决了。那为什么按Tab会出错呢?在Bash里面输入:
set -x
输入命令和空格后再按Tab,就会输出Bash补全的很长的输出:
$ a + local compdir=./completions
+ [[ /usr/share/bash-completion/bash_completion == */* ]]
+ compdir=/usr/share/bash-completion/completions

.......
在Bash设置了set -e的情况下,应该是中间有一条命令的返回值非0,所以导致Bash退出了。

由此看来,set -e是根本不能放在.bashrc里面的,否则无法交互使用终端了。需要单独写在脚本里面。另外,set -ex是调试Bash脚本很方便的东西,可经常使用。

2012年12月2日星期日

Android下的自由软件

智能手机和传统的计算机一样,有着各种各样的病毒、流氓软件、窃取用户隐私的软件。也和计算机一样,使用自由/开源软件可以保证这些软件让用户绝对放心。

因为苹果的iOS AppStore的用户许可和GPL的许可不兼容,或者说苹果公司对自由软件很不友好,所以我只对Android下的自由软件感兴趣。

Google Play商店并不提供按照许可来搜索应用,所以我以前搜索的方法比较土,就是搜索github, open source, sourceforge等这样的关键词,来找找自由软件。比较经典的自由软件有ConnectBot,现在越来越多了,比如Firefox, FB Reader。维基百科上有一个页面,列出了Android下的自由软件,还有F-Droid网站AOpenSource网站,上面也都是Android下的自由软件。

2012年11月27日星期二

Nginx的414错误

请求的URI太长了,Nginx给出了414错误。可通过这个参数调整Nginx可接受的URI长度。Nginx配置中,对该server的日志记录在单独的文件中,但出错时日志只记录在默认的日志文件中。应该是请求URI太长了,Nginx还没有处理到HTTP请求的Host头部,所以不知道是哪个虚拟主机。

2012年11月13日星期二

让echo命令不显示空格

Bash里面
echo {a..z}
会显示:
a b c d e f g h i j k l m n o p q r s t u v w x y z
怎么让它不显示空格呢?试了试IFS不管用,在Freenode上问,人说echo本来就是要显示空格的。这在help echo输出的内置帮助中没有说明,但在info echo输出的外部命令手册中有说明。可以用
printf %s {a..z}
不显示空格。如果必须用echo而不显示空格,看似可以用
echo -e '\b'{a..z}
但这只是看起来没有空格,并不是单纯的26个字母的输出。如用rev倒序上面的输出得不到正确结果:
$ echo -e '\b'{a..z} | rev
                         a
cat -e来看:
$ echo -e '\b'{a..c} | cat -e
^Ha ^Hb ^Hc$
$ echo -e '\b'{a..c} | rev | cat -e

c^H b^H a^H$
顺序的时候空格不显示,但是倒序的时候,每次退格到字母下方(除了a)再打印一个空格,所以最后就只剩下a了。

2012年11月12日星期一

查看Django Debug Toolbar

我在本地打开线上staging环境的Django网站,想用Django Debug Toolbar来查看SQL查询的情况。Django Debug Toolbar显示的默认条件settings.DEBUGTrue,并且用户IP在settings.INTERNAL_IPS里面。但是manage.py启动的服务器在日志里不显示IP地址:
 [12/Nov/2012 21:05:35] "GET /releng/ HTTP/1.1" 200 275953
我想从Staging服务器上的Django日志里面看到本地电脑访问时的IP地址,找到了这个ticket,通过修改basehttp.py可显示IP:
[12/Nov/2012 19:22:21] (119.253.xx.xx) "GET /static/css/base.css?v=bc043 HTTP/1.1" 200 9301
然后把这个IP加入INTERNAL_IPS,开启settings.DEBUG即可显示Debug Toolbar了。

其实不用这么麻烦,可以在DEBUG_TOOLBAR_CONFIG里面设置'SHOW_TOOLBAR_CALLBACK'指到对应的函数即可,见Debug Toolbar的文档。

RAR压缩包乱码的处理

别人用邮件发过来的RAR压缩包(必然是Windows下创建的,我用File Roller解压缩后文件名是乱码。应该是Windows下用的GBK编码,而Linux下用的UTF8编码的问题。用Ubuntu里的unrar-nonfree包的unrar命令解压缩没有乱码,而用free版的unrar也会有乱码,可见nonfree版才会正确处理编码。File Roller集成的必然是free的代码,所以也不能正确处理。

鉴于RAR是封闭、私有的文档格式,我就不给File Roller和unrar报告bug了,他们不能处理也很正常。

2012年11月8日星期四

安装Deb包后配置文件找不到

Debuild编译打包了带封禁模块的Nginx Deb包,需要用dpkg命令安装nginx, nginx-common和nginx-full三个包。可是装完后/etc/nginx下只有几个目录,没有配置文件。上次用同样的包在Ubuntu下安装的,一切都正常。

后来发现,是dpkg -i安装时,即使依赖包没有,显示一堆错误,也会安装上去的,但配置过程会失败。大概是中途依赖没满足,安装后nginx-common包里面的conffiles没有得到配置,所以最后/etc/nginx里面没有配置文件。以后用dpkg安装时,一定要确保安装和配置一次成功,才能避免这种陷阱。

2012年10月23日星期二

Eval is evil

PyYAML读取Puppet的YAML文件,会从中提取出一个ruby变量,成为Python的datetime模块定义的datetime对象。转换为JSON时会出错,因为JSON不知道怎么处理datetime对象。

试了用repr先处理这个对象,即可转换为JSON,在Django中处理时再eval,就可原样返回之前的datetime对象。这样做很方便,无需知道对象的具体类型。但是太不安全了,因为没有内容判断,把从文件中得到的任何东西都进行eval。测试的时候可以eval,实际代码中是用datetime类的strftimestrptime函数配对转换的。

2012年10月21日星期日

Django项目的路径设置

一个别人的Django项目,在命令行用manage.py启动是正常的,但在Eclipse里启动后却出错,页面显示找不到django-session表。发现是找不到SQLite数据库文件的原因。可以看出,django-session是Django进程访问的第一个表,以后如果碰到这个错误,那么一定是数据库配置的问题。

新建PyDev Django项目时,会有对话框来设置项目,包括数据库类型和库名。对于目录里已有相关文件的项目,这个设置是无效果的,该设置是为了给新建项目生成settings.py。出错开始,我还以为需要改正在这个对话框提供过的设置,就在项目属性里找相关的设置,但找不到。其实所有Django相关的设置都在settings.py里面。

settings.py里面,SQLite数据库文件是直接写的文件名,这就是个相对路径,相对于Django进程的工作目录,即getcwd(3)的结果。如果不是从项目根目录启动manage.py(假设settings.pymanage.py都在根目录),那就找不到数据库文件,因为在PyDev里面启动Django时,进程工作目录是Eclipse的Workspace所在目录。
所以不要提供相对路径,而应该使用“动态的绝对路径”。在settings.py的开头写入:
import os.path
ROOT = os.path.dirname(os.path.abspath(__file__))
这样ROOT就是settings.py文件所在目录,根据ROOT来指定项目文件的目录,包括SQLite数据库文件、静态文件等,不论从哪里启动项目,文件都可以找到。

2012年10月6日星期六

自动挂载存储卡的问题

弄了一个4GB的Micro SD卡,可以把手机上2GB的升级了。把手机用USB存储模式连接到电脑,SD卡没有自动挂载,就用mount /dev/sdb1 /mnt挂载了,然后用tar把卡上的内容打包。把新卡换到手机上,再用tar把打包的内容释放到新卡上,但是发现原来的中文文件名字,在压缩包里面显示的是一串问号?,导致解压失败。

因为存储卡是VFAT文件系统的,想起默认的mount会有编码问题,但是懒得查mount选项了,就想搞定自动挂载,因为Ubuntu会对VFAT的移动设备自动挂载,会设置一系列参数,不会出现编码问题。

在Ubuntu的官方文档看到自动挂载udisks实现的,我只好手动试试:
/usr/bin/udisks --mount /dev/sdb1
结果出错,说是
Mount failed: Error mounting: mount exited with exit code 1: helper failed with:
mount: only root can mount /dev/sdb1 on /mnt/pi
看到那个/mnt/pi就知道原来是我在fstab文件里面写了/dev/sdb1的条目,所以udisks没有自动挂载,把这个条目注释后,就可以自动挂载了,而且也没有文件名乱码了。自动挂载的参数是:
/dev/sdb1 on /media/595B-BB30 type vfat (rw,nosuid,nodev,uid=1000,gid=1000,shortname=mixed,dmask=0077,utf8=1,showexec,flush,uhelper=udisks)
自动挂载设定了好多有用的参数,比手动挂载方便多了,utf8=1应该就是处理编码问题的选项。

2012年9月25日星期二

Django模板的forloop出错

在Django模板的for循环中用了一句:
 {% if not forloop.last %}
出错了:
Exception Type: UndefinedError
Exception Value: 'forloop' is undefined
Google搜索未解,再看出错信息:
Exception Location: /usr/local/lib/python2.7/dist-packages/jinja2/environment.py in getattr, line 372

原来用的是Jinja2。加上Jinja再搜索找到了文档,原来Jinja2里面forlooploop,把forloop改为loop就好了。

2012年9月1日星期六

NetworkManager和Upstart

Ubuntu桌面下,网络是由NetworkManager管理的。NM很好用,但是有时要配置复杂网络,只能用interfaces等网络配置文件搞定。

我给eth0设置了静态IP,用于办公室环境(其实也是DHCP网络)。回家之后这个静态IP得不到,开机的时候会等待2分钟时间让网络起来,先显示:
Waiting for network configuration...
1分钟后是:
Waiting up to 60 more seconds for network configuration...
最后是:
Booting system without full network configuration...
这在Launchpad上有N个bug报告。之后进入Ubuntu,interfaces文件没有生效,而且NM都没自动启动呢。来分析一下这个问题。

现在的Ubuntu在/etc/init.d下,System V Init任务和Upstart任务都有。如果是Upstart任务,则都链接到/lib/init/upstart-job,如NM:
tux@macbook:/etc/init.d(0)$ ls -l network-manager
lrwxrwxrwx 1 root root 21 May 25 12:01 network-manager -> /lib/init/upstart-job
开始我看到NM在/etc/rc?.d下没有启动脚本,就用update-rc.d创建了链接,但还是不能自动启动。后来才明白Upstart任务的启动和停止控制不在/etc/rc?.d目录下,而是在/etc/init目录。NM的配置文件为/etc/init/network-manager.conf。里面有:
start on (local-filesystems
      and started dbus
      and static-network-up)
就是说NM要在local-filesystem, dbusstatic-network-up之后才会启动,而这个Upstart Event(事件)应该是要在interfaces中的网络接口启用之后才会发生(Emit)。所以如果interfaces文件失效,NW也起不来。

那么开机等待的两分钟在哪里配置的呢?看了这个文章后才知道,是在/etc/init/failsafe.conf里面配置的。具体看该文件可以知道个大概。

2012年8月29日星期三

PyDev的Debug Console

用PyDev的远程调试器(Remote Debugger)来调试Django程序,只看到调试服务器(Debug Server)的控制台(Console)(见下图),看不到PyDev的交互式调试控制器(Interactive Debug Console):
其实可以在Console视图(View)的Open Console的按钮点击,选择PyDev Console:
在弹出的对话框选择PyDev Debug Console:
然后在Console视图的Display Selected Console选择刚刚开启的PyDev Debug Console:
这样就可以出来了,在这个Console里面可以对断点处各个Frame的状态进行检查,超实用。但是还有个问题,如果鼠标在Debug视图里面点击,Console视图就会切换到Debug Server。可以点击Console视图的Pin Console按钮:
这样Debug Console就不会被自动切换了。

2012-10-12更新:
如果在Open Console对话框不能选PyDev Debug Console:
上面写着:Start the debugger and select the valid frame。在Debug的过程中,在Debug视图选中一个Frame即可选择PyDev Debug Console了。

GCC找不到标准库

一个开发的哥们,说在开发机(CentOS)上可以编译成功的C++程序,在线上机器(Ubuntu)上就不行。我发现是链接不了pthread库,而这个库是安装了的。然后另一个和他一起的哥们,来抱怨Ubuntu的动态库路径不标准。

我在自己的Ubuntu桌面上写的多线程的C程序,又编译了一下是没有问题的啊。后来他发现是应该把动态库放到编译命令行的最后。原来是:
g++ $(INCLUDE) $(CPPFLAG) -g $(LLIB) -o $(OBJ) $^
改成下面的就好了:
g++ $(INCLUDE) $(CPPFLAG) -g -o $(OBJ) $^ $(LLIB)
我又回忆起这是一个C的FAQ。我的Makefile里面-lm -pthread是写到最后的,所以没有问题。

2012年8月27日星期一

SSH批处理执行命令的问题

要在多个主机上执行一个命令,可以用Bash来循环。比如主机名在一个叫hosts的文件里面:
db14
db15
srv06
...
用一个while循环执行结果如下:
$ while read -r host; do ssh $host hostname; done < hosts
db14
只在第一个服务器执行后就退出了。但是这样执行(不推荐):
for host in `cat hosts`; do ssh $host hostname; done
是可以遍历所有服务器的。为什么呢?请先自行思考还再看下面的答案。

经过诊断,是SSH吃掉了标准输入的内容。while循环的标准输入被定向到了hosts文件,< hosts0< hosts的简写,0是该Shell进程的标准输入的文件描述符。注意这里0<之间不能有空格。

类似的问题,曾经在两个脚本里面遇到需要解决,我是让外层循环的标准输入用一个非默认的文件描述符,比如用3的话命令改成这样运行:
while read -u 3 -r host; do ssh $host hostname; done 3< hosts
同时要让read从这个文件描述符来读取内容。或者让SSH不要读取任何内容:
while read -r host; do ssh $host hostname </dev/null; done < hosts
其实SSH已经有这样的选项了,-n即是,所以也可以如下写:
while read -r host; do ssh -n $host hostname; done < hosts

2012年8月22日星期三

Python的文档

程序出了bug。我用rstrip('.py')来去掉文件名的扩展名,但是下面这个结果却出乎意料:
In [6]: 'gt_php.py'.rstrip('.py')
Out[6]: 'gt_ph'
在IPython里面用rstrip?看不到为啥,查Python的官方文档,原来'.py'不是当后缀来处理的,而是从后往前碰到的'.', 'p''y'都会去掉,所以会有上面的结果。

前不久用os.path.join的时候碰到该函数一个诡异的行为,在docstring里面没有说明,在官方文档上才有详细解释。看来docstring还是不完全,要记得查看官方文档。

KVM的桥接网络

按照官方文档在Ubuntu下安装KVM及虚拟机,竟然出奇地顺利。网络自然是桥接(bridge)爽啦,但是在办公室DHCP环境下不能保证虚拟机和宿主机都是固定IP。然后竟然发现即使虚拟机是DHCP,桥接情况下可以从宿主机直接通过虚拟机的主机名来访问。要做的是把libvirt的IP地址:
$ ip a s virbr0
4: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
    link/ether 2e:7f:c8:81:58:7f brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
192.168.122.1放到宿主机DNS服务器的第一个即可。具体可在宿主机interfaces文件的br0或者eth0配置中增加一行:
dns-nameservers 192.168.122.1 192.168.0.2 192.168.0.3
相关支持参见resolvconf(8)

2012年8月20日星期一

神秘的Cron任务

周末晚上需要暂停一个用cron定期运行来监控SSH Tunnel连通性的脚本。但是无论我把脚本去掉执行权限,还是把脚本改名,最后把Crontab里面的任务删除掉,我的邮箱还是每隔10分钟就收到一封报警邮件,仿佛是一个藏在暗处的幽灵在捉弄我,我都快要抓狂了。

后来用ps发现有好几个脚本进程在运行,把这些进程一一杀死后邮箱才安静了。我分析是脚本运行的(超时)时间太长,在10分钟内没有结束,导致不断积压监控脚本的进程。尽管已经停止了cron任务,但是这些进程还在,它们超时结束的时间间隔也会是10分钟,所以还会以10分钟的间隔发出邮件,让我看起来似乎是cron任务还在运行。

搜了一下类似的情况也有发生,可以用GNU的timeout命令来解决。

2012年8月18日星期六

Python中的*和**

这里说的***不是乘法和次幂的运算符,而是函数定义中的形参(parameters)和函数调用中的实参(arguments)。

文档中把它们的用法很简洁得说清楚了。怎么记住***在函数定义和函数调用时的区别呢?简单地概况,就是“定义的压缩,调用的解开”。
  • 若出现在函数定义中,则实际调用时把接收的一串arguments或keyword arguments,压缩成元组或者字典
  • 如出现在函数调用中,则把接收的iteratable或者mapping,解开为一串arguments或者keyword arguments
在下面这个没有做任何装饰的装饰器里面:
def decorator(target):
    def caller(*args, **kwargs):
        return target(*args, **kwargs)
    return caller
可以看到放在定义和调用时的区别。

2012年8月8日星期三

SSH的公钥登录问题

今天公司有个妹纸的Git不能用了,提示输入密码。我查了Gitosis配置没问题,再去找她,原来她自己照着从网上搜来的一篇博文搞公钥登录,没有搞成。

Git不能用,那就说明Gitosis中的公钥和客户端的私钥不配对,她的确承认应该是把私钥搞乱了。折腾好几次,SSH的公钥登录就是不成功,服务器提示输入密码。最后试着把服务器上她的authorized_keys文件的权限由664改为600,然后就可以了。这个文件别人的确是不应该有写权限的,否则会有安全问题,不过SSH的文档里面貌似没有说权限错误会拒绝登录。

郁闷的是该同学开始没告诉我自己做了什么操作,害我从Gitosis的配置查起,后来才告诉我自己折腾了什么。

2012年7月14日星期六

Ibus的启动和退出

在Macbook Pro上新装了Ubuntu 12.04 LTS,但是进入桌面后ibus不会自动起来,就在GNOME的Startup Applications里面添加了ibus-dameon命令的启动。

如果重启Lightdm后,ibus的Language Panel出不来。发现是原来的ibus-daemon进程还在导致的。解决办法是在/etc/lightdm/lightdm.conf中加入
session-cleanup-script=/home/tux/bin/cleanloggingout
这里指定了Lightdm退出时执行的脚本cleanloggingout,内容为:
for pid in $(pgrep ^ibus); do
   kill $pid;
done
就是在退出时,杀掉所有ibus相关的进程就好了。这样不管怎么重启Lightdm,进入桌面后ibus都是正常的。

另外,Lightdm更多的配置选项可以在 /usr/share/doc/lightdm/lightdm.conf.gz看到。

2012年7月13日星期五

Ubuntu遇上Mac

现在工作用电脑选的是Macbook Pro,具体型号是15寸8,2。用了这么多年Linux桌面习惯了,而且在我看来Linux桌面的不少体验要好于Mac,这里不细表了。我装了个Ubuntu用。

参考了Ubuntu详细的帮助文档,折腾了几次,终于装上去了。传言要同时插入启动光盘和U盘才可以的,我最后成功也是这样的,但没有再试验其它方法。

即使默认安装缺少的驱动,装起来也很方便。键盘上的多功能键、触摸板的多点触控都支持,这一点比较出乎意外。外接27寸的显示器也可以搞定,只是如果拔下显示器再插上的话,需要登出一下才能让双显生效。另外,电源管理也不行,有时候闲置状态风扇也会转。Macfanctl比较好玩。

我还是喜欢Ubuntu桌面胜过Mac。

2012年6月29日星期五

Cron任务没有执行

Web方式显示服务器历史负载的图中,有几天的内容是空的。昨天我从头看代码。回家后想想既然平常能正常显示,那么程序应该不会有问题,应该直接从问题入手,先查询数据库。

今天把程序在IPython里面import了调试,发现那几天的负载数据库表不存在。往这个数据库写数据的程序是个cron任务,在syslog里面没有找到任务执行记录,在root的邮件里面也没有看到任务执行出错的邮件。然后在cron(8)手册里面,发现/etc/cron.d里面的任务名字不能带点,而入库的任务名字叫checknagios.cron

另外,某人同时用 /usr/share/doc/cron/examples/cron-tasks-review.sh检查,出现提示:
WARN: The file /etc/cron.d/checknagios.cron will not be executed by cron: Does not conform to the run-parts convention
更加确认了这一点。

貌似复杂的问题,竟然源于一个点。把任务文件名中的"."去掉就解决了。

2012年5月28日星期一

libgail加载错误

在Ubuntu 12.04下面,运行GTK或者PyGTK程序都会出现如下错误:
Gtk-Message: Failed to load module "gail"

** (ancestry.py:8295): WARNING **: (../../atk-adaptor/bridge.c:793):adaptor_init: runtime check failed: (root)
这篇文章提到类似的错误,原因是32位加载了64位的libgail库。电脑上安装的libgail.so文件只有32位的:
$ locate libgail.so
/usr/lib/i386-linux-gnu/gtk-2.0/modules/libgail.so
$ file `locate libgail.so`
/usr/lib/i386-linux-gnu/gtk-2.0/modules/libgail.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=0xaa9af3bc0239d5c04771d390b3e9ea1fe4277d03, stripped
找到对应的包:
$ dpkg -S /usr/lib/i386-linux-gnu/gtk-2.0/modules/libgail.so
libgail-common:i386: /usr/lib/i386-linux-gnu/gtk-2.0/modules/libgail.so
在Synaptic(Aptitude处理multiarch有bug)里面libgail-common有两个:libgail-common:i386libgail-common,后面这个是64位的,安装上后文章开始的错误便消失了。

2012年5月27日星期日

换行符和BOM引起的麻烦

最近整理电脑里的Python程序,有一个程序运行时出错:
$ ./absent.py
./absent.py: line 1: $'\357\273\277#!': command not found
./absent.py: line 4: $'\r': command not found
然后鼠标箭头变成了一个十字。如果如下:
$ python absent.py
用Python命令来执行是好的。这个文件的前5行如下:
#! /usr/bin/env python
# -*- encoding: utf-8 -*-
# 设置不同的文字屏保,2008年6月5日

import gtk
那个十字以前就碰到过,是用./执行时程序被当成了Shell脚本,所以Python的import gtk语句被当成ImageMagick的import工具来执行了,那个十字形鼠标箭头是import用来选取截图窗口的。

这个文件是我在原单位办公室的Windows电脑下创建的,所以马上意识到是换行符引起的。Linux下的换行符是\n,而Windows下创建的文件换行符是\r\n,所以第一行其实是这样的内容:
#! /usr/bin/env python\r\n
在Linux下,系统只把\n识别为换行符,把python\r连起来识别了。我创建了一个测试程序dos.py
#! /usr/bin/env python
# -*- encoding: utf-8 -*-

import this
然后在Shell下用
sed -i 's/$/^M/g' dos.py
把换行符转成Windows下的\r\n。^M是用Control-V和Control-M打出来的。这个文件执行出错:
$ ./dos.py
: No such file or directory
当然,用Python命令来解释是好的。上面的错误可以在Shell下用等同的命令复现:
$ /usr/bin/env python^M
: No such file or directory
如果真的有python^M这个命令会呢?在~/bin目录里创建到Python解释器的链接:
ln -s /usr/bin/python python^M
~/bin目录在路径的前面,这样python^M命令可以找到了。再执行一次就好了:
 $ ./dos.py
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
......
 其实不要用env,直接在Shebang里面指定执行文件:
#! /home/tux/bin/python
# -*- encoding: utf-8 -*-

import this
出错会更直观:
$ ./dos.py
bash: ./dos.py: /home/tux/bin/python^M: bad interpreter: No such file or directory
~/bin里面创建名为python^M的链接后,就又可以执行了。再回到开始的错误:
$ ./absent.py
./absent.py: line 1: $'\357\273\277#!': command not found
./absent.py: line 4: $'\r': command not found
这个错误和上面dos.py的不同,这'\357\273\277#!'是哪里来的呢?把absent.py文件这样打开:
$ cat -e absent.py
M-oM-;M-?#! /usr/bin/env python^M$
# -*- encoding: utf-8 -*-^M$
# M-hM-.M->M-gM-=M-.M-dM-8M-^MM-eM-^PM-^LM-gM-^ZM-^DM-fM-^VM-^GM-eM--M-^WM-eM-1M-^OM-dM-?M-^]M-oM-<M-^L2008M-eM-9M-46M-fM-^\M-^H5M-fM-^WM-%^M$
^M$
import gtk^M$
原来在#!之前还有几个字符,用bvi打开:
00000000  EF BB BF 23 21 20 2F 75 73 72 2F 62 69 6E 2F 65 ...#! /usr/bin/e
00000010  6E 76 20 70 79 74 68 6F 6E 0D 0A 23 20 2D 2A 2D nv python..# -*-
它们是0xEF, 0xBB0xBF。原来这个文件是用微软的Notepad创建的,在文件头被自动加上了UTF-8的Byte Order Mark。用cat -e absent.py打开时,在#!前面显示的是M-oM-;M-?,这是指EE BB BF是把'o', ';''?'的高位设为1,可以如下验证:
>>> print ' '.join('%X' % (ord(b) + 128) for b in 'o;?')
EF BB BF
要解决问题,必须把前三个字节删除掉,并把换行转换为Unix格式的\n才能用./absent.py正确执行程序。

2012年5月13日星期日

Python和Bash的混合

上面的图片是我写的一个粗糙的Python脚本的一部分,功能是文本处理,这不是要说的,重点要说的是第30、35、54和55行。

30行是把src字符串所指的zip文件解压缩到tmpdir目录。Python有zip文件处理的模块zipfile,但是只是解压缩有点麻烦,就用Shell下的unzip工具搞定了。

54行是截取othersrc文件的一段,这个用Python的文件处理其实更自然,但是在写脚本前面已经用过现成的Bash命令了,所以就懒得再写Python,直接把Bash命令抄过来。

55行是比较文件的,这个想也没想就用Shell下的diff命令搞定了。

35行计算文件的MD5校验和,这个首先想到的应该是Shell命令,但是Shell下md5sum命令计算出来的结果还得截取字段才能得到,而Python的hashlib直接得到校验和也很简单,就用Python来完成。

上面的例子是在Python里面混合进Bash语句。我又想到一个Bash里面用Python的实例:
第49行用Python开了一个临时的HTTP服务。

这样一会儿Python,一会儿Bash的做法,我以前觉得挺别扭的,会觉得不够纯洁。可是现实情况往往是这样的:很多问题写脚本,用Bash或者 Python都可以完成,但是应该有一个最佳的办法;对脚本里面的单一功能,也是一样的,Bash或者Python的实现总有一个更好的,比如更快实现、 更简洁、已有现成的等等。现在我觉得,更加重要的是完成事情,而不是所谓的“纯洁性”,什么好用就用什么。再说,初看开头的图片,也没觉得因为Python和Bash的混合而变得很乱吧,呵呵。

2012年4月11日星期三

Firefox不能打开mailto链接

本来设置的Firefox用Gmail打开mailto链接,但最近无效了,改成用Thunderbird打开也没反应。在about:config里面搜索mailto,看到几个非默认的设置,试着把network.protocol-handler.external.mailtofalse重置回true后好了。原来这个设置决定该协议是否由外部程序打开。

2012年3月27日星期二

AutoProxy与SOCKS代理DNS

Firefox的network.proxy.socks_remote_dns选项可设置在使用SOCKS5代理的时候,DNS解析是在本地设置的服务器上还是在SOCKS代理服务器上。用AutoProxy时,对Twitter这样DNS被污染的网站,按理说socks_remote_dns必须为真才能打开。可是在about:config里面把这个选项关掉后,Twitter还是能打开。

后来怀疑是Autoproxy的问题。以安全模式启动Firefox后,插件都被禁用,该开关选项生效了。开着,Twitter能打开;关了,Twitter打不开。

用Wireshark抓包,可以看到用AutoProxy(版本0.4b2.2011041023)的时候,不管socks_remote_dns是什么值,都看不到DNS数据包。又在用户档案的extensions/autoproxy@autoproxy.org里面查找这个选项,在chrome/content/proxy.js里面看到:
/**
 * Refresh proxy name & available proxy servers
 *
 * newProxyInfo(type, host, port, socks_remote_dns, failoverTimeout, failoverProxy)
 */
proxy.server = []; proxy.getName = [];
for each (var conf in proxy.validConfigs) {
  proxy.getName.push(conf.name);
  proxy.server.push(pS.newProxyInfo(conf.type, conf.host, conf.port, 1, 0, null));
}
可以看到AutoProxy确实把socks_remote_dns设置成了1,所以在about:config里面设置也无效了。这样做的好处是用户无需知道这个选项,也可以打开被DNS污染的网站,但是最好在文档里说明一下。

2012年3月26日星期一

Bash提示符的Bug

当用户在passwd文件中的家目录最后有"/"时,Bash的提示符中"\w"指代的家目录不能缩写为"~",而是原样显示目录。我汇报了这个bug,但是作者并没承认,更没打算修复。

如果咬文嚼字,这个还真可以不算bug。Bash的手册里这么写的:
\w    the current working directory, with $HOME abbreviated with a tilde (uses the value of the PROMPT_DIRTRIM variable) 
因为这种情况下,"current working directory"是去掉"/"的,而$HOME还是有"/"的:
user@host /export/home/pan$ echo $PWD
/export/home/pan
user@host /export/home/pan$ echo $HOME
/export/home/pan/
这俩变量不相等,所以没有缩写,即使它们在“物理上”是同一个目录。但是这个问题修复了的话,可以让这种特别情况下的提示符依旧缩写,对用户更友好。

2012年3月24日星期六

Dropbox的Case Conflict

修改了主页后,用rsync同步,看到如下的画面:
*deleting   images/rms/17am/7.JPG
*deleting   images/rms/17am/4.jpg
*deleting   images/rms/17am/3.JPG
*deleting   images/rms/17am/2.JPG
*deleting   images/rms/17am/12.JPG
*deleting   images/rms/17am/10.JPG
*deleting   images/rms/17am/1.JPG
<f+++++++++ images/rms/17am/1.jpg (Case Conflict 1)
       66998 100%    3.19MB/s    0:00:00 (xfer#9, to-check=103/1237)
<f+++++++++ images/rms/17am/10.jpg (Case Conflict 1)
       90494 100%    3.92MB/s    0:00:00 (xfer#10, to-check=101/1237)
<f+++++++++ images/rms/17am/12.jpg (Case Conflict 1)
      101524 100%    4.21MB/s    0:00:00 (xfer#11, to-check=99/1237)
<f+++++++++ images/rms/17am/2.jpg (Case Conflict 1)
       76399 100%    2.91MB/s    0:00:00 (xfer#12, to-check=97/1237)
<f+++++++++ images/rms/17am/3.jpg (Case Conflict 1)
       83717 100%    3.07MB/s    0:00:00 (xfer#13, to-check=95/1237)
<f+++++++++ images/rms/17am/4.JPG (Case Conflict 1)
       20923 100%  756.76kB/s    0:00:00 (xfer#14, to-check=93/1237)
<f+++++++++ images/rms/17am/7.jpg (Case Conflict 1)
       77626 100%    2.74MB/s    0:00:00 (xfer#15, to-check=91/1237)
这些图片没有改,怎么要传送,而且出现了Case Conflict 1的字眼。

在rsync的手册里面翻了两下没看出来原因,到这这几个图片所在网页才明白。原来我的缩略图叫1.jpg,而放在同一个目录的大图叫1.JPG,其它几个图片也是如此命名的。最近用了Dropbox,这些文件同步的时候被Dropbox改名了,两个文件扩展名大小写改成一样,其中一个的文件名后加上了(Case Conflict 1)。这应该是Dropbox为了和Windows、OSX等系统下对大小写不敏感的文件系统兼容而为。

那我只好避免这种命名方式了。

2012年3月9日星期五

Bash长行换行问题

发现Bash命令行里面敲的字符超过一行的话,后面的字符不会从下一行开始,而是从提示符开始覆盖。这样覆盖一行后,再输入才会换行。初步锁定问题在PS1变量上,PS1是:
\e[35m\u@\h \w$ \e[0m
如果没有颜色的PS1就没有问题。

后来搜索到解决办法了,要用\[\]把非打印字符括起来,这样Bash就不会糊涂不知道光标在哪里了。其实Bash的手册里面有介绍\[\]在提示符中的作用。

2012年3月1日星期四

SSH登录太慢

从CentOS 6.2用ssh连接Solaris 10很慢,加上-v参数后看到:
debug1: Authentications that can continue: gssapi-keyex,gssapi-with-mic,publickey,password,keyboard-interactive
debug1: Next authentication method: gssapi-keyex
debug1: No valid Key exchange context
debug1: Next authentication method: gssapi-with-mic
debug1: An invalid name was supplied
Cannot determine realm for numeric host address
debug1: An invalid name was supplied
Cannot determine realm for numeric host address
debug1: An invalid name was supplied
debug1: An invalid name was supplied
debug1: Next authentication method: publickey
......
是两个GSSAPI的认证等待失败导致登录很慢。看了Solaris上的sshd_config(4)GSSAPIAuthentication默认打开,CentOS上的ssh_config(5)GSSAPIAuthentication默认也是关闭,但是/etc/ssh/ssh_config中却打开了该选项,这导致ssh尝试了GSSAPI认证。

解决办法很多,要么在服务器端的sshd_config中关掉该选项,要么在客户机的~/.ssh/config或者ssh_config中关掉,或者用ssh -o GSSAPIAuthentication=no来登录。真是:*nix is like a box of chocolate, you never know what you are gonna get.

2012年2月24日星期五

Iptables的nat表

Iptables的nat表,看其名字,乃是Network Address Translation的缩写。我用过nat表的几次都是路由器在不同网段之间转发报文需要做NAT。现在碰到如下的情况:
笔记本电脑是配置成一个网桥的,两个网卡配置在一个网段,只需开启内核的转发和Proxy ARP两边网络即可打通,无需用iptables做NAT。

现在要用Dell作透明代理,要把192.168.2.5这边发出的所有对80端口的访问,转到Dell的3128端口。一般是用下面的一条iptables规则:
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3128
可是这里没有地址转换,是不是不应该用nat表?经过研究,其实nat表不一定必须要做NAT。iptables的手册里面写着:
nat:
This table is consulted when a packet that creates a new connection is encountered. It consists of three built-ins: PREROUTING (for altering packets as soon as they come in), OUTPUT (for altering locally-generated packets before routing), and POSTROUTING (for altering packets as they are about to go out).
例如PREROUTING链,在数据包进入的时候就要穿越,所以上面的iptables规则自然是可以用在网桥上的。
iptables的报文处理顺序
用iptables,一定要搞清楚报文处理顺序了,这样就能真正理解其用法了。推荐Linux Home Networking上的上图。

Squid+Nginx地址结尾的斜杠问题

前面用Squid(3.1)作反向代理,端口在3128;后面是Nginx(0.8.54),端口在8080。Squid的配置如下:
http_port 3128 vhost
cache_peer 10.146.18.213 parent 8080 0 originserver
对于HTML文件,工作正常。但是对目录,如果在访问地址后面有"/",访问正常;但是如果去掉"/",则URL会由
http://cache.fatduck.org:3128/dvorak
变成:
http://cache.fatduck.org:8080/dvorak/
相当于浏览器是从Nginx而不是从Squid得到了应答。而在生产环境中,Nginx的监听端口8080可能是被防火墙阻止的,这样没有"/"的话,这个请求就得不到应答,难于看到问题。
没有"/"时的HTTP报头
我的理解是Squid转给了Nginx,Nginx对目录请求自动在后面加了"/",这样301之后就不返回请求给Squid了。解决办法是让配置Nginx,让它在目录后面显式地增加"/":
if (-d $request_filename) {
        rewrite ^(.*[^/])$ $1/ break;
}
之后的报头就没有301了:
解决问题后的HTTP报头
这样URL不会变,还是http://cache.fatduck.org:3128/dvorak,甚至连最后的"/"都没有,尽管我们在Nginx里面重写URL加了"/"。

2012年2月22日星期三

用ping来traceroute

那天看TCP/IP Illustrated第一卷的第8章Traceroute,突然想到用ping就可以来做traceroute
for i in {1..30}; do  ping -t $i -c 1 google.com; done | grep "Time to live exceeded"
这只是一个演示traceroute原理的例子,不过是我灵机一动的原创哦。之前发在commandlinefu了,又在这里发一下。

其实这个和traceroute还是有区别的。比如ping发送的是ICMP echo request,而traceroute默认发送的是UDP报文。

2012年2月18日星期六

DHCP的租期

前几天DHCP出现问题,顺便研究了下DHCP的租期。
ChinaUnicom客户端的DHCP信息
北京联通的WLAN免费试用不错,SSID就是不加密的那个ChinaUnicom。不过被我发现,这个服务的DHCP租期竟然达10天。就是说如果连接的设备下线后,从连接开始到10天这个IP才会释放到IP池里面供别的设备使用。如果NAT为私有地址的话,那么IP池可以设置很大,没有关系。但只这个服务给用户的是因特网地址,上面的111.195.89.234即是。这样就很浪费IP地址的。

DHCP租期过短的话,局域网内Renew包和客户端租期的过期就会过多;DHCP租期过长的话,地址浪费就会过多。这篇2007年的论文研究了DHCP的租期,自称是对DHCP使用最大规模的研究(we present the largest known study of DHCP utilization)。论文研究发现,指数方式动态调整DHCP的租期,可以比较有效地提高IP地址利用率,并同时减少DHCP的Renew和Expire。具体策略是客户端每次续租的时候把租期翻倍,最高到一个固定值(the lease time a client receives doubles each time it renews a lease, up to a maximum possible lease time).

2012-02-21更新:发现每次连接ChinaUnicom ,得到的IP地址都会变化;说明它的DHCP服务器不保留客户端的租期信息,每次都重新分配IP地址。

Virtualbox的Bridge模式联网

VitualBox里的虚拟机用Bridge模式来联网,宿主机连接到无线路由器上网。无线路由器设置了MAC地址过滤,要把宿主机无线网卡的MAC地址加到路由器里,宿主机才能上网。而虚拟机网卡的MAC地址是不需要加进路由器的许可列表的,虚拟机依然可以上网。在这种模式下,相当于宿主机的无线网卡,替虚拟机的网卡从路由器又租用了一个IP地址给虚拟机用。在路由器上可以看到宿主机和虚拟机的MAC地址及租用IP。不过不需要将虚拟机MAC地址加入许可列表是比较特殊的一点。
路由器上看到的:X61为宿主机,*为虚拟机

2012年2月13日星期一

DHCP的问题

打开Windows下的CentOS虚拟机,ifup得不到DHCP地址了。CentOS是在VirtualBox里面用Bridge模式连网的,这种情况下虚拟机会和宿主机从同一个DHCPD(办公室路由器)获得地址。

DHCP超时失败前,屏幕上出现了ping几个地址失败的消息。在/etc目录下用grep没有找到这个地址,很奇怪。/etc/resolv.conf里面有两行nameserver,我把这两行删除后DHCP就成功了。

后来在/var/lib/dhclient/dhclient-eth1.leases里面找到了ping过的几个地址。这个文件里面有几个租约历史,有的还未过期。原来dhclient.conf(5)里面有解释:
The DHCP client may decide after some period of time (see PROTOCOL TIMING) that it is not going to succeed in contacting a server. At that time, it consults its own database of old leases and tests each one that has not yet timed out by pinging the listed router for that lease to see if that lease could work.
原来如果DHCP失败,dhclient会尝试曾经租约过但是还未过期的地址。不过只有在DHCP失败的情况下才会尝试,这也就是为什么是在出现ping的消息后ifup命令也马上回到了命令提示符。

本来我以为是resolv.conf中过期的内容导致DHCP失败,但后来却未能复现这样的判定,resolv.conf中加入错误的信息后DHCP还是可以成功的。那么,目前能得出的最可信的结论,就是办公室的无线路由器,或者Windows电脑正好出问题了,让CentOS虚拟机没能获得地址。在写这篇文章之前,我电脑就一度连不上网,这或许可以提高前面推测的可信性。

在此记录这个小小的“灵异事件”,以供日后参考。

2012年2月7日星期二

Openswan的证书认证

Openswan支持证书认证的IPSec。用OpenSSL生成CA、用户证书,然后在Windows下导入证书后连接,在服务器日志/var/log/secure中发现错误:
could not open host cert with nick name '/etc/pki/CA/private/cakey.pem' in NSS DB
Google搜索发现要把证书导入到NSS数据库中。这是Openswan 2.6.23之后改变的,要用Mozilla的Network Seurity Services管理证书。看了openswan-doc提供的README.nss,发现这个变动又引入了一堆新东西,而且不是那么容易搞定的,就暂时放弃了,还是用Preshared Key就够了。

2012年2月2日星期四

让APT不自动安装推荐包

Ubuntu里面apt-get会自动安装推荐包。如果要禁用掉自动安装推荐包,可以用apt-get--no-install-recommends参数,永久禁用可以在/etc/apt/apt.conf里面加入
APT::Install-Recommends "false";
Aptitude可以在其设置里面禁用此功能,Synaptic也可在其设置中禁用该功能。

2012年1月20日星期五

VPN故障排除

在Linux上配置了L2TP/IPSec,用 Android手机连上去,不能访问因特网。后来想到服务器内核的ip_forward没有打开,这样手机的请求在ppp0上可以看到,但是内核不会转发 给默认网关网卡。开了后,在服务器的默认网关网卡上可捕捉到手机对Internet的访问,但是收不到回复包。连DNS都不通,先搞定这个。

直接在服务器上查询dig @8.8.8.8 ebay.com是成功的:
11:11:26.994869 IP 10.146.18.213.50984 > 8.8.8.8.domain: 17471+ A? ebay.com. (26)
11:11:26.997250 IP 8.8.8.8.domain > 10.146.18.213.50984: 17471 4/0/0 A 66.135.205.13, A 66.135.205.14, A 66.211.160.87, A 66.211.160.88 (90)
该包的源地址10.146.18.213eth0的inet地址。而手机上的DNS查询,在服务器eth0抓的包如下:
11:16:11.133115 IP 192.168.1.128.58100 > 8.8.8.8.domain: 57041+ A? www.ebay.com. (30) 
eth0上出发的包,地址却是VPN上客户端的地址192.168.1.128,这应该是失败的原因,这样的包到都到不了DNS服务器(内核会丢弃吗?)。

后来才想到要做SNAT的,要让VPN用户访问外网这是必须的一步:
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 10.146.18.213
以后一定要记得了。虽然忘记了这一步,但是通过冷静分析,通过抓包搞清了问题的原因。

2012年1月7日星期六

自挖陷阱

刚刚进去AWS EC2,执行了个sudo命令竟然没有要密码。visudo一看sudoers文件最后写着:
ec2-user ALL = NOPASSWD: ALL
默认用户sudo不用密码太危险了,立刻改成了
ec2-user ALL = (ALL): ALL
这样sudo需要密码。然后就傻了。默认用户ec2-user的密码我不知道,不能sudo,也不能改密码,root密码更不知道了。

这云端系统也不能像常规系统一样设置init=/bin/bash的内核参数启动到单用户,也不能用LiveCD解决。网上搜了一下试图找到默认密码无果,最简单的办法就是停用这个EC2实例再新建一个。小小弄一下,没想到给自己挖了个大陷阱。