通过wordpress钩子接口实现新增修改删除文章后执行相应动作

wordpress hook介绍

hook是wordpress实现功能扩展的重要手段,详细的介绍:
Plugin API

hook主要分两类:actions 和filters。
对应的注册函数分别为:add_action()和add_filter()。

目前支持的hook类型参见:http://adambrown.info/p/wp_hooks

例子

在模板的基础函数文件中:
/data/wp/wordpress/wp-content/themes/premium-style/functions.php

新增以下代码:

function publish_post_extra($post_ID){
    // 1. 初始化
    $ch = curl_init();
    // 2. 设置选项,包括URL
    curl_setopt($ch, CURLOPT_URL, "https://xxx" . $post_ID);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    // 3. 执行
    curl_exec($ch);
}
add_action('publish_post', 'publish_post_extra');
add_action('deleted_post', 'publish_post_extra');

以上的代码实现动作发生时,发送一个特定的通知url。

Docker容器的数据管理

这两天开始学习docker,发现docker确实很强大,让网站部署和维护的效率大大提高。遂准备将手头维护的几个小站,全部docker化。整理的过程中感觉到,docker可以以功能或者进程为单位进行部署和维护,不用再花时间在繁琐的配置上面,但是docker和宿主之间的数据共享以及docker间的数据共享仍然是让人头疼和操心的地方。正好翻到官方文档,看到相关内容,遂决定翻译一下,水平有限,欢迎吐槽。

几个基本概念:
docker: 一种容器管理技术,这里也指既有的开发工具链。
container: 容器
image: 镜像
volum:卷 [译者:卷可以理解成计算机中的文件路径]

原文链接:Manage data in containers

翻译正文:

之前我们介绍了docker基本概念,学习了docker 镜像如何工作以及docker之间的网络和联系。这章节我们将继续讨论怎么在docker内和docker之间管理数据。

我们将着重讨论两种你所能管理docker数据的方式

  • 数据卷
  • 数据卷容器

数据卷

数据卷是一种特殊的存在于一个或者多个docker内部的不同于Union File System的目录。数据卷提供多种有用的特性用来持久化和共享数据:

  • 数据卷在docker初始化时创建。如果容器的镜像包含外挂的数据,外挂的数据将在卷初始化时被拷贝到新的本地卷。
  • 数据卷可以被共享和在多个docker间复用。
  • 可以对数据卷直接修改。
  • 更新镜像时数据卷并不受影响。
  • 即使镜像被删除,数据卷也仍然会持久化到本地。

数据卷被设计用来持久化存储数据,独立于容器的生命周期。当你删除容器时,docker并不会自动删除数据卷,不使用的数据卷,也不会替你“垃圾回收”。

增加一个数据卷

你可以通过 -v 标示在 docker create 和 docker run 命令中给容器增加一个数据卷。你可以多次使用 -v 增加多个数据卷。让我们给我们的web应用容器挂载一个单独的数据卷。

$ docker run -d -P --name web -v /webapp training/webapp python app.py

这条指令将会在容器内部的 /webapp 路径下创建一个新卷。

注意:你也可以使用 VOLUME 指令在 Dockerfile 文件中添加一个或者多个卷到容器中。
docker中的卷默认是读写权限,但你也可以设置为只读。

$ docker run -d -P --name web -v /opt/webapp:ro training/webapp python app.py

查看一个卷

你可以使用 ‘docker inspect’ 指令来查看一个卷。

$ docker inspect web

输出将会提供详细的容器配置和卷信息。输出格式类似如下:

Mounts": [
    {
        "Name": "fac362...80535",
        "Source": "/var/lib/docker/volumes/fac362...80535/_data",
        "Destination": "/webapp",
        "Driver": "local",
        "Mode": "",
        "RW": true
    }
]

你将看到‘Source’表示的是宿主路径,‘Destination’表示的是容器路径。 RW 用来标示这个卷的读写属性。

将宿主目录挂载为数据卷

通过 -v 标示你可以挂载一个宿主目录到容器中。

$ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py

这条命令将宿主的 /src/webapp 挂载到容器的 /opt/webapp 卷上。如果 /opt/webapp 已经存在,/src/webapp 将会覆盖但是不会移除已有的文件。当挂载的卷移除后,原先被覆盖的内容将可以再次使用。这个和mount的行为是一致的。

container-dir 必须是绝对路径,比如 /src/docs。host-dir可以是绝对路径,也可以是一个已经声明过的数据卷。如果你指定host-dir为绝对路径,docker将会按你指定的路径挂载,如果你提供的是一个声明的卷,docker将会按照name 指定的名称创建一个声明的卷。

一个 name 声明的卷必须以字母开头,后面跟随z-z0-9,_,. 或者 -。绝对路径都以 / 开始。

例如,你可以用/foo 或者 foo 作为一个 host-dir.如果你使用 /foo ,docker 创建一个挂载点。如果你使用 foo, docker 创建一个声明的卷。

如果你在 Mac 或者 Windows 上使用 docker,你的docker后台只能拥有有限的权限。docker尝试着自动分享你的 /User 或者 C:\Users 目录,所以在OS X上挂载如下:

docker run -v /Users/<path>:/<container path> ...

windows上如下:

docker run -v /c/Users/<path>:/<container path> ...

其他来自虚拟机的目录,比如你想共享virtualbox 中的某些目录,你需要做些额外的工作。在 virtualbox下,你先要使宿主的目录变成共享,然后才能使用 -v 来挂载。

挂载宿主的目录对测试来说很有用。比如你可以在容器中挂载源码,然后修改代码,实时看修改后的执行效果。宿主的路径必须是绝对路径,如果路径不存在,docker就会在本地创建它。这种 auto-creation 将会被移除。

这里我们仍然挂载 /src/webapp 目录,但是增加了ro选项来声明挂载的目录是只读的。

$ docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py

注意:宿主目录是主机相关的,所以你不能在Dockerfile中挂载一个宿主目录,因为镜像要可移植,而不同的主机有不同的目录结构。

卷标识

Labeling 系统例如 SELinux 要求数据卷被容器挂载时要标示。如果没有标示,安全系统可能会阻止容器内的进程访问卷的内容。默认情况下,docker不会改变操作系统的卷标识。

要在容器的上下文中修改卷标识,你需要在挂载时增加 :z 或者 :Z 标识。z 表示多个容器共享卷内容,所以docke用共享标识来标示卷内容,共享的标识允许多个容器读写内容。Z 表示docker用私有的标识来标示卷内容,所以只有当前的容器能访问这个私有的卷内容。

挂载宿主的文件作为卷

-v 也可以用来挂载一个单独的文件而不仅仅是目录:

$ docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

这个指令将会让你进入容器的命令行,能获取宿主的历史纪录,并且退出时,宿主的历史记录能纪录容器的指令操作。

注意:许多编辑工具包括 vi ,sed –in-place 等可能会导致inode数据结构变更。自从docker1.1.0后,此类操作会导致” sed:cannot rename ./sedKdj9Dy:Device or resource busy”。[In the case where you want to edit the mounted file, it is often easiest to instead mount the parent directory.][译者:这句不是很明白什么意思,望看到的网友指点]

创建和挂载一个数据卷容器

