Vim查找替换的高端用法

处理文本时,有这么一个需求:一个包含大量聊天记录的文本文件,有两列。一列是时间,一列是内容。大致如:

1319265479, 你好啊。
...

现要将第一列的Unix时间戳转换成”2011-10-22 14:37:59″这种形式。

我第一反应就是用Vim。但发现,每一行的时间戳都不一样,不能直接简单的查找替换。其次,使用sed来处理每行,使用某个程序来将时间戳转换成直观的时间。但据我查找,Linux下的date可以将时间戳转成直观的时间,但Mac OS X下的date却不能做到。再次,我只好回到Vim里,打算从文档看看查找替换时能不能支持函数来转换匹配的字符串,结果还真有。

因为这文件里的时间戳已经很有规律了,都是以131开头的,所以我就用简单的匹配来做了。

:%s/\(131\d+\),/\=strftime("%Y-%m-%d %H:%I:%S", submatch(1)) . ", "/g

下面简要解释下这个正则表达式。:表示进入Vim的命令模式,%s表示进行全局替换,\(131\d+\),表示匹配以逗号结尾的时间戳,\=strftime()表示将先执行函数strftime(),将返回的结果进行替换,submatch(1)表示将匹配的时间戳当作strftime()的参数,.”,”表示将strftime()的结果与”,”组合起来(.号表示连接两个字符串)。这个正则表达式应用在文首的例文,进行展开后的结果如下:

:%s/1319265479,/\=stftime("%Y-%m-%d %H:%I:%S", 1319265479).","/g
等同于:
:%s/1319265479,/2011-10-22 14:37:59,/g

关于这种用法,可以Vim中使用

:h sub-replace-\=

来查看相关文档。

Vim编辑远程文件

Vi/Vim作为我最值得炫耀的编辑器(没有之一),与它的对手Emacs相比,经常被提到的一个优点就是:这个世界上几乎所有的*nix服务器,都会自带Vi/Vim。这个优点带来的方便之处在于,只要会用Vi/Vim,你就可以在任何一台*nix服务器上使用神乎奇技的手法来编辑文件,而不用额外安装软件(Emacs去哭吧)。这个优点几乎在我所有的服务器操作中都用到过,但在今天,似乎略有不便。

我有一台VPS,这基本上是众所周知的事情了。但中美网速的差别与两国国民生活水平一样大,导致我娴熟的指头在服务器上用Vim直接修改文件时,遇到了十分强烈停顿感。这深深地挫伤了我文思如泉涌的灵感。便想把远程文件下载到本地,修改后再上传,就像Emacs一直以来无奈的做法一样。当然这事必须不能手工去做,不然不仅不能给Vimer长脸,还会被认为是其它阵营派来给Vim抹黑的。

本文的主角是netrw.vim,它可以让Vim直接编辑网络上的文件(当然是netrw已经把该文件下载到临时文件里了),并且还支持保存到网络上(如果有写权限的话)。不知道从什么开始,它便成为Vim默认自带的插件了。在大多数现代GNU/Linux发行版里,安装Vim时都会顺便把它给装上了。

netrw使用起来也很简单。在终端里可以直接:

$ vim ftp://user@some_url/path/filename

这样便可以编辑一个远程文件,其它操作,便与编辑本地文件无异,该’:w’或’:q’时都悉听尊便。

但并不是所有的远程文件都能这么方便的访问,像我今天修改的那个文件,只有通过ssh才能访问到,并且ssh还是有key认证的。但是呢,对于我而言丝毫不影响,因为我还可以这样:

$ vim scp://t@tigerlee.me/path/target_file

也许你这样做,并不会成功,当然不成功的原因是多样的,最主要原因可能是你不能顺利地(不用输入密码,没有任何交互的提示)使用

$ scp://t@tigerlee.me/path/target_file .

