I am LAZY bones? AN ancient AND boring SITE

HZLUG的第一次线下活动

昨天,HZLUG有了第一次线下活动,虽然地点远在滨江的网易大楼,但前来参加的仍然有60人左右,有学生也有已经工作的,有各大IT公司的SA还有来自上海的debian developer。
这次的人数已经大大出乎我的意料了,之前我觉得最多也就10多20个人的样子,演讲都可以直接围着电脑讲的那种,呵呵。。。
活动的详情可以看group内的帖子,也可以看TX的博文,都有大量的照片哦。
我这里就贴一下我演讲的slide吧:

我的神舟本3年了

首先声明,本文不是神舟的软文。

07年底,我买了一台神舟的笔记本,但是没过几天,就出现了问题,所以08年初去换了一台新的。
说实话,当时确实觉得神舟不靠谱,尤其是人家都在质疑神舟的时候我出的手,而且买回来没几天又坏了。但是后来事实证明,神舟的性价比还是可以的:当时5k多点的价格,买是配置就算放在今天也不算很差。尤其是显示器的分辨率,现在1680 × 1050的分辨率也还是不多见,这是我最满意的一点。
说说具体情况:3年来我基本上是办公+家用两用,使用时间蛮长的,有数据为证:

lily@LLY ~$ sudo smartctl -A /dev/sda | grep Power_On_Hours
  9 Power_On_Hours          0x0032   087   087   000    Old_age   Always       -       10102
lily@LLY ~$ echo "scale=2;10102/3/365" | bc
9.22

也就是说,3年一共使用的时间是10102个小时,平均每天工作9.22小时。
损耗方面:电池,现在基本只能作为UPS防止意外断电用了,估计能撑10min就很好了;光驱也严重挑盘了,不是质量很好的盘读不了了,刻录功能也废了;另外,键盘在我把所有键帽拆下来洗了一遍再装上以后,2个键变得不怎么灵了(能按,但是弹回无力了,算基本不影响使用吧);电源键下陷严重,不太好按了。初此之外,主要的硬盘、CPU、内存什么的,一点问题都没有。
可以说,神舟的质量算是经得起考验的。不过,好像现在随着其他品牌的价格也都下降了蛮多,神舟的性价比优势也在渐渐失去了,希望神舟能掀起下一轮的降价风波,给消费者带来更多物美价廉的好本本。这样我就更支持你了。

天朝第二代身份证号码的验证机制

今天,在盛大某网站注册的时候,身份证必填,但我又不想填真实身份证号码,于是随便编了串自认为合法的身份证号码,但是却马上被提示号码错误,由于响应速度极快,可以肯定不是联机校验正确性的,那也就是说第二代身份证除了大家都知道的几位表示生日和性别的规则以外,还有另外的自我校验规则。于是翻开页面源码查看,发现这段js没有被压缩,所以规则也很好懂。
就在这里给大家科普下,不知道是不是火星了,呵呵。
以下代码来自这里,版权归盛大。当然,你也可以在维基百科找到更详细的介绍和算法。

iW = new Array(7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2,1);
iSum = 0;
for( i=0;i<17;i++){
    iC = v_card.charAt(i) ;
    iVal = parseInt(iC);
    iSum += iVal * iW[i];
}
iJYM = iSum % 11;
var sJYM = '';
if(iJYM == 0) sJYM = "1";
else if(iJYM == 1) sJYM = "0";
else if(iJYM == 2) sJYM = "x";
else if(iJYM == 3) sJYM = "9";
else if(iJYM == 4) sJYM = "8";
else if(iJYM == 5) sJYM = "7";
else if(iJYM == 6) sJYM = "6";
else if(iJYM == 7) sJYM = "5";
else if(iJYM == 8) sJYM = "4";
else if(iJYM == 9) sJYM = "3";
else if(iJYM == 10) sJYM = "2";
var cCheck = v_card.charAt(17).toLowerCase();
if( cCheck != sJYM ){
    return false; //对不上就是假号码
}