如果你有一些希望分享给多个容器的持久化数据,或者想从 non-persistent 容器中使用这些数据,最好的办法是创建一个命名的数据卷容器,然后从从容器中挂载使用数据。

让我们创建一个共享数据卷的容器。这个容器不运行应用,它提供 training/postgres 镜像,这样所有的容器都共用统一的接口,从而节省空间。

$ docker create -v /dbdata --name dbdata training/postgres /bin/true

你可以在其他的容器中用 –volumes-from 标示来挂载 /dbdata 卷。

$ docker run -d --volumes-from dbdata --name db1 training/postgres

$ docker run -d --volumes-from dbdata --name db2 training/postgres

这个例子里,如果 postgres 镜像已经包含一个叫做 /dbdata 的目录,将会隐藏这个目录,只有挂载的可见。

你可以多次使用 –volumes-from 从多个容器中挂载多个卷。

你也可以用 db1 db2 来引用 dbdata。

$ docker run -d --name db3 --volumes-from db1 training/postgres

如果你删除挂载了卷的容器,包括初始的dbdata 或者子序列的 db1 db2等,这些卷将不会被删除。如果你要删除卷,你需要明确的调用 docker rm -v,这个操作可以让你去在容器之间更新,或者高效的迁移数据。

注意:当你删除一个容器不带 -v 时,docker不会警告提醒你。当你不带 -v 删除容器时,将会出现‘挂起’的数据卷,这些卷不再被容器引用。‘挂起’的卷很难避免,并且占用空间。我们在尽力提升卷管理功能,参见pull request #14214

备份,恢复,迁移数据卷

数据卷的另外一个有用的功能是用来备份,恢复和迁移数据。我们使用 –volumes-from 来创建一个新容器并挂载数据卷:

$ docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

示例中我们启动了一个新容器并且从dbdata 容器挂载了数据卷。我们把宿主本地路径挂载成 /backup.最后,我们用tar命令将dbdata数据卷备份进backup.tar,并存入 /backup目录。当命令结束时,我们将在本地目录下得到一个dbdata的备份。

你可以用它来恢复到之前备份的容器里,或者其他的容器:

docker run -v /dbdata --name dbdata2 ubuntu /bin/bash

然后 un-tar 备份的文件到新的容器数据卷中:

$ docker run --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar"

你可以使用上面的方法来自动化地备份迁移和恢复数据。

使用共享卷的注意点

多个容器可以共享一个或者多个数据卷,但是同时写入的时候会发生冲突。

数据卷在宿主里面是可以直接操作的。你可以使用普通的linux工具操作它们。但是建议你不要这样直接做,因为容器和应用并不知道你的操作,这可能会导致数据操作冲突。

下一步

我们学习了很多关于怎么使用docker,接下来我们将看到怎么将docker和Docker Hub上的服务例如自动编译,创建私有仓库等结合起来使用。

Go to Working with Docker Hub

完!

译者:本文中的部分命令参数是过时或者错误的,但这些可以忽略,并不影响我们理解指令的正确用法。

debian7系统安装docker

起始

最近开始做docker相关的学习和技术准备,准备将目前的一些基础服务,通过docker做同构或者异构分布式调整,提升运维效率和系统稳定性。

安装

我的系统:

Linux chicago1 3.2.0-4-amd64 #1 SMP Debian 3.2.41-2 x86_64 GNU/Linux

Docker要求Kernel 3.8+,幸运的是, wheezy-backports 目前有Kernel 3.16 , 该版本正式支持Docker。

升级系统

  • 从wheezy-backports安装内核

在文件 /etc/apt/sources.list中添加如下行

deb http://http.debian.net/debian wheezy-backports main

然后安装linux-image-amd64包 (注意使用 -t wheezy-backports)

sudo apt-get update
sudo apt-get install -t wheezy-backports linux-image-amd64
  • 重启你的系统。对于Debian来说使用新内核是必要的。

安装Docker

使用get.docker.com 的脚本:

curl -sSL https://get.docker.com/ | sh

卸载

为了卸载Docker包:

$ sudo apt-get purge lxc-docker

为了永久卸载Docker及其依赖包,你应该这样:

$ sudo apt-get autoremove --purge lxc-docker

命令将不会移除镜像,容器,数据卷,或者用户创建的配置文件。如果你希望删除所有镜像,容器,数据卷 ,运行如下命令:

$ rm -rf /var/lib/docker

你必须手动删除用户创建的配置项。

一键迁移wordpress到hugo

网站已经迁移了一个多月,一直想写下,但是因为一个issue的问题,一直不爽快:
the tag format
一直想等待解决再来写。

周末抽空过了一遍代码,代码中是用spyc来生成yaml的。改成[xx, x] 的格式还是要费点神的,而且破坏了原来格式的简单优美,什么意思?看下两种格式的生成就明白了,前者是不用考虑长度的迭代模式,后者要考虑长度,因为要在结尾加后括号’]’,这样逻辑就不是简单流式的了。为了确认是否值得修改,确认了官方的文档:
yaml官方文档
发现2种格式都是支持的,但是hugo的问题是:
文档的tags和categories本质来讲,都是列表数组,没有道理两种采用不同的格式,详情:
the tag format advice
显然关闭的人并没有理解我的意思,但是还是有其他的开发者明白了我的意思,做了回复,总之此问题目前确认是属于hugo的“不优雅”。不打算勉强,为了修复而修复,静待hugo的变化吧。

hugo

hugo是一个能把markdown转化成静态html网站的工具。

wordpress-to-hugo-exporter

说了这么多,言归正传,一键切换主要是使用这个插件:
wordpress-to-hugo-exporter
这个插件功能是通过提取wp的页面,将所有的可见页面转换成markdown文档。

生成md文件

支持两种方式:
1. 在wp插件页面通过按钮执行
2. 在后台命令行通过cli方式执行:

php hugo-export-cli.php

前者的文件会导出到用户客户端,后者生成的.zip在/tmp目录下。

后继的工作

这个插件只是将wp的内容转化成markdown文档,要创建hugo网站,你需要:

  • 找一个你喜欢的theme,从hugo官网就能找到
  • 根据theme的指导,配置相应的config文件,每一个theme的config会有略微差异
  • 将之前的markdown打包文件根据你自己配置的目录层次解压到hugo的content目录下的对应路径中。
    如果以上你都做对了,你应该已经可以看到你的hugo网站了。
    相关参考:
    一步一步教你用hugo搭建博客

中文支持的PR

拿到插件时,是不支持中文路径的,含有中文路径的文章,都是404。这个对大陆用户来说很不方便的,因为有很多人的文章都是有中文路径的。于是提交了对应的PR:
fix the unicode url issue and markdown format error
作者很勤快,很快就合入了主干,所以现在大家可以直接使用主干的代码,就是支持中文了。

selinux相关的permission-denied问题

起因

最近将系统更新到了centos7,由于centos7 采用systemctl 替代了原有server相关的服务管理接口。当使用

systemctl start nginx.service

时,系统提示出现错误,请运行:

systemctl status nginx.service -l

-l 表示显示完整的提示日志,否则会出现提示日志缩略的问题。运行status指令时,出现下面日志:

[root@server]# systemctl status nginx.service -l
nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled)
   Active: failed (Result: exit-code) since Fri 2016-01-08 11:56:09 EST; 30min ago
  Process: 704 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=1/FAILURE)
  Process: 698 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)

Jan 08 11:56:09 chicago1 nginx[704]: nginx: [emerg] BIO_new_file("/xxx/xxx.crt") failed (SSL: error:0200100D:system library:fopen:Permission denied:fopen('/xxx/xxx.crt','r') error:2006D002:BIO routines:BIO_new_file:system lib)
Jan 08 11:56:09 chicago1 nginx[704]: nginx: configuration file /etc/nginx/nginx.conf test failed
Jan 08 11:56:09 chicago1 systemd[1]: nginx.service: control process exited, code=exited status=1
Jan 08 11:56:09 chicago1 systemd[1]: Failed to start The nginx HTTP and reverse proxy server.
Jan 08 11:56:09 chicago1 systemd[1]: Unit nginx.service entered failed state.

分析

因为看到’Permission denied’,所以先检查文件权限,ll指令查看,文件的权限是没有问题。想起的了之前遇到的部署nginx时,部署在自定义目录下不行,部署在nginx默认安装目录’/usr/share/nginx/html’ 下就可以的问题。当时也是直接ls查看文件属性,两边的文件权限和归属都是相同的。

今天再次遇到这个问题是,觉得两次应该是同一个问题,所以有必要彻搞清楚,因为是权限问题,所以还是要从权限策略方面入手,于是想到selinux,在之前的系统中默认都是关闭的,但在新系统中我们并没有关闭。于是确认下:

[root@server]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      28

于是我们查看下selinux策略配置:

ls -lrtZ /usr/share/nginx/html

显示如下:

-rw-r--r--. root root system_u:object_r:usr_t:s0

而我们自己的目录:

-rw-r--r--. root root unconfined_u:object_r:default_t:s0

解决

于是修改策略:

chcon -R -u system_u /xxx/
chcon -R -t usr_t /xxx/

重新执行,发现还是失败,最终确认,需要重启后,才能生效。

一线之间

英文名: “A Fine Line”

应了那句“书非借不能读也”的俗语,本人很少涉猎非计算机书籍,但是因为是朋友的推荐,竟然很快地读完了此书。
作者是一位有着多年设计经验参加过无数伟大公司经典产品设计的设计师和创业者,创立了著名的“青蛙”公司。

觉得值得推荐,主要因为以下几点:

  • 翻译很到位,阅读很流畅舒服。
  • 作者的经验总结都来源于实际的案例,涉及国际一线公司,从而不会让人觉得空洞而无趣。
  • 作者的境界很高,将创意设计和商业成功,公司竞争力,外包模式,全球环境等联系在了一起,描述了其关联和相互作用,是一本让人很惊醒和受启发的好书。

此书适合的群体对象:设计师,产品经理,决策管理层,创业厂长们。

更多参见:
豆瓣地址:http://book.douban.com/subject/3865302/
其他:http://www.madisonboom.com/2012/11/10/book-of-a-fine-line-by-hartmut-esslinger/

参考封面:

正则学习笔记

简介

如果做文本处理,日志运维,数据分析,爬虫等相关工作的同学,一定离不开的一个工具就是正则表达式。正则可以从一个完整的字符串中查找或者提取到具有相同“模式”的字符串子串。

什么是正则表达式

正则就是描述文本规则的代码,例如: *.doc 表示所有以doc后缀结束的文件名,这就是一个简单的正则。

基本模式

正则 含义
[abc] A single character of: a, b or c
[^abc] Any single character except: a, b, or c
[a-z] Any single character in the range a-z
[a-zA-Z] Any single character in the range a-z or A-Z
^ Start of line
$ End of line
\A Start of string
\z End of string
. Any single character
\s Any whitespace character
\S Any non-whitespace character
\d Any digit
\D Any non-digit
\w Any word character (letter, number, underscore)
\W Any non-word character
\b Any word boundary
(…) Capture everything enclosed
(a/b) a or b
a? Zero or one of a
a* Zero or more of a
a+ One or more of a
a{3} Exactly 3 of a
a{3,} 3 or more of a
a{3,6} Between 3 and 6 of a

元字符

元字符是正则中的具有独立完整意义的字符,类似变成语言中的保留字,只不过保留字一般是多字符的。

代码/语法 说明
. 匹配除换行符以外的任意字符
w 匹配字母或数字或下划线或汉字
s 匹配任意的空白符
d 匹配数字
b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

例子说明:
元字符 ^(和数字6在同一个键位上的符号)和 $ 都匹配一个位置,这和 \b 有点类似。^ 匹配你要用来查找的字符串的开头,$ 匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的 QQ 号必须为 5 位到 12 位数字时,可以使用:^\d{5,12}$ 。
这里的 {5,12} 和前面介绍过的 {2} 是类似的,只不过 {2} 匹配只能不多不少重复 2 次,{5,12} 则是重复的次数不能少于 5 次,不能多于 12 次,否则都不匹配。
因为使用了 ^ 和 $,所以输入的整个字符串都要用来和 \d{5,12} 来匹配,也就是说整个输入必须是 5 到 12 个数字,因此如果输入的QQ号能匹配这个正则表达式的话,那就符合要求了。

字符转义

如果你想查找元字符本身的话,比如你查找 . 或者 *,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\ 来取消这些字符的特殊意义。因此,你应该使用 \. 和 \* 。当然,要查找 \ 本身,你也得用 \\。
例如:unibetter\.com 匹配 unibetter.com,C:\\Windows 匹配 C:\Windows。

重复

常用的表示符

代码/语法 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

字符类

要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?
很简单,你只需要在方括号里列出它们就行了,像 [aeiou] 就匹配任何一个英文元音字母,[.?!] 匹配标点符号 (. 或 ? 或 !)。
我们也可以轻松地指定一个字符范围,像 [0-9] 代表的含意与 \d 就是完全一致的:一位数字;同理 [a-z0-9A-Z_] 也完全等同于 \w(如果只考虑英文的话)。

下面是一个更复杂的表达式:

\(?0\d{2}[)-]?\d{8}  

英文括号 ( 和 ) 也是元字符,后面的分组节里会提到,所以在这里需要使用转义。

这个表达式可以匹配几种格式的电话号码,像 (010)88886666,或 022-22334455,或 02912345678 等。我们对它进行一些分析吧:
首先是一个转义字符 (,它能出现 0 次或 1 次(?),然后是一个 0,后面跟着 2 个数字 (\d{2}),然后是)或 – 或 空格 中的一个,它出现 1 次或不出现(?),最后是 8 个数(\d{8})。

分支条件

正则1|正则2:从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

分组

我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。

(\d{1,3}.){3}\d{1,3} 是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3} 匹配 1 到 3 位的数字,(\d{1,3}.){3} 匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复 3 次,最后再加上一个一到三位的数字 (\d{1,3})。

不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:

((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)  

理解这个表达式的关键是理解 2[0-4]\d|25[0-5]|[01]?\d\d?,具体含义参看后面的注释章节

反义

有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义:

代码/语法 说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了 x 以外的任意字符
[^aeiou] 匹配除了 aeiou 这几个字母以外的任意字符

例子:
\S+ 匹配不包含空白符的字符串。
\<a[^>]+> 匹配用尖括号括起来的以 a 开头的字符串。

后向引用

使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。

呃……其实,组号分配还不像我刚说得那么简单:
分组0对应整个正则表达式
实际上组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给命名组分配--因此所有命名组的组号都大于未命名的组号
你可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权.

后向引用用于重复搜索前面某个分组匹配的文本。例如,\1 代表分组 1 匹配的文本。难以理解?请看示例:

\b(\w+)\b\s+\1\b 可以用来匹配重复的单词,像 go go, 或者 kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字 (\b(\w+)\b),这个单词会被捕获到编号为 1 的分组中,然后是 1 个或几个空白符 (\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。

你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?\\w+) (或者把尖括号换成 ‘ 也行:(?’Word’\w+)),这样就把 \w+ 的组名指定为 Word了。要反向引用这个分组捕获的内容,你可以使用 \k\,所以上一个例子也可以写成这样:\b(?\\w+)\b\s+\k\\b。

使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:

常用分组语法

分类 代码/语法 说明
捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
(?< name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp)
(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言 (?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

我们已经讨论了前两种语法。第三个 (?:exp) 不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面,也不会拥有组号。“我为什么会想要这样做?”——好问题,你觉得为什么呢?

零宽断言

接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像 \b , ^ , $ 那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。最好还是拿例子来说明吧:

断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。

(?=exp) 也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式 exp。比如 \b\w+(?=ing\b),匹配以 ing 结尾的单词的前面部分(除了ing以外的部分),如查找 I’m singing while you’re dancing. 时,它会匹配 sing 和 danc。

(?<=exp) 也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如 (?<=\bre)\w+\b 会匹配以 re 开头的单词的后半部分(除了re以外的部分),例如在查找 reading a book 时,它匹配 ading。

假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})+\b,用它对 1234567890 进行查找时结果是 234567890。

下面这个例子同时使用了这两种断言:

(?<=\s)\d+(?=\s)

匹配以空白符间隔的数字 (再次强调,不包括这些空白符)。

负向零宽断言

前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词–它里面出现了字母 q,但是 q 后面跟的不是字母 u,我们可以尝试这样:

\b\w*q[^u]\w*\b 匹配包含后面不是字母 u 的字母 q 的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像 Iraq,Benq,这个表达式就会出错。这是因为 [^u] 总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的 [^u] 将会匹配 q 后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的 \w*\b 将会匹配下一个单词,于是 \b\w*q[^u]\w*\b 就能匹配整个 Iraq fighting。负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b。

零宽度负预测先行断言 (?!exp),断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d) 匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。

同理,我们可以用 (?<!exp),零宽度负回顾后发断言来断言此位置的前面不能匹配表达式 exp:(?<![a-z])\d{7} 匹配前面不是小写字母的七位数字。

请详细分析表达式 (?<=<(\w+)>).*(?=<\/\1>),这个表达式最能表现零宽断言的真正用途。

一个更复杂的例子:
(?<=<(\w+)>).*(?=<\/\1>) 匹配不包含属性的简单 HTML 标签内里的内容。(?<=<(\w+)>) 指定了这样的前缀:被尖括号括起来的单词(比如可能是 < xxx> ),然后是 “.*” (任意的字符串),最后是一个后缀 (?=<\/\1>)。注意后缀里的 \/,它用到了前面提过的字符转义;\1 则是一个反向引用,引用的正是捕获的第一组,前面的 (\w+) 匹配的内容,这样如果前缀实际上是 < xxx> 的话,后缀就是 < /xxx> 了。整个表达式匹配的是 < xxx> 和 < /xxx> 之间的内容(再次提醒,不包括前缀和后缀本身)。

注释

小括号的另一种用途是通过语法 (?#comment) 来包含注释。例如:
2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)

要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样:

(?<=    # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签)
)       # 前缀结束
.*      # 匹配任意文本
(?=     # 断言要匹配的文本的后缀
<\/\1>  # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
)       # 后缀结束

贪婪与懒惰匹配

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以 a 开始,以 b 结束的字符串。如果用它来搜索 aabab 的话,它会匹配整个字符串 aabab。这被称为贪婪匹配。

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ? 。这样 .*? 就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:

a.*?b 匹配最短的,以 a 开始,以 b 结束的字符串。如果把它应用于 aabab的话,它会匹配 aab(第一到第三个字符)和 ab(第四到第五个字符)。

为什么第一个匹配是 aab(第一到第三个字符)而不是 ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。

懒惰限定符

代码/语法 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

处理选项

正则支持设置匹配的选项,比如忽略大小写,忽略空白,单行模式,多行模式等

其他语法

代码/语法 说明
\a 报警字符(打印它的效果是电脑嘀一声)
\b 通常是单词分界位置,但如果在字符类里使用代表退格
\t 制表符,Tab
\r 回车
\v 竖向制表符
\f 换页符
\n 换行符
\e Escape
\0nn ASCII代码中八进制代码为nn的字符
\xnn ASCII代码中十六进制代码为nn的字符
\unnnn Unicode代码中十六进制代码为nnnn的字符
\cN ASCII控制字符。比如\cC代表Ctrl+C
\A 字符串开头(类似^,但不受处理多行选项的影响)
\Z 字符串结尾或行尾(不受处理多行选项的影响)
\z 字符串结尾(类似$,但不受处理多行选项的影响)
\G 当前搜索的开头
\p{name} Unicode中命名为name的字符类,例如\p{IsGreek}
(?>exp) 贪婪子表达式
(?< x>-< y>exp) 平衡组
(?im-nsx:exp) 在子表达式exp中改变处理选项
(?im-nsx) 为表达式后面的部分改变处理选项
(?(exp)yes/no) 把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no
(?(exp)yes) 同上,只是使用空表达式作为no
(?(name)yes/no) 如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no
(?(name)yes) 同上,只是使用空表达式作为no

在线测试

以上这么多知识,边学边练是很有必要的。这里推荐两个有趣的在线网站:
http://rubular.com/
https://www.debuggex.com/

参考

http://blog.jobbole.com/96708/

通过源码将git升级到最新版

因为go-get下载hugo失败,需要升级git到最新版本。

下载最新git代码

wget -O git-master.zip https://github.com/git/git/archive/master.zip

编译

unzip git-master.zip
cd git-master

yum install openssl-devel curl-devel expat-devel perl-ExtUtils-MakeMaker gettext gettext-libs gettext-devel asciidoc xmlto docbook2X

ln -s /usr/bin/db2x_docbook2texi /usr/bin/docbook2x-texi

错误

libgit.a(utf8.o): In function `reencode_string_iconv':
/usr/local/git/utf8.c:463: undefined reference to `libiconv'
libgit.a(utf8.o): In function `reencode_string_len':
/usr/local/git/utf8.c:502: undefined reference to `libiconv_open'
/usr/local/git/utf8.c:521: undefined reference to `libiconv_close'
/usr/local/git/utf8.c:515: undefined reference to `libiconv_open'
collect2: ld returned 1 exit status
make: *** [git-credential-store] Error 1

从错误提示看,是缺少libiconv。