来将target_file拷贝到本地。如果能做到顺利,那么你也会成功的。要实现顺利地使用scp,无外乎就是去掉一些不必要的认证过程,使用不带密码的key是最方便的了。详细使用参见netrw的文档:‘netrw-ssh-hack’,有Vim的哥们呢,就直接使用’:h netrw-ssh-hack’来看好了。

当远程文件拥有一个冗长的路径时,直接输入全部路径可能比较麻烦,因为它又不能使用Tab补全。这时可以先输入:

$ vim scp://t@tigerlee.me/

然后再在稍后出现的路径列表里慢慢选了。

netrw共支持三种使用模式:远程编辑,远程浏览(包括浏览目录内容),远程写入。不同的模式,支持的协议也不同。这是理所当然了,不然对于HTTP地址的文件,怎么写入啊?以下是不同模式支持的协议:

远程编辑
:e dav://machine[:port]/path                    uses cadaver
:e fetch://[user@]machine/path                  uses fetch
:e ftp://[user@]machine[[:#]port]/path          uses ftp   autodetects <.netrc>
:e http://[user@]machine/path                   uses http  uses wget
:e rcp://[user@]machine/path                    uses rcp
:e rsync://[user@]machine[:port]/path           uses rsync
:e scp://[user@]machine[[:#]port]/path          uses scp
:e sftp://[user@]machine/path                   uses sftp

远程浏览
:Nread ?                                        give help
:Nread "machine:path"                           uses rcp
:Nread "machine path"                           uses ftp   with <.netrc>
:Nread "machine id password path"               uses ftp
:Nread "dav://machine[:port]/path"              uses cadaver
:Nread "fetch://[user@]machine/path"            uses fetch
:Nread "ftp://[user@]machine[[:#]port]/path"    uses ftp   autodetects <.netrc>
:Nread "http://[user@]machine/path"             uses http  uses wget
:Nread "rcp://[user@]machine/path"              uses rcp
:Nread "rsync://[user@]machine[:port]/path"     uses rsync
:Nread "scp://[user@]machine[[:#]port]/path"    uses scp
:Nread "sftp://[user@]machine/path"             uses sftp

远程写入
:Nwrite ?                                       give help
:Nwrite "machine:path"                          uses rcp
:Nwrite "machine path"                          uses ftp   with <.netrc>
:Nwrite "machine id password path"              uses ftp
:Nwrite "dav://machine[:port]/path"             uses cadaver
:Nwrite "ftp://[user@]machine[[:#]port]/path"   uses ftp   autodetects <.netrc>
:Nwrite "rcp://[user@]machine/path"             uses rcp
:Nwrite "rsync://[user@]machine[:port]/path"    uses rsync
:Nwrite "scp://[user@]machine[[:#]port]/path"   uses scp
:Nwrite "sftp://[user@]machine/path"            uses sftp
http: not supported!

小总结一下:

  • 网速慢,文件小的情况下,可以使用此法编辑远程文件。
  • 网速快,文件大的情况下,那就远程编辑文件吧。
  • 网速慢,文件大或网速快,文件小的情况下,你应该明白怎样做吧?

VPS迁移:从OpenVZ到Xen

前阵子,在PhotonVPS上租的VPS到期了,当时图便宜,选用了基于OpenVZ的,结果这一年来,用得不甚舒坦。ssh连接上去,断断续续的的;OpenVPN装上去,为了nat之类的发了好久的ticket;博客打开的速度,也是老慢老慢的。趁着到期了,就琢磨着换成基于Xen的了。

在原来的机器上,大大小小的网站有10来个,每次需要增加新网站时,都是本着知根知底的态度,也没装个WebPanel,全是手工操作来增加Virtual Host,创建数据库,设置目录权限等。弄了几次终于累了。

Apache2的速度果然不是吹的,死慢,还巨吃内存。Word Press没搭几个,1G的峰值内存就全吃光了。就算照着网上的LAMP教程优化了一遍,也是没见起效。

Open VPN这玩意,既要有nat设备,还有设置iptables,更加有一堆证书。在我已知的任一平台上使用,都得额外安装软件,烦不胜烦。想想还是PPTP VPN好使,每个平台都默认支持,不用装其它软件,也没有证书之类的,很好操作。

