2010-01-31

眼不见为净——过滤人人网分享的chrome扩展

眼不见为净——过滤人人网分享的chrome扩展

redraiment, 2010-01-31

人人网的分享标题





  进人人网(校内网)就免不了分享一些在线视频、音乐……有些同学进校内也纯粹是为了看分享,呵呵。分享原本是不错的东西,可以让新鲜事迅速传播。但最近有部分人,在分享链接时,在标题上赫然写着“看见不分享就倒霉一年”、“不分享的考试挂科”等诅咒信息,强奸我们的眼睛,唯恐天下不乱!
  以前,在人人网中分享好友的“分享”时可以自己修改标题,所以我看到此类信息时都会手工去除恶意信息。但最近人人进行了升级,分享好友的“分享”时只能添加评论,不能修改标题。询问周围的同学,也有不少人对此类标题深恶痛绝!

我要过滤

  本来这无可厚非,人家写这样的标题或许他/她们真觉得这个视频、音乐很不错,迫切地希望大家都能看/听到。但每个人的喜恶不同,这样的做法有点类似看电影时旁边一个同学向你絮絮叨叨后面的情景——好心办坏事。我还是希望每个人能比较客观地发表自己的意见,因此我不希望这些标题出现在我的首页上。
  一开始,我还是打算像往常一样写个客户端程序,每次在进人人网之前先执行一下,把带此类标题的新鲜事标记为已读。但以我这样的马大哈还真不能保证每次记得执行;另外,我从人人网上下载的Java Client开发包,但找来找去也没找到个像样的文档。所以就放弃客户端的想法。
  转念一想“如果浏览器本身提供这样的功能就好了”,再一想就想到开发浏览器的扩展/插件。我习惯用Chrome,最新版的也支持扩展开发了,而且在线文档也比较完整:http://code.google.com/chrome/extensions/index.html

Chrome扩展

  我已经将扩展发布,如果你也有被相类似的问题困扰,并且也使用最新的 Chrome 浏览器,欢迎到项目主页https://chrome.google.com/extensions/detail/kajehjgpeliapeaileldhcabdalkeflo下载安装。
  安装完后,当打开人人网时扩展就会启动,并在地址栏里显示图标:
  在页面加载完后,扩展就会自动检查你的“新鲜事列表”,把所有带“不分享”的文字统统替换掉,下图是我和同学做的一个测试:
  想关闭过滤功能,只要将它禁用即可。这个程序很小很简单,我会在下一篇文章中介绍它的开发步骤。


2010-01-25

Linux下用nc实现DuplexPipe

Linux下用nc实现DuplexPipe

redraiment, 2010-01-25





  nc 是一把网络的瑞士军刀,我以前在介绍 DuplexPipe 时也提到过,如果你没接触过它,可以先参看一下《DuplexPipe二三事(二)》。再来简单地介绍一下 DuplexPipe,顾名思义,它是一个“双向管道”。在 shell 中,我们通过“|”使用匿名管道,让前一条命令的输出作为后一条命令的输入;双向管道即在此基础上在加上“后一条命令的输入作为前一条命令的输入”。这是最初开发它的原因,但后来发现它更像是一个网络接口转换器,“DuplexPipe”这个名字反而不能体现它的功能。更多内容请参看DuplexPipe系列文章

留言

  今天网友黄海给我留言,他通过用 nc 的 -e 选项来执行 nc 本身来实现 DuplexPipe。留言原文如下:
哥们,你写的那个DuplexPipe, 我很欣赏。不过近日于网上逛发现此工具的功能竟然完全可以用netcat做到,有两种方法,我的博客上载了一种。简单描述如下:
在windows下:
echo nc [ip] [port] > relay.bat
nc -l -p [port2] -e relay.bat
其余的类推
第二种方法是用命名管道:(linux下)
mknod backpipe p
nc -l -p [port] 0<backpipe | nc [ip] [port12] | tee backpipe
  其中选项 -e 的作用是:
for NT:    -e prog        inbound program to exec [dangerous!!]
for Linux: -e filename    program to exec after connect [dangerous!!]