安装libiconv

  1. 下载编译

    cd /usr/local
    wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
    tar -zxvf libiconv-1.14.tar.gz
    cd libiconv-1.14
    ./configure –prefix=/usr/local/libiconv && make && make install

  2. 创建一个软链接到/usr/lib

    ln -s /usr/local/lib/libiconv.so /usr/lib
    ln -s /usr/local/lib/libiconv.so.2 /usr/lib

  3. 然后回到git目录继续编译

    cd /usr/local/git
    make configure
    ./configure –prefix=/usr/local –with-iconv=/usr/local/libiconv
    make
    make install

  4. 此时,git应该编译安装成功了,测试如下:

    $ git –version
    git version 2.5.0

centos6下go get卡停问题

go get可以通过选项,直接下载所有依赖项,非常方便。所以在安装hugo时,我们也通过go get 下载源码:

go get -u -v github.com/spf13/hugo 

下载时突然卡住,没有任何反应和提示,日志停留在:

Fetching https://gopkg.in/fsnotify.v1?go-get=1
Parsing meta tags from https://gopkg.in/fsnotify.v1?go-get=1 (status code 200)
get "gopkg.in/fsnotify.v1": found meta tag main.metaImport{Prefix:"gopkg.in/fsnotify.v1", VCS:"git", RepoRoot:"https://gopkg.in/fsnotify.v1"} at https://gopkg.in/fsnotify.v1?go-get=1
gopkg.in/fsnotify.v1 (download) 

根据缺失的库逐一排查,后来根据缺失 gopkg.in/yaml.v1 为线索,网上说是原因git版本太低,需>= 1.7.9.5,而Centos6.7 自带的git 是1.7.1的。

升级到git最新版后,go get 成功!

一步一步教你用hugo搭建博客

Hugo 是一个轻量级的静态网站生成工具,是基于GO语言的模版技术开发而成,因为最近在学习go,就花了时间研究了下,一研究就喜欢上了。
再加上最新wordpress版本有严重的问题,在文章发表后或者再次编辑时,编辑框会丢失所有的格式,这个让使用Markdown的人无法接受。

安装hugo

Hugo官方主页:HUGO
hugo托管在github上,我们可以直接二进制安装也可以源码安装。
这里我们演示源码安装。

  1. 源码安装
    在go里面我们可以直接通过get安装:
    go get -u -v github.com/spf13/hugo
    或者直接git下载
    git clone https://github.com/spf13/hugo.git

  2. 编译
    go build -o hugo main.go
    mv hugo $GOPATH/bin
    终端查看是否成功,mac下可能出现路径没找到的问题,要重新开终端
    $ hugo version
    Hugo Static Site Generator v0.16-DEV BuildDate: 2015-12-14T16:07:24+08:00

生成静态站点

  1. 创建网站
    我们先创建一个空网站.
    $ hugo new site localhost
    $ tree
    .
    ├── archetypes
    ├── config.toml
    ├── content
    ├── data
    ├── layouts
    ├── public
    ├── static
    └── themes  

默认情况下这些目录都是空的,直接运行的话会有ERROR提示

    ERROR: 2015/12/14   =============================================================
    ERROR: 2015/12/14 Your rendered home page is blank: /index.html is zero-length
    ERROR: 2015/12/14  * Did you specify a theme on the command-line or in your
    ERROR: 2015/12/14    "config.toml" file?  (Current theme: "")
    ERROR: 2015/12/14  * For more debugging information, run "hugo -v"
    ERROR: 2015/12/14 =============================================================

看提示说是没有指定theme导致,我们需要下载一个theme。

  1. 安装theme
    我们可以从hugo的网站下载自己喜欢的theme
    $ cd themes
    $ git clone https://github.com/spf13/hyde.git

  2. 测试框架
    安装完theme后,我们体验下效果,使用 hugo server就可以起一个http server,默认监听在1313端口,如果没有在config中配置theme,就要指定theme。

    $ hugo server -t hyde
    0 draft content
    0 future content
    0 pages created
    0 paginator pages created
    0 tags created
    0 topics created
    in 24 ms
    Watching for changes in /Users/alex/run/localhost/{data,content,layouts,static,themes}
    Serving pages from memory
    Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
    Press Ctrl+C to stop            

hyde 界面

  1. 发表文章
    hugo里面写文章其实就是写markdown文档了。写好文档,hugo会给你自动转成html静态文件。我们通过Hugo创建一个md文档。
    $ hugo new first.md
    /Users/alex/run/localhost/content/first.md created
    运行时在网站根目录下运行,创建的文件默认创建在content目录下。
    +++
    date = "2015-12-15T22:35:22+08:00"
    draft = true
    title = "first"
    
    +++

我们从内容看默认创建的是草稿类型,需要将draft改为true才能看到页面。正常情况下我们会通过Mou或者github编辑文档,只要文件头符合hugo的规范就可以。
第一篇文章

调试部署

  1. 调试
    在开发的过程中,我们需要不断的修改验证,所以hugo支持LiveReload功能,用户修改后,可以实时看到效果。执行hugo server命令时加上-w选项,hugo就可以自动检测本地站点文件的变更。
    $ hugo server -w -t hyde
    注意:在使用server命令时,hugo并没有在public目录下产生相应的静态页面。

  2. 部署
    部署时,我们需要生成静态页面文件,然后就可以随便部署在自己的空间上了。转化时,一个hugo命令就搞定:

    $ hugo -t hyde
    0 draft content
    0 future content
    1 pages created
    0 paginator pages created
    0 topics created
    0 tags created
    in 38 ms

我们看到有一个页面生成了,默认在public目录,实际一起生成的还有其他文件:

    $ ls
    404.html                first
    apple-touch-icon-144-precomposed.png    index.html
    css                 index.xml
    favicon.png             sitemap.xml

把这些文件放到你的空间,你就可以看见你的页面和theme了。

到这里,我们已经有了一个基本的能创建文章并且显示的网站了。

have fun!

记一次移动端frame注入事件

起因

在用手机调试网站效果时,偶尔发现底部出现广告,于是就有了以下的内容。

界面表现


停留一会后自动消失

代码表现