基于以上几点,在迁移到Xen的过程中,做了如下改变:

Web服务器:Apache -> Nginx
网站部署:手动->使用自写脚本
VPN: OpenVPN -> PPTP

没变化 的也就php和mysql了。

在Apache下使用php基本上都是安装mod来达到支持php的,但使用nginx的时候,却没有这样的mod可以用,所以为了支持php,还需要安装一个php-fpm,在Ubuntu 10.04中,这个软件包名字叫php5-fpm。php5-fpm是一个服务,默认会监听在9000端口。在nginx的默认Virtual Host配置中,也有一段被注释掉的代码,包含有”location ~ \.php$”字样的。取消掉后重启就可以支持php了。

至于 PPTP,那就更简单了,aptitude安装好后,就直接可用了。只是默认是没有用户的,需要在/etc/ppp/chap-secrets里增加用户,并且密码是明文保存的。[2011-07-21更新] 漏了一步骤

iptables -t nat -A POSTROUTING -s 192.168.255.0/24 -o eth0 -j MASQUERADE

因为我在/etc/pptpd.conf里设置了remoteip值为192.168.255.100-238,所以iptables中192.168.255.0/24表示我这个remoteip的网段了。[2011-07-21更新完毕]

至于自写脚本,贴上来仅供参考:使用之前请先过目,知道每条命令在做什么。

#!/bin/sh
USER=$1
SITENAME=$2

sample_conf() {
    echo "server {
        root /var/www/$SITENAME;
        index index.html index.htm index.php;
        access_log /access.log;
        error_log /error.log;
        server_name $SITENAME;
        location / {
            try_files \$uri \$uri/ /index.php?q=\$uri&\$args;
        }
        location ~ \.php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            include fastcgi_params;
        }
    }"
}

sample_conf $SITENAME > /etc/nginx/sites-available/$SITENAME
ln -s /etc/nginx/sites-available/$SITENAME /etc/nginx/sites-enabled/$SITENAME
mkdir /var/www/$SITENAME
chmod o+t /var/www/$SITENAME

## create database
DB_NAME=`echo $SITENAME | sed -e 's/\./_/g'`
mysql -uroot -p --password=MYSQL_ROOT_PASSWD -e "create database $DB_NAME"
mysql -uroot -p --password=MYSQL_ROOT_PASSWD -e "grant all privileges on $DB_NAME.* to '$USER'@'localhost' identified by ''"

exit 0

使用该脚本前,需要先创建一个用户。这个脚本会使用自动为指定的域名创建一个Virtual Host配置,并且创建一个数据库,让该用户对其有所有操作权限。脚本使用格式:./script USER SITE_NAME,如:./new_site.sh tiger blog.tigerlee.me

人太懒了,都静不下心来把个教程写完整,我知道这是缺点,得改。

让nagios报警消息重复通知

其实这也没什么好讲的,只要在service_tmpl.cfg中把notification_interval设置为不小于0的数字就好了,但实际上我在设定Nagios Server的时候,已经这么做了,但一旦报警通知发出,如果下次检查的时候仍为此状态,Nagios却不会继续通知。这让我郁闷了好久,相关的文档都翻阅了,还是不知道所以然。

后来不经意地在services.cfg文件中发现了这样的定义:

define service {
hostgroup_name                ssh-servers
service_description           SSH
check_command                 check_ssh
use                           generic-service
notification_interval         0 ; set > 0 if you want to be renotified
}

在具体的service中,居然把notification_interval设置为0了!怪不得不重复通知呢,我光改了模板文件,却没注意到具体的service都是由默认的service修改而来的,而默认的service,都将notification_interval设置为0了。知道原因后,马上将notification_interval删除,让其使用service_tmpl.cfg中的配置,然后重启nagios,就可以收到重复的报警消息了。

自定义ubuntu clock applet显示格式