Windows下不行

  在我开发 DuplexPipe 时确实考虑过功能会不会和 nc 重叠,当时只想着通过 shell 管道来连接,忘了 nc 自带了一个双向管道!我首先在 Vista 下做了测试,nc(win32) 是从 http://www.securityfocus.com/tools/139 下载。开启三个命令提示符,分别执行:
1) nc -l -p 1234
2) nc localhost 1234 -e "nc -l -p 1235"
3) nc localhost 1235
  其中第二条和留言中使用批处理等价。理论上在提示符(3)中输入一行数据,提示符(1)三中马上显示。但我每次在提示符(3)中输入一堆数据后,提示符(1)要输入两个回车才会把数据显示出来。我又另外开启四个命令提示符,模拟 DuplexPipe:
1) nc -l -p 1234
2) nc -l -p 1235 -e "nc -l -p 1236"
3) nc localhost 1234 -e "nc localhost 1235"
4) nc localhost 1236
  此时提示符(1)中的数据能发送到(4)中,而提示符(4)中的数据却倒不了(1)。调整(2)、(3)中端口的顺序会出现不同结果,但都达不到理想效果。后来又下了其他几个不同版本的 nc,并在 WinXP 下也进行了测试,但都不成功。

Linux下成功

  Linux下有些不同,nc 不能脱离 shell 执行。可以像留言中一样先创建一个脚本文件,也可以使用另一个选项:
-c shell commands      as `-e'; use /bin/sh to exec [dangerous!!]
  我的系统是 Debian Lenny+nc v1.10-38,经测试能实现 DuplexPipe 的全部功能!最新版的 DuplexPipe 能实现九种连接模式,对应的指令分别是:
  1. nc -l -p port1 -c 'nc -l -p port2'
  2. nc -l -p port1 -c 'nc host2 port2'
  3. nc -l -p port1 -c 'nc -u -l -p port2'
  4. nc -l -p port1 -c 'nc -u host2 port2'
  5. nc host1 port1 -c 'nc host2 port2'
  6. nc host1 port1 -c 'nc -u -l -p port2'
  7. nc host1 port1 -c 'nc -u host2 port2'
  8. nc -u -l -p port1 -c 'nc -u -l -p port2'
  9. nc -u -l -p port1 -c 'nc -u host2 port2'
  在留言中还提到用命名管道来实现。
mknod pipe p
nc -l -p 1234 0< pipe | nc -l -p 1235 > pipe
  shell 的匿名管道让前一条命令的标准输出作为后一条命令的标准输入,手工创建的命名管道则是让后一条命令的标准输出作为前一条命令的标准输入。你会发现这已经解决了文章开头的问题,而且不需要通过网络。

下一步任务

  我想,DuplexPipe 已经没必要继续维护了。虽然 Windows 下测试不成功,但所幸它的源码开放,也许接下来我们应该考虑转向去完善 netcat for NT。

2010-01-06

用awk去除C语言注释

用awk去除C语言注释

redraiment, 2010-01-06





  今天闲逛Linux宝库,看到论坛里有人在讨论如何用 shell 脚本来处理 C 语言注释,发帖时间是 08-10-23(以前怎么都没注意到,失败...),但问题好像并没被解决。正好这两天玩 sed & awk,来小试一下身手。

C语句注释

  本文讨论的是 C99 标准,它支持单行注释(“// ...”)和块注释(“/*...*/”),并且当单行注释以“\”结尾时也可以跨多行。测试代码如下:
#include <stdlib.h>
#include <stdio.h>

int main (int argc, char *argv[])
{
// not show\
not show\
not show
// not show
/* not show */
    int is; // not show
    int/* not show */ ms; /* not show */
    double ds; // not show\
    not show\
    not show
    double dm; /* ...
    not show
    not show */ float fs; /**
                           * now show
                           */
    float/**/ fm;
    char cs[] = "aaa // /***/";
    char cm1[] = /* not show */"hello*/";
    char cm2[] = "/*redraiment"/* not show */;
    /* printf("/////"); */

    return EXIT_SUCCESS;
}
  其中绿色部分就是注释,经过处理后需要将它们全部移除或用替换成空字符。论坛原帖中没有处理以“\”结尾的单行注释,也没处理注释关键字出现在字符串中的情况。

工具的选择

  sed 是一个流编辑器,它能对文件进行“插入”、“删除”、“替换”、“追加”等编辑操作;而 awk 是一门模式匹配的程序设计语言,它除了能编辑文本还可以统计信息,你可以把它看成基于文本文件的数据库系统。原帖中作者使用 sed 来解决,因为问题涉及的操作仅仅是删除 C 代码中的注释。但由于以下原因导致 sed 心有余而力不足:

一、不支持最小匹配

  正则表达式默认采用贪心匹配策略,在正则的标准中通过在量词后面加“?”来使用最小匹配策略,详细规则介绍请参见这里。问题中多行注释必须使用最小匹配原则,如果关键词只有一个字符,就可以通过排除字符集来模拟,比如我们经常用“"[^"]*"”来匹配一个字符串。可惜 C 语言的注释关键词都是多字符。

二、排序字符(Collating Symbols)只是一个美丽的梦想

  排序字符用于字符列表(character list)中。按照文档的描述,排序字符可将多个字符当一个字符来匹配。比如模式“[[.ch.]i]”可以匹配行“char”和“int”,但不能匹配“coho”。如果它能被支持,就可以把“/*”、“*/”、“//”都看做一个字符,通过(一)中的排除字符集来实现最小匹配。可惜到目前为止,我接触的工具中没有一款支持这个特性,更不用说对正则支持平平的 sed 了。

三、字符串来捣乱

  在《sed单行脚本学习笔记》中我给出了一段 sed 单行脚本,用于替换不在字符串中的模式。看起来正适合解决这个问题,但它的前提是模式要和待修改的文本完全匹配。由(一)、(二)两个条件决定了 sed 实现的正则表达式无法匹配 C 语言所有类型的注释。另外,sed 提供的控制语句是“b、t”,它们的功能是类似于 C 语言的 goto,因此它不能像“if ... else ...”一样方便地判断某个注释的起始位置是否在字符串中。
  由于上述原因,我们需要一个变量来记录当前状态——是否在字符串中。因此我使用 awk 来解决。

我的解决方案

# filename: strip_c_comment.awk
# issue: awk -f scrip_c_comment.awk test.c

BEGIN { FS="" }

!(ignore_line && $NF == "\\") && !ignore_line-- {
    ignore_line = 0;
    for(i = 1; i <= NF; i++) {
        if (ignore_block) {
            if ($i $(i+1) == "*/") {
                ignore_block = 0
                i++ # remove '*'
            }
            continue
        }
        if (!instr && $i $(i+1) == "/*") {
            ignore_block = 1
            i++ # remove '/'
            continue
        }
        if (!instr && $i $(i+1) == "//") {
            ignore_line = ($NF == "\\")? 1: 0
            break
        }
        if ($i == "\"") {
            instr = 1 - instr
        }
        printf($i)
    }
    printf("\n")
}
  在开始时将 FS 设为空字符串,使得输入记录的每个字符都成为一个独立的字段。代码中的三个布尔变量分别代表:
  1. ignore_line:如果上一行是以“\”结尾的单行注释则为“True”;
  2. ignore_block:如果当前字符在块注释中则为“True”;
  3. instr:如果当前字符在非注释的字符串内则为“True”。
  脚本的工作就是保留“ignore_line”和“ignore_block”都为“False”时的字符。

执行结果

#include <stdlib.h>
#include <stdio.h>

int main (int argc, char *argv[])
{



    int is; 
    int ms; 
    double ds; 
    double dm; 

 float fs; 


    float fm;
    char cs[] = "aaa // /***/";
    char cm1[] = "hello*/";
    char cm2[] = "/*redraiment";
    

    return EXIT_SUCCESS;
}

2010-01-05

awk学习笔记

awk学习笔记

redraiment, 2010-01-05

看完sed部分之后





  花了几个晚上看完后面 awk 部分。awk 不同于 sed,它是一门模式匹配的程序设计语言。学习 sed 和 awk 时,正则表达式可能是一大障碍。但事有凑巧,去年我暑假我一个人出去散心时,顺带看完了《精通正则表达式(第三版)》,另外我还掌握一些 Bash、Perl 等脚步编程的经验,因此很快就能适应 awk 的风格。
  写到此处,我突然联系到《倚天屠龙记》中的张无忌,他经常说“我有九阳神功护体,学什么武功都很快”;那我们程序员也可以牛气地喊“我掌握了正则表达式,UNIX下工具上手都很快”,哈哈。

环境的问题

  比起 sed,使用 awk 时让我有点小意外,哈哈
  、Debian 5.0 默认安装的 awk 是 mawk。我一开始以为是 GNU awk,直到测试“gensub”函数(gawk特有)时才发现不对,于是通过 CD 盘安装了 GNU awk。到目前为止,gawk 的最新版本是 3.1.7,但 Debian 5.0 的软件包中提供的是 v3.1.5。而 fedora 默认安装的是 gawk v3.1.5,另外我在 Windows 下也使用 gawk.exe v3.1.5。
  、在上文《sed单行脚本学习笔记》中已经提到用模式“[ -~]”来匹配任意可打印字符,这个特性在 mawk 中也可使用。理论上在 GNU gawk 中不能使用,但在 Windows 平台下的 gawk 却也具备此特性。为保持脚本的可移植性,应该用“[:print:]”来代替。
  、gawk 支持扩展的正则表达式,在文档中指出操作符“\B”可以匹配单词中字符与字符之间的空白位置。例如模式“/\Bour/”可以匹配“course”,不能匹配“our”。但这一特性在 gawk v3.1.5 中实现有问题。
$ cat data 
ABCDE
ABCD
ABC
AB
A
$ awk --version | head -1
GNU Awk 3.1.5
$ awk '{gsub(/\B/,"-")}1' data 
A-B-C-DE
A-B-CD
A-BC
A-B
A
  如上所示,当单词长度大于二,在 gsub 中“\B”不能匹配最后一个空白位置。这个问题在 gawk v3.1.6 版本被修复。
$ gawk-3.1.6/gawk --version | head -1
GNU Awk 3.1.6
$ gawk-3.1.6/gawk '{gsub(/\B/,"-")}1' data 
A-B-C-D-E
A-B-C-D
A-B-C
A-B
A
  这到底算不算 bug,也只有 gawk 的维护者说了算,哈哈。

awk单行脚本

  较少的设施总能给人带来更多的快乐。awk 作为一门编程语言,它的能力比原作者预期的更多,一些 sed 很费劲要完成的事情它能轻易完成。这造成的结果是,在大多数情况下用 awk 完成任务后,不会像 sed 一样让你兴奋不已,因为杀鸡用了牛刀,显得理所当然。
  另一方面,一门抽象的语言不仅拥有丰富的表达能力,也有更具可读性的语句,使得语句不会过分精练。同样是输出,awk 中是“print”、sed 中只需一个字符“p”。
  综上原因,用 awk 来编写只要 65 个字符的单行脚本显得勉强,下面罗列《AWK单行脚本快速参考》中几段精练的脚本。
# 删除所有空白行 (类似于 "grep '.' ")
awk NF
  这段脚本使用了 awk 的隐含动作:“{print $0}”。awk 中只要当值为 0 或空("")时才为“False”,否则都是“True”,而 NF 只有在空行上才为 0。因此整个语句的意思就是“但当前行的字段数大于0时,显示该行”。
# 删除重复的、非连续的行
awk '! a[$0]++'
  这段脚本同样使用了隐含的打印动作。awk 的数组是一种关联数组,允许用字符串或数值做下标(事实上数值会先根据 CONVFMT 规则来转换成字符串),因此所谓的数组其实更像是键/值映射。通过关联数组来记录每一行的出现次数,且仅在第一次出现时输出。

单行脚本中的错误

# 倒置每行并打印
awk '{for (i=NF; i>0; i--) printf("%s ",i);printf ("\n")}' file
  其中“printf("%s",i)”应该是“printf("%s",$i)”。这是译文中的错误,原文中正确。
# 删除重复连续的行 (模拟 "uniq")
awk 'a !~ $0; {a=$0}'
  运算符“!~”为“不匹配”,运算符右边可以是 awk 中任意表达式,awk 将它作为一个字符串并用来指定一个正则表达式。由此,这段代码不能正确处理以下数据:
$ cat data 
ABCDE
ABCD
ABC
AB
A
$ awk 'a !~ $0; {a=$0}' data 
ABCDE
  需要将匹配改成比较,即“awk 'a != $0; {a=$0}'”。