gnome-panel 消失解决办法

2010年的最后一天,打开自己的blog看了一眼,最后一个月居然什么都没留下了,觉得这样实在不太好,于是趁最后时刻,写点什么。
想了一下有什么值得一写的,发现还真不多,因为近来实在是少有时间去折腾,就拿这个来充数吧,如果能帮到有同样现象的朋友,也算不错。
我的gentoo在某次升级以后,gnome-panel就突然消失不见了,我的环境是蛮正常蛮标准的gnome+compiz,我的compiz开了窗口阴影,在屏幕最上方,本来是panel的地方,阴影还是有的。ps看了一下进程,gnome-panel也在。杀掉重启,或者执行 gnome-panel –replace 都无效。看起来就是面板的高度变成了0像素。
查了任何可查的日志,也没有发现什么异常。于是google了一把,发现有人说把 .gconf 删掉就可以恢复了,于是先把整个xdm停掉,把 .gconf 改名,再启动gnome,发现面板果然正常了。当然副作用就是我的大部分设置都丢了,这是我不能接受的。
当然到了这一步,就比较好办了,虽然我的办法很土,但是有效:继续用类似二分法的办法缩小.gconf里面的影响范围,当然,由于gconfd每次都会随gnome启动,所以每做一次范围确认都得停掉gnome,再打开。呵呵,虽然麻烦,但是还是很快早到了元凶,那就是: ~/.gconf/desktop/gnome/interface/%gconf.xml 一个主管界面和字体设置的配置文件,于是,干脆把它删掉,重新在系统-首选项-外观 那里设置一下字体,我的gnome-panel就这么回来了。
可能我这个问题是因为我的ubuntu和gentoo共用一个 /home 引起的,但是奇怪的是ubuntu下面板一切正常,自是gentoo有问题。
好了,问题解决了,最后一句俗却真诚的话:新年快乐!

几个连接数据库用的python模块

工作中,经常会有用python访问各种数据库的需求,比如从oracle读点配置文件或者往mysql写点结果信息之类的。
这里列一下可能用到的各个模块。

sqlite3: 内置模块
用sqlite,有时候确实很方便,我觉得它确实做到了宣称的“零配置”。python自2.5版以来,就内置了对sqlite3的支持,使用也非常简单,按照文档上来:

#打开db文件,获得连接
conn = sqlite3.connect('数据文件名')
#获得游标
c = conn.cursor()
#执行SQL
c.execute('''SQL 片段''')
#如果有对数据的修改操作,那就需要commit一下
conn.commit()
#关闭游标
c.close()
#关闭连接
conn.close()

另外,关于sqlite在C和bash下的用法,可以参考为以前的文章

oracle: cx_Oracle
其实,前面先介绍sqlite3,除了它确实是个小数据库以外,还有一个原因:其他数据库在python下的操作,其实基本上和sqlite3的操作是一样的,也就是说,python其实已经几乎统一了数据库的接口。
打开cx_Oracle的文档页面,你会发现其风格也和python文档很像,因为他们都是用 Sphinx 做的。模块的使用方法就更像了,把上面的代码里,获得连接的那行,换成这样:

conn = cx_Oracle.connect('username/password@TNSname')

就可以了。只要把用户名、密码、TNS组成一个字符串,传进去,就可以得到一个oracle的连接了。

mysql: MySQLdb
和前两个非常类似,连接的时候用以下两个语法之一:

conn = MySQLdb.connect('host', 'username', 'password', 'database')
conn = MySQLdb.connect(host="host", user="username", passwd="password", db="database")

接下来,也把它当成sqlite用就好了。

excel: pyExcelerator
好吧,我承认excel不算数据库,只是写在这里充数而已,哈哈。因为偶尔还是要取下别人发来的excel里的数据的。
其实,用pyExcelerator来读取文件也是很简单的:

sheets=pyExcelerator.parse_xls('xxx.xls')

