2010-10-31

listfile.exe 用正则表达式来找文件

背景

最近的工作里,涉及到不少目录之间的批量操作(产品的部署、文件夹之间的同步等等)。工作的环境是 Win XP,没有自带诸如 Linux 下 find 这样方便的工具。当然,我在我自己的环境里安装了 Cygwin,但我不能要求其他同事仅仅为了使用这个命令而也装一个 Cygwin。而且,find 是用通配符去匹配文件名,我一直幻想着用正则表达式来匹配文件名/目录,所以我趁周末开发这个小工具。程序已经写好了,并发布到了 Google Code 上,主页是 http://code.google.com/p/listfile/。如下图:

正如下面版权里写的,你可以对这个程序/代码做任何你想做的事情:将这个工具用于你的工作中;拿源代码去交作业;向你的<(男|女)?朋友们?> 炫耀你开发/找到了一个好玩的东西……

使用方法

lf [OPTIONS] PATTERN...

描述

lf 通过给定的模式来列出文件(或目录),类似 UNIX 系统下的 find (1) 命令,不过 find (1) 是用统配符来匹配文件名。比如:星号(*)代表零到多个字符;问号(?)代表单个任意字符。不过,用于匹配路径的话,正则表达式的功能更强大也更灵活。另一个好玩的特性是 lf 可以输出彩色信息,就像 UNIX 里 ls --color=auto 一样。

参数

-a, --all
    显示所有文件(包括隐藏文件)。

-A, --absolute-path
    输出的文件路径显示为绝对路径。
 
-e, --exec command
    执行命令。可以通过<1> .. <n>来引用前面匹配的路径的每一项,而<0>则匹配完整路径。
    此外,可以通过用逗号分隔范围中的第一个和最后一个数字指定一系列连续的文件名。因此<0>和<1,n>是等价的。
 
-h, --help 
    显示这个帮助文档,并退出。

-i, --ignore-case
    匹配时忽略大小写。

-r, --relative-path 
    输出的文件路径显示为相对路径(相对于当前路径)。 

模式

模式表达式同时支持通配符和正则表达式,其中正则表达式要放在尖括号里(“<”和“>”)。

环境变量

USERPROFILE

变量%UserProfile%是 MS Windows NT系统中一个特殊环境变量,它的值是当前用户配置文件所在的目录(类似UNIX下的HOME目录)。

退出值

正常情况下为零,否则为非零。

例子

1. lf "workspaces\project<[1-9]>\src\*.cpp" -e "copy <0> backup\<2,4>"

依次把 project1 到 project9 下的 src 文件夹里的 C++ 源文件备份到 backup 目录下相应的 project*\src 目录下,并且文件名保持一致。

版权

这个程序引用了微软的正则表达式库 GRETA 2.6.4 ,和它相关的东西你要遵循 GRETA 自己的协议。不过,由我写的那部分代码你可以随意使用,你甚至可以告诉你的<(男|女)?朋友们?>(这也是个正则表达式,哈哈)这个程序是你写的!我只希望这个小玩意儿能帮你节省点时间。


2010-02-13

祝大家新春快乐

祝大家新春快乐

redraiment, 2010-02-13


昨天和前天我参加了小学、初中、高中同学会。自毕业以来,小学和初中同学还是第一次相聚,大家都很开心。我也是头一回喝这么多酒,晚上沿着S型曲线回家。

今早起来为自己泡了一杯蜂蜜解酒,并习惯性地打开Google Reader看看订阅的新闻。博客更新的也不少,同学们都发表日志畅谈新年感言。在这弱冠之年不免多愁善感,一些同学抱怨现在春节没有节日气氛、感叹对过年不再有小时候的期盼,还有同学说过年要被逼吃很多垃圾食品、说一些言不由衷的话……