通过提取,我们可以拿到广告出现时和消失后的代码。
出现广告时页面被插入的frame代码
<iframe src="http://i.dreamfull.cn/api/my.jsp?sid=320418129&amp;pn=_QWERJAD_274192119_320418129_1_&amp;sd=api.dreamfull.cn#_maerd_dnegel_=1" style="display: none; border: 0px; width: 0px; height: 0px;"></iframe><div style="display: block; visibility: visible; overflow: hidden; width: 375px; height: 56.25px; margin: 0px; padding: 0px; border: 0px; box-sizing: border-box; z-index: 2147483647; position: fixed; bottom: 0px; left: 0px;"><div style="position: relative; z-index: 0;"><a style="width: 100%; height: 59px; display: none; text-decoration: none; -webkit-box-align: center; color: rgb(0, 0, 0); overflow: hidden; line-height: 59px; font-size: 10px;"></a><script src="http://c3.moogos.com/js/_jssdk.js?aid=s4b7b3f9" type="text/javascript" async="async"></script><div style="left: 0px; font-size: 0px; z-index: 2147483583; position: fixed; bottom: 0px; display: block; width: 100%; height: 59px;" csstext="display:block;left:0;font-size:0;z-index:2147483583;position:fixed;bottom:0;display:block;width:100%;height:59px;"><iframe style="border: 1px; bottom: 0px; display: block; width: 375px; height: 56.25px;" frameborder="0" scrolling="no" border="0" src="http://api.moogos.com/js/index.html?_ts=1450470254933&amp;info=%7B%22domain%22:%22www.goodmemory.cc%22,%22urls%22:%22www.goodmemory.cc/%25E9%2580%259A%25E8%25BF%2587event-hook%25E5%25B0%2586github%25E8%2587%25AA%25E5%258A%25A8%25E9%2583%25A8%25E7%25BD%25B2%25E8%2587%25B3hugo%25E7%25BD%2591%25E7%25AB%2599/%22,%22adslot%22:%22s4b7b3f9%22,%22version%22:%7B%22major%22:2,%22minor%22:26%7D,%22prod%22:1,%22adParentId%22:%22moogos_s4b7b3f9%22,%22inittime%22:1450470254923,%22duration%22:10,%22appId%22:%2202eddf5b%22%7D"></iframe><div style="position:absolute; width:30px; height:30px;top:0;right:0;z-index:2147483584;background:rgba(255,255,255,0);" onclick="var p = this.parentNode;p.parentNode.removeChild(p);p.setAttribute(&quot;close&quot;, &quot;0&quot;);var b = document.getElementById(&quot;blankDivs4b7b3f9&quot;);b &amp;&amp; b.parentNode.removeChild(b);"><i style="position:absolute; width:1px; height:18px; background:#000; top:6px;left:6px; -ms-transform:rotate(45deg); -webkit-transform:rotate(45deg); -moz-transform:rotate(45deg); transform:rotate(45deg); left:15px;"></i><i style="position:absolute; width:1px; height:18px; background:#000; top:6px;left:6px; -ms-transform:rotate(-45deg); -webkit-transform:rotate(-45deg); -moz-transform:rotate(-45deg); transform:rotate(-45deg); left:15px;"></i><i style="position:absolute; width:24px; height:24px; top:3px; left:3px; background:rgba(255,255,255,0.4); border-radius:50%;"></i></div></div><div style="position: absolute; top: -20px; right: -10px; z-index: 99; width: 50px; height: 60px; overflow: hidden; display: block; left: auto !important; background: url(&quot;http://api.dreamfull.cn/s/images/none.png&quot;) repeat transparent;"></div></div><div style="position: absolute; cursor: pointer; bottom: 0px; right: 0px; z-index: 100; width: 35px; height: 20px; overflow: hidden; display: block; left: auto !important; background: url(&quot;http://api.dreamfull.cn/s/images/logo_mini.gif&quot;) repeat transparent;"></div><div style="position: absolute; cursor: pointer; top: 0px; right: 0px; z-index: 100; width: 35px; height: 20px; overflow: hidden; display: block; left: auto !important; background: url(&quot;http://api.dreamfull.cn/s/images/none.png&quot;) repeat transparent;"></div><div style="position: absolute; cursor: pointer; top: 0px; right: 0px; z-index: 100; display: none; width: 86px; height: 20px; overflow: hidden; left: auto !important; background: url(&quot;http://api.dreamfull.cn/s/images/close_long.gif&quot;) repeat transparent;"></div></div>
广告自动消失后的代码变成:
<img src="http://rcv.moogos.com/rtsdk?type=show&amp;version=2.26&amp;urls=www.goodmemory.cc/%E9%80%9A%E8%BF%87event-hook%E5%B0%86github%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%E8%87%B3hugo%E7%BD%91%E7%AB%99/&amp;adslot=s4b7b3f9&amp;_ts=1450467804078" style="display:none;">

分析

通过以上的信息,我们得到如下信息:
* http://dreamfull.cn 是广告平台方,根据域名信息查询:


* 广告投放方为http://moogos.com
* 广告代码样本
https://hiproz.github.io/goodmemory.cc/blog/images/2015/12/frame1.txt
https://hiproz.github.io/goodmemory.cc/blog/images/2015/12/frame2.txt
* 其中的js脚本:http://c3.moogos.com/js/_jssdk.js?aid=s4b7b3f9,搜索这个脚本中的”.com”和”.png”能看到更多细节。
为了避免被清理,做了备份,方便后面举证:
https://hiproz.github.io/goodmemory.cc/blog/images/2015/12/jssdk.js-bak
* 搜索了以下,有很多dreamfull.cn的案例,多和联通有关:

安全

因为注入的原理不是在服务器修改源代码的,可能是在运营商的路由环节,或者我们使用的所谓智能路由器,或者第三方动态加载时被劫持,所以很难从根源上消除,目前能想到的就是先把https做了,还有就是加载安全js插件,用户加载时动态触发检测,不过这个只是计划,目前本人能时间上和能力还做不到。

最后

以上做了这么多细致的工作,是为了拿到更多的证据,方便更多的人去投诉和举报,创造健康的网络环境。

通过event-hook将Github自动部署至Hugo网站

WHAT

hugo是一个轻量高效的博客系统,很适合个人博客。使用hugo,我们只要写作完markdown文档,就可以利用hugo工具,自动生成网页,
变成我们的网站。

我们理想的步骤:

  • 在github上写完markdown文章
  • 提交完后,数秒后就看见了我们的网站页面
  • 在github上修改完网站的配置文件,数秒后我们的网站就变化和更新了。

好爽!

流程

为了完成上面的效果,我们大概分为几步:

  1. 设置github的webservice hook。当完成一篇新的文章或者修改旧的文章后,github就会向目标网站发webservice hook消息。
  2. 目标网站收到消息后git pull,解析消息特征,更新相关的文档,最后调用hugo
  3. hugo将markdown文章转化成html静态页面
  4. 将html页面部署到目标web服务器

HOW

详细请移步:
https://github.com/hiproz/hugo-sync

分分钟就搞定了,一劳永逸!

have fun!

hugo-md文档书写时的格式说明

前言

在决定使用github-hugo自动化流程后,第一个面临的问题就是,这个文档头的格式到底是什么样的,因为现在你要手写,就需要彻底搞清楚, 以便达到正确和高效。

阅读查找了gohugo.io的所有相关页面,找到以下两个相关的内容基本上说的很清楚了:
https://gohugo.io/content/front-matter/
https://gohugo.io/content/archetypes/

头格式

md只是文档的类型,但是里面的内容遵循怎样的格式呢? hugo目前支持3中toml,yaml,json,三种格式的文件头。识别符号分别如下

  • toml: +++
  • yaml: —
  • json: {}

toml示例:

+++
title = "spf13-vim 3.0 release and new website"
description = "spf13-vim is a cross platform distribution of vim plugins and resources for Vim."
tags = [ ".vimrc", "plugins", "spf13-vim", "vim" ]
date = "2012-04-06"
categories = [
  "Development",
  "VIM"
]
slug = "spf13-vim-3-0-release-and-new-website"
+++

Content of the file goes Here

yaml示例:

---
title: "spf13-vim 3.0 release and new website"
description: "spf13-vim is a cross platform distribution of vim plugins and resources for Vim."
tags: [ ".vimrc", "plugins", "spf13-vim", "vim" ]
date: "2012-04-06"
categories:
  - "Development"
  - "VIM"
slug: "spf13-vim-3-0-release-and-new-website"
---