默认的clock applet只能显示有限的几种格式,可这种非中文locale的格式,还真是不习惯。

折腾了一会,终于找到了解决办法。

打开gnome的配置管理器,或直接输入命令

$ gconf-editor

找到节点

/apps/panel/applets/clock_screen0/prefs/

编辑右边format键值为"custom"(不含引号),同时将custom_format键值设置为

%Y-%m-%d %H:%M:%S

便可看到适合国情的时间格式了,嗯,我的格式加上了星期(%a)。

其实custom_format的格式可参见"man strftime"。:)

crontab中使用scp提示’permission denied’的解决方法

曾经有一段时间需要从集群中的一台服务器收集数据,并放到另一台服务器上。手工做了几次,实在是烦了。于是写了个脚本加上crontab来做这件事情,脚本内容大致如下:

#!/bin/sh
# file: script.sh
#1. collect some data…
#2. generate data file
scp data_file someuser@somehost:~/destination/today.data
echo "[Today] collect data completed." >> /tmp/collect.log

而crontab的内容也很简单:

* 16 * * * cd path_of_script; sh script.sh

当然,在放到crontab之前,我用手动运行过个脚本,是可以正确的收集数据,并且将收集到的数据复制到另一台服务器的指定路径。为了记录收集情况,每当执行完后,都会在/tmp的日志中写入一条log。

我在crontab中设定了让这个脚本每天下午4点钟开始执行,可是第二天下午5点钟了,同事说当天的数据没到收集到。我觉得很奇怪,登录到这台机器上发现日志里已经显示当天的"collect data completed"了,但不知道为什么,数据没有复制过去。想了想,把脚本和crontab都稍做了修改:

script.sh:

#!/bin/sh
# file: script.sh

#1. collect some data…
#2. generate data file
scp data_file someuser@somehost:~/destination/today.data
echo "[Today] collect data completed."

crontab:

* 16 * * * cd path_of_script; sh script.sh &> /tmp/collect.log

不仅记录自己输出的日志,而且把scp的stdout也记录下来。第三天下午4点过了,数据依然还是没有复制到目的机器上,查看collect.log发现当天的日志是:

Permission denied, please try again.
Permission denied, please try again.
Permission denied (publickey,password).
lost connection
[2010-08-30] collect data completed.

这时候我才想起来,这台机器压根就没有可用的key来登录到目的机器!当初我之所以能成功地执行脚本是因为我是开启了ssh_config中的ForwardAgent,在执行脚本中的scp时,使用了我的key,而当我退出这台机器后,它便无法再登录到目的机器,因而亦不能执行scp了。

找到原因后,问题就好解决了。在收集数据的机器上使用ssh-keygen产生一对rsa公私钥对。把公钥追加到目的机器的"/home/someuser/.ssh/authorized_keys"中,然后使用scp时加上-i选项来指定私钥的位置即可,修改后的脚本如下:

#!/bin/sh
# file: script.sh
#1. collect some data…
#2. generate data file

scp -i ~/.ssh/id_rsa data_file someuser@somehost:~/destination/today.data
echo "[Today] collect data completed."

记住,退出机器前先使用ssh -i ~/.ssh/id_rsa someuser@somehost来登录到目的机器,不然下次crontab运行时,scp会傻傻地等着你输入yes来确定把目的机器加到known_hosts里。

在命令行中设置终端编码

我的gnome-terminal和tilda默认都是utf8编码,当使用某些只支持gbk/gb2312编码的程序时,总是要手动把字符编码改成gbk,有时候还不一定记得改。现在碰到的问题是使用ssh连BBS,而国内的BBS大多数是gbk/gb2312编码的,每次连接时还得先将终端编码给改了,完了退出来后又得把编码改回来。于是搜索一番,发现个小工具’luit’。

luit – Locale and ISO 2022 support for Unicode terminals

‘luit’不是单独的程序,它是包含在’x11-utils’这个软件包里的小工具。用法也很简单,像我要连接一个中文BBS站,就可以这样:

$ luit -encoding gbk ssh bbs.xxx.xxx

不过每次都样输这么长一串也很麻烦,那就在.bashrc下加个alias好了:

alias gssh=”luit -encoding gbk ssh”

这样的好处是,进入BBS前不用改终端编码,退出BBS后,luit也跟着退出了,即还是不用改终端编码。如果直接运行:

$ luit -encoding gbk

你会发现提示符没有变化,因为luit已经在运行了,这时终端编码已经变成了gbk。退出方法:’exit’或者’ctrl^d’。

拥有VPS之-清除木马

离上一节整整一个月了,我这才把下节给补起来。上节写完时感觉还有好多没说,可是等过了这么一段时间后,我连当时保存的现场证据都快找不到了。不管怎样,还是把第一次写的系列篇迅速地划个句号吧。

上次讲到发现了若干个木马根据地,但如果是用简单的’rm’命令,是根本不能将其删除的(什么?你是root?即便你是foot,你也无法将其踢掉!)。是的,root也不能。为啥呢?root在u*ix系统中不都是万能的么?要啥有啥,要没啥,啥就没。是的,可root也能给自己加个限制,用来防止自己的误操作。这要用到ext2/3文件系统的一个特性”file attribute”。

以前初中时,学到一招很管用的办法,不让别人把自己的文件无意中删除。当然,那是在DOS下。用”attrib +h filename”就能让文件隐藏了”,同理”attrib -h filename”就是将隐藏的重新显示出来。这么个特性,在linux中怎么会缺席呢?下面我简单介绍下今天要用到的两个工具:lsattr和chattr。

lsattr和chattr它们俩是linux系统下用于的控制ext2/3文件系统家族特殊属性的组件,并且已经成为了e2fsprogs包中的一部分了。它们可以用来显示或修改文件的一系列属性:文件访问时间、安全删除、不可删除、只可追加、同步更新等。更详细地移步维基百科词条chattr继续阅读“拥有VPS之-清除木马”

拥有VPS之-发现入侵

上周末才买入一台VPS,由于急着把blog迁移过来,所以装完系统后,就只顾着blog的事了,VPS安全方面的事情,本来打算在本周末来收拾一番的。没想到啊,没想到,万恶的资本主义国家,黑客也这么多,哥刚把blog搭好,你又来给哥捣乱了。

上午,正在彻erlang代码,zhoux在gtalk上和我说,他的账号不能通过ssh登录。于是乎,做为VPS的首席维护官,我立马试着去登录。提示输入密码后,很快就看到了$提示符,然后手指不由自主地做了个终端爱好者的习惯性动作-’ls’,可是’ls’并没有像往常一样显示我当前目录的内容,而是提示”/bin/ls: no such directory”,哥当时就震惊了。第一反应还以为是环境变量被人改了,’env’看了下,没有发现异常。然后想用’ps aux’查看到进程列表,结果哥再次震惊了,屏幕上提示”/bin/ps: no such directory”,再试’top’,还是”/usr/bin/top: no such directory”,我靠,你都把路径给提示出来了,却还告诉我没有这个路径,存心逗哥玩么。以前刚开始用linux时,还带着windows cmd的习惯,查看目录内容会用’dir’,结果发现也有效,然后哥又试着用了下’dir’,这下还好,能够看到目录内容了。 继续阅读“拥有VPS之-发现入侵”

ubuntu 8.04.2 Server Raid 1设置

公司服务器要组raid 1,服务器主板是支持raid 1,但是个fakeRaid,还需要在操作系统里装驱动,只支持Mindows,RedHat,Suse,对于其它系统用户就得自力更生了。
还好Linux内核原生支持raid系列啊,至少我想要的raid 1是支持滴,fakeRaid这么丑陋,那就用softRaid好了。
安装过程其实是很简单的,安装时参考了这位哥们的步骤: RAID1 in My Ubuntu Installation
为了防止网页过期,转一部分过来。 继续阅读“ubuntu 8.04.2 Server Raid 1设置”