这样出来以后,sheets就是整个工作薄了,它是工作表组成的list,而一个工作表对应于一个tuple,格式是: ('工作表名', 内容),而内容又是一个dict,key是一个(行数, 列数)的tuple,value才是正在的对应格子的内容。看起来确实比较绕,好在处理excel的应用也不多,将就吧。
另外,其实pyExcelerator还支持写入数据到excel的,如果有把查询结果保存成excel的需求的话,可以试试看,我还是尽量不用这种格式了,哈哈。

其实,文件也可以truncate

现在觉得,时间的流逝速度和年龄确实是成正比的。也就是说,年纪越大,就会觉得空闲时间越来越少了~
因此,本blog都大半个月没更新了,呵呵。也不能老这么沉寂下去,今天来写点东西。

熟悉数据库的朋友们都知道,大多数数据库都有个truncate指令:truncate table xxx可以把xxx表里的所有数据都删掉,但是保留表结构。其实,在有任何数据库之前,UNIX系统里就有了truncate这个命令了,当然后面的*nix里都保留了这个。可以想像,系统里的truncate命令的操作对象肯定是文件,而且此命令不仅能把文件的数据删成0字节,还可以缩减(甚至扩大)文件至指定的大小(通过 -s 选项指定文件大小值),这对于那种日志头部有些不想删除的关键信息,但后面的部分又很多很杂的情况下很有用。对于普通的日志文件,我们要清理的时候通常可以执行 > log 来清除文件的内容(这样,log文件会变成0字节),但是如果清理的同时想保留原始日志的前面4K的信息,不用truncate就会很麻烦了。

truncate的用法还是通过实战来解释吧,如下:

lily@LLY ~$ echo -n 1234567 > txt
lily@LLY ~$ cat txt
1234567lily@LLY ~$ 
lily@LLY ~$ truncate -s 4 txt
lily@LLY ~$ cat txt
1234lily@LLY ~$ 
lily@LLY ~$ ls -l txt
-rw-r--r-- 1 lily lily 4 1024 16:54 txt
lily@LLY ~$ truncate -s 1M txt
lily@LLY ~$ ls -l txt
-rw-r--r-- 1 lily lily 1048576 1024 17:17 txt
lily@LLY ~$ du txt
4	txt
lily@LLY ~$ wc -c txt
1048576 txt

这里还可以看到一个“奇怪”的现象,本来已经缩至4字节的文件,把它扩展成1M以后,ls 和 wc 的结果显示大小确实是1M,但是 du 的结果却发现大小还是4字节。这也是要注意的地方之一,这种文件称为“空洞文件”,也就是说,文件的部分内容并没有实际存在于硬盘上(即没有分配对应的inode),只是“声称”有1M的大小而已。对于不存在于硬盘上的那部分字节,如果去读的话,也是不会报错的,会读到全0的数据。
这也从另一个方面反映出ls等命令默认显示的是文件“声称”的大小,而du (disk use)默认显示的是真正的磁盘占用。这里是我以前的另外一个例子。

又一个简单的轻量级图片查看器──Viewnior

gnome默认的图片查看器是eog(Eye of GNOME,gnome之眼),但是由于这个比较笨重,我不是很喜欢。
之前用的都是 gpicview 这个轻量级的小玩意。因为我对图片查看器的需求只有:能快速打开,能进行旋转操作就可以了,当然,最好旋转完了以后能自动保存,这是因为我的傻瓜相机真的很傻,没有重力感应装置,所以拍出来的相机有很多都需要手工转一下~
但是,不知道从什么时候起,我的gpicview就不能做旋转照片的操作了(记得好像是哪次jpeg库升级了以后才有的,不是很确定),一点旋转按钮,程序就自动退出了,终端运行的话,会丢出一句让人摸不着头脑的提示:Bogus virtual array access。本来以为马上升级一下就可以解决问题的,但是等了很久也没解决,于是去找类似的替代品,就发现了这个:VIEWNIOR
功能上没啥可说的,就是显示和支持旋转,哈哈,主要是依赖少,基本只依赖一个GTK了。依赖少,自然速度也快~
恩,够用就好。