Content of the file goes Here

json示例:

{
    "title": "spf13-vim 3.0 release and new website",
    "description": "spf13-vim is a cross platform distribution of vim plugins and resources for Vim.",
    "tags": [ ".vimrc", "plugins", "spf13-vim", "vim" ],
    "date": "2012-04-06",
    "categories": [
        "Development",
        "VIM"
    ],
    "slug": "spf13-vim-3-0-release-and-new-website",
}

Content of the file goes Here

字段变量

文档可以包含很多变量。

必要字段

  • tile: 内容的标题
  • description: 内容的描述
  • date:日期
  • taxonomies:分类字段,包括tag和categories

可选字段

  • aliases: 别名
  • draft: 是否是草稿
  • publishdate: 定时未来发布的时间
  • type: 内容的格式,可以从内容自动识别
  • isCJKLanguage:是否时CJK
  • weight: 排序的权重
  • markup: 时markdown格式还是reStructuredText
  • slug: url尾部的token
  • url: 完整的url

hugo markdown引擎的配置

finish!

mac下安装php56

今天调试php的代码,提示需要PHP56,于是看了下php在github上的指导文档

Installation

Setup the homebrew/dupes tap which has dependencies we need:

$ brew tap homebrew/dupes

Setup the homebrew/versions tap which has dependencies we need:

$ brew tap homebrew/versions

Then, run the following in your command-line:

$ brew tap homebrew/homebrew-php

Usage

Note: For a list of available configuration options run:

$ brew options php56

Once the tap is installed, you can install php53, php54, php55, php56, php70, or any formulae you might need via:

$ brew install php56

按照上面的指导操作,出现如下的告警,导致tap失败 ,后继无法install成功:

$ brew tap homebrew/homebrew-php

Warning: Tap homebrew/php already tapped.

解决:先untap,然后重新tap:

$ brew untap homebrew/php

done!

centos7的mariadb相关配置

centos下面的repo源默认是支持mariadb的,因为是和mysql兼容的,所以如果特定需求,可以直接使用mariadb。

安装

yum -y install MariaDB-server MariaDB-client
or
yum -y install mysql

启动/停止/重启/重新加载配置

systemctl start|stop|restart|reload mariadb.service

修改密码

mysqladmin -u root password ‘root’

自动启动

systemctl enable mariadb.service

初次设置(密码/权限/库)

mysql_secure_installation

Primary script unknown

nginx运行时的error log:

FastCGI sent in stderr: “Primary script unknown” while reading response header from upstream 原因在于nginx 默认的脚本配置中

location ~ \.php$ { 
    root html; fastcgi_pass 127.0.0.1:9000; 
    fastcgi_index index.php; 
    fastcgi_param SCRIPT_FILENAME /script$fastcgi_script_name; 
    include fastcgi_params; 
} 

SCRIPT_FILENAME 需要更新成实际的目录,修改如下:

$document_root$fastcgi_script_name;

c++中的extern “c” {}