我觉得大家不必如此,节日对我们来说并不是一条法律规定的几月几号。只要快乐,任何一天都是我们的节日。同一天对于每个人来说都有不同的意义,大家都有自己的节日:比如生日、结婚纪念日等。以前对于小朋友来说,穿新衣服就是最快乐的事情,所以过年无疑是最有吸引力的;在谈成一笔大生意或考上重点大学时,我们也都会相邀庆祝;一些人过生日很讲究,但也有像Leonard(生活大爆炸)一样觉得自己从产道被排除并不是一向重大成就,因此不需要纪念……

在忙碌一年后和家人团聚吃年夜饭,是大部分人都觉得开心的事情,所以春节自古就是大家共同的节日。如果哪天大家都觉得千里迢迢挤春运回来吃顿饭不快乐,那这一天也不再是全民庆祝了。同样的,随着年龄的增长、阅历变得丰富,我们不同时期追求亦不相同,小时候喜欢新衣服现在可能喜欢新房新车。有些同学感叹说变得现实、没有童真了,我倒是觉得这样也无可厚非,有些人只喜欢看鲜花绽放的瞬间(比如火影里的迪达拉),也有人喜欢恒久远永流传(像火影里的蝎),也有类似陶渊明的“园日涉以成趣”欣赏春来冬去、花开花谢的全过程……

另一方面,每个人对如何庆祝节日的观点也不一样。有些人喜欢锣鼓喧天、鞭炮齐鸣、红旗招展、人山人海,也有一些朋友喜欢待在“蝉噪林愈静鸟鸣山更幽”的自在生活。前些时候好多朋友都和我说这个寒假时最后一个假期了,建议我出去旅游,我说这一年我东奔西跑,没多少时间待家里,如果我喜欢旅游还回家干嘛?对我来说,在家好好休息就是最好的庆祝方式。

我希望大家都能用自己喜欢的方式庆祝节日,但也要能包容其他人^_^。在此祝福大家新春快乐、身体健康、万事如意!


2010-02-08

过滤分享的Chrome扩展开发详解

过滤分享的Chrome扩展开发详解

redraiment, 2010-02-08