对老域名用PHP写了个301重定向

之前,这里的域名一直都是 li2z.cn ,但是,被CNNIC逼得走投无路以后,我终于把域名换成 luy.li 了。其实准确的说,不是“换”,是加了一个域名,并射成默认了而已,因此用 li2z.cn 还是可以访问的,甚至连301都没有做,之前我的做法只是把两个域名的后台路径指向同一个了而已(题外话:由于两个站的内容完全一致,所以被google惩罚了,li2z.cn 的PR瞬间就变成0了,所以在意PR的站长千万别这么干哦~)。
现在,时间也过去这么久了,大多数的流量已经是新域名下的了,但是老域名的流量也还有不少,于是我就想能不能在不影响访问的同时,统计一下老域名的每次http请求的来路。
方法自然是把所有request用301重定向到 luy.li 的对应地址,然后在日志里记录refer了(不明白301和refer的请自行google基础知识)。
这个用我三脚猫的php都很容易搞定,就几行代码,index.php如下:

<?php
header("HTTP/1.1 301 Moved Permanently");
header("Location: https://luy.li".$_SERVER['REQUEST_URI']);
date_default_timezone_set('Etc/GMT-8');
$msg = date('Y-m-d H:i:s').' '.$_SERVER['HTTP_REFERER'].' '.$_SERVER['REQUEST_URI']."\n";
file_put_contents('log.txt',$msg,FILE_APPEND);
?>

然后,建个 .htaccess 把域名下的所有请求都指向 index.php:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]
</IfModule>

测试一下,对get请求,可以完整地转到新的域名了:

$ curl -v "http://li2z.cn/abc?xxx=yyy"
* About to connect() to li2z.cn port 80 (#0)
* Trying 66.147.240.158… connected
* Connected to li2z.cn (66.147.240.158) port 80 (#0)
> GET /abc?xxx=yyy HTTP/1.1
> User-Agent: curl/7.21.1 (i686-pc-linux-gnu) libcurl/7.21.1 GnuTLS/2.10.2 zlib/1.2.5
> Host: li2z.cn
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently < Date: Sat, 02 Oct 2010 12:05:53 GMT < Server: Apache < X-Powered-By: PHP/5.2.14 < Location: https://luy.li/abc?xxx=yyy
< Cache-Control: max-age=1 < Expires: Sat, 02 Oct 2010 12:05:54 GMT < Vary: Accept-Encoding < Content-Length: 0 < Content-Type: text/html < * Connection #0 to host li2z.cn left intact * Closing connection #0 然后在后台目录里会生成一个 log.txt ,里面会有所有访问的时间、refer和uri,这样日后就可以方便地查出老域名的所有来路和受访页面了,最重要的是,由于有301,还不会对正常访问造成影响哦~

sudo的配置

大家都知道,root权限是linux系统中的最高权限,有了root权限,就可以对系统做任何操作。
但是,很多情况下,这样一个笼统的root权限并不能很好地满足需求,比如,有时候想让系统的某几个用户有装包的权限(就是执行apt-get或者yum什么的),但是不能随便更改其他系统配置;又比如,想让某个用户有杀死指定另外一个用户的进程的权限(比如www用户什么的),但是也不能随便杀其他用户的进程。
这样一来,便有了细化这个“最高权限”的需求了。于是,权限管理的一大利器——sudo——便应运而生了。
可能是由于sudo的需求本来就比较复杂,看我上面说的例子,用口语表达都比较拗口;也可能是有sudo需要的安全级别比一般的程序要高一些。导致sudo的配置,看上去有点凌乱和摸不着北,所以这里稍微解释一下。
首先,sudo的配置文件是 /etc/sudoers,虽然你也可以手工打开、编辑、保存。但还是建议使用visudo命令来编辑。这是因为:

  • 它能够防止多个用户同时修改它
  • 它能进行有限的语法检查
  • 它能避免因权限位出错而不被sudo认可

然后,打开配置文件以后可以看到有这几部分:

  • Host_Alias、User_Alias和Cmnd_Alias,分别是主机、用户和命令的别名。
  • Defaults一些默认的特性,比如默认要不要重设环境变量,重设哪几个环境变量,默认以谁的身份执行等等。
  • 后面就是重头戏了,类似这样的一行:
    root ALL=(ALL) ALL
    其实这行是 user machine=(users) commands 这样的格式,也就是,允许在machine登录的 user 用户以users的身份来执行commands命令。这里的machine、users呵呵commands就可以用Host_Alias、User_Alias和Cmnd_Alias来代替了。
  • %group ALL=(ALL) ALL
    和上面的一样,只不过这个按组来限制权限。

还是用例子来说话吧,接着上面的两个例子。

例1. 让系统的某几个用户有装包的权限(就是执行apt-get或者yum什么的),但是不能随便更改其他系统配置。
做法就是将这几个用户加入某个特殊的组里(当然,如果你愿意也可以一个个用户分别设置,哈),比如建了yumer组,把用户都加进去了,然后sudo的配置加上:
%yumer ALL=/usr/bin/yum

例2. 让某个用户(dev)有杀死指定另外一个用户的进程的权限(比如www用户什么的),但是也不能随便杀其他用户的进程。
配置如下:
## Processes
Cmnd_Alias PROCESSES = /bin/kill, /usr/bin/kill, /usr/bin/killall, /usr/bin/pkill
Defaults:dev runas_default=www
dev ALL=(www) PROCESSES

先把几个命令alias成一个 PROCESSES,然后指定dev用户,有以www用户的名义执行PROCESSES里的程序的权限。
本来,dev用户必须使用 sudo -u www kill 1111 来杀死www用户的1111号进程的,但是加个-u显然麻烦,所以有了一行: Defaults:dev runas_default=www
这行的意思,是让sudo知道,只要是dev用户执行的,默认就是www的身份,而不是一般的root身份。

这里只是通过两个简单的例子介绍了sudo最常用的功能,其实sudo还有很多其他的有趣功能。比如,sudo还可以实时将非法操作检测出来,以多种方式记录到日志里,不只是本地日志,还可以通过http等方式传到别的机器等。更多功能,当然得参见man页了,哈哈。

python编程细节──遍历dict的两种方法比较

python以其优美的语法和方便的内置数据结构,赢得了不少程序员的亲睐。
其中有个很有用的数据结构,就是字典(dict),使用非常简单。说到遍历一个dict结构,我想大多数人都会想到 for key in dictobj 的方法,确实这个方法在大多数情况下都是适用的。但是并不是完全安全,请看下面这个例子:

#这里初始化一个dict
>>> d = {'a':1, 'b':0, 'c':1, 'd':0}
#本意是遍历dict,发现元素的值是0的话,就删掉
>>> for k in d:
...   if d[k] == 0:
...     del(d[k])
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
#结果抛出异常了,两个0的元素,也只删掉一个。
>>> d
{'a': 1, 'c': 1, 'd': 0}
 
>>> d = {'a':1, 'b':0, 'c':1, 'd':0}
#d.keys() 是一个下标的数组
>>> d.keys()
['a', 'c', 'b', 'd']
#这样遍历,就没问题了,因为其实其实这里遍历的是d.keys()这个list常量。
>>> for k in d.keys():
...   if d[k] == 0:
...     del(d[k])
... 
>>> d
{'a': 1, 'c': 1}
#结果也是对的
>>>

其实,这个例子是我简化过的,我是在一个多线程的程序里发现这个问题的,所以,我的建议是:遍历dict的时候,养成使用 for k in d.keys() 的习惯。
不过,如果是多线程的话,这样就绝对安全吗?也不见得:当两个线程都取完d.keys()以后,如果两个线程都去删同一个key的话,先删的会成功,后删的那个肯定会报 KeyError ,这个看来只能通过其他方式来保证了。