讲的很清晰,透彻,特此存档 原文:[C++项目中的extern “C” {}](http://www.cnblogs.com/skynet/archive/2010/07/10/1774964.html)
以下正文:

引言

在用C++的项目源码中,经常会不可避免的会看到下面的代码:

#ifdef __cplusplus
extern "C" {
#endif

/*...*/

#ifdef __cplusplus
}
#endif

它到底有什么用呢,你知道吗?而且这样的问题经常会出现在面试or笔试中。下面我就从以下几个方面来介绍它:

  • 1、#ifdef _cplusplus/#endif _cplusplus及发散
  • 2、extern “C”
    • 2.1、extern关键字
    • 2.2、”C”
    • 2.3、小结extern “C”
  • 3、C和C++互相调用
    • 3.1、C++的编译和连接
    • 3.2、C的编译和连接
    • 3.3、C++中调用C的代码
    • 3.4、C中调用C++的代码
  • 4、C和C++混合调用特别之处函数指针

1、#ifdef _cplusplus/#endif _cplusplus及发散

在介绍extern “C”之前,我们来看下#ifdef _cplusplus/#endif _cplusplus的作用。很明显#ifdef/#endif、#ifndef/#endif用于条件编译,#ifdef _cplusplus/#endif _cplusplus——表示如果定义了宏_cplusplus,就执行#ifdef/#endif之间的语句,否则就不执行。

在这里为什么需要#ifdef _cplusplus/#endif _cplusplus呢?因为C语言中不支持extern “C”声明,如果你明白extern “C”的作用就知道在C中也没有必要这样做,这就是条件编译的作用!在.c文件中包含了extern “C”时会出现编译时错误。

既然说到了条件编译,我就介绍它的一个重要应用——避免重复包含头文件。还记得腾讯笔试就考过这个题目,给出类似下面的代码(下面是我最近在研究的一个开源web服务器——Mongoose的头文件mongoose.h中的一段代码):

#ifndef MONGOOSE_HEADER_INCLUDED
#define    MONGOOSE_HEADER_INCLUDED

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/*.................................
 * do something here
 *.................................
 */

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* MONGOOSE_HEADER_INCLUDED */

然后叫你说明上面宏#ifndef/#endif的作用?为了解释一个问题,我们先来看两个事实:

  • 这个头文件mongoose.h可能在项目中被多个源文件包含(#include “mongoose.h”),而对于一个大型项目来说,这些冗余可能导致错误,因为一个头文件包含类定义或inline函数,在一个源文件中mongoose.h可能会被#include两次(如,a.h头文件包含了mongoose.h,而在b.c文件中#include a.h和mongoose.h)——这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
  • 从逻辑观点和减少编译时间上,都要求去除这些冗余。然而让程序员去分析和去掉这些冗余,不仅枯燥且不太实际,最重要的是有时候又需要这种冗余来保证各个模块的独立

为了解决这个问题,上面代码中的

#ifndef MONGOOSE_HEADER_INCLUDED
#define    MONGOOSE_HEADER_INCLUDED
/*……………………………*/
#endif /* MONGOOSE_HEADER_INCLUDED */

就起作用了。如果定义了MONGOOSE_HEADER_INCLUDED,#ifndef/#endif之间的内容就被忽略掉。因此,编译时第一次看到mongoose.h头文件,它的内容会被读取且给定MONGOOSE_HEADER_INCLUDED一个值。之后再次看到mongoose.h头文件时,MONGOOSE_HEADER_INCLUDED就已经定义了,mongoose.h的内容就不会再次被读取了。

2、extern “C”

首先从字面上分析extern “C”,它由两部分组成——extern关键字、”C”。下面我就从这两个方面来解读extern “C”的含义。

2.1、extern关键字

在一个项目中必须保证函数、变量、枚举等在所有的源文件中保持一致,除非你指定定义为局部的。首先来一个例子:

//file1.c:
    int x=1;
    int f(){do something here}
//file2.c:
    extern int x;
    int f();
    void g(){x=f();}

在file2.c中g()使用的x和f()是定义在file1.c中的。extern关键字表明file2.c中x,仅仅是一个变量的声明,其并不是在定义变量x,并未为x分配内存空间。变量x在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。但是可以声明多次,且声明必须保证类型一致,如:

//file1.c:
    int x=1;
    int b=1;
    extern c;
//file2.c:
    int x;// x equals to default of int type 0
    int f();
    extern double b;
    extern int c;

在这段代码中存在着这样的三个错误:

  1. x被定义了两次
  2. b两次被声明为不同的类型
  3. c被声明了两次,但却没有定义

回到extern关键字,extern是C/C++语言中表明函数全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

与extern对应的关键字是 static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

2.2、”C”

典型的,一个C++程序包含其它语言编写的部分代码。类似的,C++编写的代码片段可能被使用在其它语言编写的代码中。不同语言编写的代码互相调用是困难的,甚至是同一种编写的代码但不同的编译器编译的代码。例如,不同语言和同种语言的不同实现可能会在注册变量保持参数和参数在栈上的布局,这个方面不一样。

为了使它们遵守统一规则,可以使用extern指定一个编译和连接规约。例如,声明C和C++标准库函数strcyp(),并指定它应该根据C的编译和连接规约来链接:

extern "C" char* strcpy(char*,const char*);

注意它与下面的声明的不同之处:

extern char* strcpy(char*,const char*);

下面的这个声明仅表示在连接的时候调用strcpy()。

extern “C”指令非常有用,因为C和C++的近亲关系。注意:extern “C”指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言,如Fortran、assembler等。

还有要说明的是,extern “C”指令仅指定编译和连接规约,但不影响语义。例如在函数声明中,指定了extern “C”,仍然要遵守C++的类型检测、参数转换规则。

再看下面的一个例子,为了声明一个变量而不是定义一个变量,你必须在声明时指定extern关键字,但是当你又加上了”C”,它不会改变语义,但是会改变它的编译和连接方式。

如果你有很多语言要加上extern “C”,你可以将它们放到extern “C”{ }中。

2.3、小结extern “C”

通过上面两节的分析,我们知道extern “C”的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern “C”,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。(注:我在这里所说的类C,代表的是跟C语言的编译和连接方式一致的所有语言)

3、C和C++互相调用

我们既然知道extern “C”是实现的类C和C++的混合编程。下面我们就分别介绍如何在C++中调用C的代码、C中调用C++的代码。首先要明白C和C++互相调用,你得知道它们之间的编译和连接差异,及如何利用extern “C”来实现相互调用。

3.1、C++的编译和连接

C++是一个面向对象语言(虽不是纯粹的面向对象语言),它支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++编译器实际上将下面这些重载函数:

void print(int i);
void print(char c);
void print(float f);
void print(char* s);

编译为:

_print_int
_print_char
_print_float
_pirnt_string

这样的函数名,来唯一标识每个函数。注:不同的编译器实现可能不一样,但是都是利用这种机制。所以当连接是调用print(3)时,它会去查找_print_int(3)这样的函数。下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。

C++中的变量,编译也类似,如全局变量可能编译g_xx,类变量编译为c_xx等。连接是也是按照这种机制去查找相应的变量。

3.2、C的编译和连接

C语言中并没有重载和类这些特性,故并不像C++那样print(int i),会被编译为_print_int,而是直接编译为_print等。因此如果直接在C++中调用C的函数会失败,因为连接是调用C中的print(3)时,它会去找_print_int(3)。因此extern “C”的作用就体现出来了。

3.3、C++中调用C的代码

假设一个C的头文件cHeader.h中包含一个函数print(int i),为了在C++中能够调用它,必须要加上extern关键字(原因在extern关键字那节已经介绍)。它的代码如下:

#ifndef C_HEADER
#define C_HEADER

extern void print(int i);

#endif C_HEADER

相对应的实现文件为cHeader.c的代码为:

#include <stdio.h>
#include "cHeader.h"
void print(int i)
{
    printf("cHeader %d\n",i);
}

现在C++的代码文件C++.cpp中引用C中的print(int i)函数:

extern "C"{
#include "cHeader.h"
}

int main(int argc,char** argv)
{
    print(3);
    return 0;
}

执行程序输出:

image 

3.4、C中调用C++的代码

现在换成在C中调用C++的代码,这与在C++中调用C的代码有所不同。如下在cppHeader.h头文件中定义了下面的代码:

#ifndef CPP_HEADER
#define CPP_HEADER

extern "C" void print(int i);

#endif CPP_HEADER

相应的实现文件cppHeader.cpp文件中代码如下:

#include "cppHeader.h"

#include <iostream>
using namespace std;
void print(int i)
{
    cout<<"cppHeader "<<i<<endl;
}

在C的代码文件c.c中调用print函数:

extern void print(int i);
int main(int argc,char** argv)
{
    print(3);
    return 0;
}

注意在C的代码文件中直接#include “cppHeader.h”头文件,编译出错。而且如果不加extern int print(int i)编译也会出错。

4、C和C++混合调用特别之处函数指针

当我们C和C++混合编程时,有时候会用一种语言定义函数指针,而在应用中将函数指针指向另一中语言定义的函数。如果C和C++共享同一中编译和连接、函数调用机制,这样做是可以的。然而,这样的通用机制,通常不然假定它存在,因此我们必须小心地确保函数以期望的方式调用。

而且当指定一个函数指针的编译和连接方式时,函数的所有类型,包括函数名、函数引入的变量也按照指定的方式编译和连接。如下例:

typedef int (*FT) (const void* ,const void*);//style of C++

extern "C"{
    typedef int (*CFT) (const void*,const void*);//style of C
    void qsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
}

void isort(void* p,size_t n,size_t sz,FT cmp);//style of C++
void xsort(void* p,size_t n,size_t sz,CFT cmp);//style of C

//style of C
extern "C" void ysort(void* p,size_t n,size_t sz,FT cmp);

int compare(const void*,const void*);//style of C++
extern "C" ccomp(const void*,const void*);//style of C

void f(char* v,int sz)
{
    //error,as qsort is style of C
    //but compare is style of C++
    qsort(v,sz,1,&compare);
    qsort(v,sz,1,&ccomp);//ok
    
    isort(v,sz,1,&compare);//ok
    //error,as isort is style of C++
    //but ccomp is style of C
    isort(v,sz,1,&ccopm);
}

注意:typedef int (*FT) (const void* ,const void*),表示定义了一个函数指针的别名FT,这种函数指针指向的函数有这样的特征:返回值为int型、有两个参数,参数类型可以为任意类型的指针(因为为void*)。

最典型的函数指针的别名的例子是,信号处理函数signal,它的定义如下:

typedef void (*HANDLER)(int);
HANDLER signal(int ,HANDLER);

上面的代码定义了信函处理函数signal,它的返回值类型为HANDLER,有两个参数分别为int、HANDLER。 这样避免了要这样定义signal函数:

void (*signal (int ,void(*)(int) ))(int)

比较之后可以明显的体会到typedef的好处。

Go中struct{}{}的解读

新人刚开始看到go中的struct{}{}作为interface的返回值时,一定很费解,怎么理解呢?看下面的例子
关于空接口,我们有如下的用法:

var v1 interface{} = 1      // 将int类型赋值给interface{}
var v2 interface{} = "abc"    // 将string类型赋值给interface{}
var v3 interface{} = &v2    // 将*interface{}类型赋值给interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}

看到4,5行的用法了嘛,其实 struct{}{}只是一个空的特例而已。也就是一个空的struct实例对象而已。