明确需求





  扩展的功能在前文《眼不见为净——过滤人人网分享的chrome扩展》中已做了介绍:把人人网中一些带有“不分享就如何如何”等诅咒信息的标题给屏蔽掉,眼不见为净!描述得更详细些就是:
  1. 过滤功能只在人人网域名(http://*.renren.com/*)下启用;
  2. 只有访问人人网时,扩展的图标才显示;
  3. 好友的分享信息会出现在很多地方(如好友主页、新鲜事、分享主页等),这些都要过滤;
  4. 我只是不想看到那些讨厌的标题,但分享的视频、图片等还是想看的。
  其中(4)是核心功能,(2)可有可无。

Chrome扩展简介

  Chrome扩展的主页是:http://code.google.com/chrome/extensions/index.html。在此我只简单地介绍一下和本扩展相关的内容,更详细的信息请访问其主页。在本扩展中一共只有六个文件,其中三个是图片文件(用于图标),其他三个文件大小都不到1KB,所以无需“多事”把所谓的源码上传,直接把那几行代码贴到文章中即可。下面逐一介绍六个文件:

图标文件

  图标文件有三个尺寸,都是由同一张图片修改而成。我没什么艺术细胞,原始图片的作者并不是我,只是觉得蛮符合这个扩展的主题,呵呵。如果您知道它的作者请给我留言,或者有志同道合的朋友帮忙贡献一张新图标,在此谢过!
  1. icon19.png:大小为19x19,用于地址栏中显示;
  2. icon48.png:大小为48x48,在Chrome的扩展管理页面(chrome://extensions)中显示;
  3. icon128.png:大小为128x128,安装此扩展时显示。
  原始图片:

manifest.json

  这个Chrome扩展的配置文件,用JSON(JavaScript)来描述与扩展相关的信息,完整介绍请访问:http://code.google.com/chrome/extensions/manifest.html。本扩展的配置信息如下:
{
  "name": "FRRS",
  "version": "0.2.3",
  "description": "FRRS",
  "icons": {
    "48": "icon48.png",
    "128": "icon128.png"
  },
  "background_page": "background.html",
  "page_action": {
    "default_icon": "icon19.png",
    "default_title": "FRRS"
  },
  "content_scripts": [
  {
    "matches": ["http://*.renren.com/*"],
    "js": ["filter.js"],
    "run_at": "document_end"
  }
  ]
}
  前面五项比较简单,顾名思义,分别是扩展的“名称”、“版本号”、“描述”、“图标”以及“背景页面”。在本扩展中“背景页面”用于实现功能(2),详细内容会在下文介绍。
  “page_action”是声明页面信息(本例中包含标题和图标),与之对应的是“browser_action”(本例中没有使用)。当你的扩展与页面无关时用“browser_action”,此时图标会一直显示在工具栏中;而像本例中,扩展只是针对某些特点的页面(人人网)则使用“page_action”,此时图标显示在地址栏中,并且默认是不显示的(功能2)。
  至于这个扩展具体作用于哪些页面,则需要在“content_scripts”里指定,其中元素“matches”用于匹配URL(详细内容参见http://code.google.com/chrome/extensions/match_patterns.html)、“js”用于指定具体的动作、“run_at”用于指定脚步运行的时间。本例中指定了,当前页面的URL与“http://*.renren.com/*”匹配时(功能1),并且在页面加载完成后,执行“filter.js”里的脚步。

filter.js

// show icon
chrome.extension.sendRequest();

// pattern
var reg = new RegExp('[^>]*不分享[^<]*', 'g');
var msg = 'redraiment提醒您:此信息中可能含有另您不愉快的内容,眼不见心不烦。';

// filter span
var spans = document.getElementsByTagName('span');
for (var s = 0; s < spans.length; s++) {
  var context = spans[s].innerHTML.replace(/<[^>]+>/g, '');
  if (reg.test(context)) {
    spans[s].innerHTML = msg;
  }
}

// filter heads
// ...

// filter href
// ...
  这部分是核心功能,我使用的模式(reg)非常简单:检查是否包含“不分享”三个字。大家可以照葫芦画瓢添加其他模式。
  人人网中分享标题分别使用“span”标签(新鲜事中)、“h4”等标题标签(分享主页)以及“a”超链接标签(好友主页)。我们需要通过DOM逐一获取,此处以“span”标签为例:获取页面中所有的“span”元素,删除它们的子标签,用模式“reg”测试,如果成功就用文本“msg”替换。其他标签依此类推。
  另外一个需要注意的地方:字符编码。人人网页面采用UTF-8编码,这对匹配中文会有影响,因此在保存filter.js这个文件时,需要将字符编码设为UTF-8(默认ANSI),否则将匹配失败。
  剩下的第一行特别引人注目,这不是一个标准的JavaScript方法,而是Chrome提供的API。它向扩展发送了一个请求,此请求可被“background.html”中的脚步捕捉。Chrome扩展就是通过发送和捕捉请求来通信的。

background.html

<html>
<head>
<script>
  chrome.extension.onRequest.addListener(
    function(request, sender, sendResponse) {
      chrome.pageAction.show(sender.tab.id);
    }
  );
</script>
</head>
</html>
  脚步中添加了一个监听器,当捕捉到“filter.js”发送的消息时就显示图标。
  我想大家肯定会问,为什么不直接在“filter.js”里执行“chrome.pageAction.show()”来显示图标,干嘛这么多事要发送一个请求?这是Chrome扩展的限制,Chrome的所以API请参看:http://code.google.com/chrome/extensions/api_index.html,但“content_script”里指定的脚步只能使用部分API(显示图标这个API就不在其中),而“background_page”里可以调用所以API,因此只能通过这种方式来实现。

其他

  扩展开发好了,至于如何发布使用,请参见Chrome扩展开发的“Hello World”:http://code.google.com/chrome/extensions/getstarted.html


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}'”。