在斯坦福公开课《编程范式》中见到的几种特殊的无线循环,需要从内存模型、汇编的角度理解,蛮有意思的。

We should understand why these program does what it does.

  • 数组越界
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdlib.h>

int main(int argc, char const * argv[]){
int i;
int array[4];

for(i=0; i<=4; i++){ // 这里故意数组越界
array[i] = 0;
}

return 0;
}

该程序的内存模型:

--------
| saved PC |
| ———- |
| i = 0 |
| array[3] |
| array[2] |
| array[1] |
| array[0] |

i = 0 时,for 循环条件为真,array[0]=0;
i = 1 时,for 循环条件为真,array[1]=0;
i = 2 时,for 循环条件为真,array[2]=0;
i = 3 时,for 循环条件为真,array[3]=0;
i = 4 时,for 循环条件仍然为真,array[4]=0,也就是说i本来是4,这一步由于数组越界而将其设置为0;
于是,循环条件仍然为真,会无限循环下去。

  • 还是数组越界

将上面程序里面的int array[4]改为short array[4],仍有可能出现无限循环。先来看i=4时内存模型:

Big Endian 系统:

| saved PC |
| 0 0 0 4 |
| array[2] | array[3] |
| array[0] | array[1] |

Little Endian 系统:

| saved PC |
| 4 0 0 0 |
| array[2] | array[3] |
| array[0] | array[1] |

可以看到在Little Endian 系统中,又无限循环了。

  • 仍然是数组越界
1
2
3
4
5
6
7
void foo(){
int array[4];
int i;
for(i=0; i<=4; i++){ // 这里故意数组越界
array[i] -= 4;
}
}

内存模型:

| saved PC |
| array[3] |
| array[2] |
| array[1] |
| array[0] |
| i |

当调用函数foo时,数组array没有初始化,无论没一个元素的值是多少,for循环
会将数组元素的值减4。问题来了,当i=4时,数组越界将saved PC的值减4

我们知道,saved PC是一个指针,指向汇编指令CALL <foo>的下一条指令,减去4过后,
又指向CALL <foo>这条汇编指令,foo返回时又开始调用函数foo,于是无限循环下去。

自行编译安装最新版本的 GCC 编译器,按照 GNU 的安装文档一步步做下来,有一个关键步骤就是
要先 unset 掉一些环境变量,否则会出现各种意想不到的问题,连 Google 都要着好半天才能找到解决办法。

1
unset C_INCLUDE_PATH CPLUS_INCLUDE_PATH LIBRARY_PATH

由于开发需要连接 Oracle 数据库,就自己安装了一个 Oracle 12c,但是,每次开机都得
手动 lsnrctl start && dbstart,比较麻烦,我们总是希望开机就自动为我们启动了相关
服务,于是就想着自己写一个 systemd.service 来管理 Oracle。

  1. 编写 systemd.service 服务脚本,在 /etc/systemd/system/oracle.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=Oracle Service
After=network.target
After=syslog.target

[Service]
User=oracle
Group=dba
Type=oneshot
ExecStart=/home/oracle/bin/oracle.sh start
ExecReload=/home/oracle/bin/oracle.sh restart
ExecStop=/home/oracle/bin/oracle.sh stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
Alias=oracle.service

关于,systemd.service 服务文件的语法格式,参见 man systemd.service 或者 ArchLinux 关于 Systemd 的文档,这个相当详细。

  1. 写一个启动脚本,放在 /home/oracle/bin/oracle.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/bash

export ORACLE_BASE=/u01/app/oracle
export ORACLE_HOME=/${ORACLE_BASE}/product/12.1.0/dbhome_1

function oracle_start(){
${ORACLE_HOME}/bin/lsnrctl start
${ORACLE_HOME}/bin/dbstart

}

function oracle_stop(){
${ORACLE_HOME}/bin/dbshut
${ORACLE_HOME}/bin/lsnrctl stop

}

case "$1" in
start)
oracle_start
echo "Oracle Started Successfully."
;;
stop)
oracle_stop
echo "Oracle Stopped."
;;
restart)
oracle_stop
oracle_start
;;
status)
${ORACLE_HOME}/bin/lsnrctl status
;;
*)
echo "Usage: $(basename $0) {start|strop|restart|status}"
exit 1
esac

exit 0
  1. enable 刚刚写的服务:
1
$ sudo systemdctl enable oracle.service

我在此时启动 Oracle 时,突然出现了一个意想不到的错误:TNS-01190。在网上搜索了一下,原因可能是 listener.ora 文件的权限不对;另一个是其他用户已经
启动了一个监听。ps -ef | grep -i listener 一看,果然是以另外一个用户身份启动了一个监听,在 Xshell 里面开了多个登陆连接,用另一个用户执行了 lsnrctl start
然后试图以 oracle 用户身份执行 lsnrctl stop,当然会出错。真是粗心大意啊。。。

Refference:

Shell 变量的值,可以通过简单的动作来进行删除、替换。

变量内容的删除

假如需要对 Shell 变量的内容进行部分删除,删除的动作可以从前往后删,也可以从后往前删。语法如下:

语法表达式 意义
${variable#pattern} 若变量内容从前向后匹配 pattern,则将匹配的最短的内容删除,类似正则表达式的非贪婪匹配
${variable##pattern} 若变量内容从前向后匹配 pattern,则将匹配的最长的内容删除,类似正则表达式的贪婪匹配
${variable%pattern} 若变量内容从后向前匹配 pattern,则将匹配的最短的内容删除,类似正则表达式的非贪婪匹配
${variable%%pattern} 若变量内容从后向前匹配 pattern,则将匹配的最长的内容删除,类似正则表达式的贪婪匹配

在此,以命令 latex 的绝对路径 /usr/local/texlive/2015/bin/x86_64-linux/latex 来进行实验。

1
2
3
4
5
6
7
8
9
10
11
$ path=$(which latex)
$ echo ${path}
/usr/local/texlive/2015/bin/x86_64-linux/latex
$ echo ${path#/*/} # 从前向后,最短匹配,only 最前面的`/`
usr/local/texlive/2015/bin/x86_64-linux/latex
$ echo ${path##/*/} # 从前向后,最长匹配,只剩下最后面的 `latex`
latex
$ echo ${path%/*} # 从后向前,最短匹配
/usr/local/texlive/2015/bin/x86_64-linux
$ echo ${path%%/local*} # 从后向前,最长匹配
/usr

变量内容替换

替换语法:

语法表达式 意义
${variable/old/new} 从前向后匹配,将遇到的第一个 old 替换成 new
${variable//old/new} 从前向后匹配,将所有陪陪的 old 替换为 new

$PATH 举例,将 bin 替换为大写的 BIN

1
2
3
4
5
6
7
$ path=${PATH}
$ echo ${path}
/opt/mpich-install/bin:/usr/local/texlive/2015/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
$ echo ${path/bin/BIN} # 只替换了第一个
/opt/mpich-install/BIN/:/usr/local/texlive/2015/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
$ echo ${path//bin/BIN} # 全部替换
/opt/mpich-install/BIN:/usr/local/texlive/2015/BIN//x86_64-linux:/usr/local/sBIN:/usr/local/BIN:/usr/sBIN:/usr/BIN:/sBIN:/BIN:/usr/games:/usr/local/games

变量的测试与内容替换

某些时候,我们需要检测一下某个变量是否存在,若存在则使用其原有的值,否则给出一个值。虽然,在 Shell Script 中可以用 if...then... 结构来处理,但直接用 bash 提供的内置功能进行测试判断,
使得整个程序简洁得多。

1
2
3
4
5
6
7
8
9
$ echo $var
# <<=== 空行,表示变量 `var` 不存在或者为空字符
$ var=${var-Linux}
$ echo ${var}
Linux
$ var="Ubuntu Server"
$ var=${var-Linux}
$ echo ${var}
Ubuntu Server

上面的例子中,关键在于减号(-)及其后面的内容,语法格式为 new=${old-default},其意义为:若变量 old 存在且不为空,则将其内容赋值给变量 new
否则,将给定的 default 赋值给 new。Shell 变量设定语法,还可以将前面语法格式中的 - 改为 :-, +, :+, =, :=, ?, :?,一共 8 种格式,各自的意义为:

语法表达式 old 不存在 old 为空字符串 old 存在且不为空
new=${old-content} new=content new= new=$old
new=${old:-content} new=content new=content new=$old
new=${old+content} new= new=content new=content
new=${old:+content} new= new= new=content
new=${old=content} old=content, new=content old 不变,new= old 不变,new=$old
new=${old:=content} old=content, new=content old=content, new=content old 不变,new=$old
new=${old?content} content 输出至 stderr old= new=$old
new=${old:?content} content 输出至 stderr content 输出至 stderr new=$old

Reference:
鸟哥的 Linux 私房菜

刚发布不久的 Nginx 1.9.5 正式支持HTTP/2,于是就来折腾一番吧。由于 HTTP/2 是基于 https, 因此必须先配置 Nginx 支持 https。文中所有操作都在自己本地机器上进行测试,采用的是 openssl 生成自签名证书。

  1. 创建 Key:

    1
    $ openssl genrsa -des3 -out test.key 1024
  2. 创建签名请求:

    1
    $ openssl req -new -subj "/C=US/ST=Mars/L=iTranswarp/O=iTranswarp/OU=iTranswrap/CN=test" -key test.key -out test.csr

    在实际部署时,必须将 CN 字段中的 test 换成域名,否则无法通过浏览器验证。

  3. 将 Key 的口令移除:

    1
    2
    $ mv test.key test.origin.key
    $ openssl rsa -in test.origin.key -out test.key
  4. 用Key签名证书:

    1
    $ openssl x509 -req -days 3650 -in test.csr -signkey test.key -out test.crt
  5. 配置 Nginx

    • 重定向所有的流量到 SSL/TLS
    1
    2
    3
    4
    5
    6
    server {
    listen 80;
    location / {
    return 301 https://$host$request_uri;
    }
    }
    • 启用 HTTP/2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server {
    listen 443 ssl http2 default_server;
    server_name localhost;
    keeplive_timeout 70;

    ssl_certificate /path/to/test.crt;
    ssl_certificate_key /path/to/test.key;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_prefer_server_ciphers on;
    }
  6. 重新加载配置文件

    1
    2
    $ sudo nginx -t   # 检验配置文件是否有错误
    $ sudo nginx -s reload

Refferences:

  1. https://www.nginx.com/blog/nginx-1-9-5/
  2. http://www.liaoxuefeng.com/article/0014189023237367e8d42829de24b6eaf893ca47df4fb5e000

对于 Python 中内置的 list, tuple, set and dict 这样的简单数据结构,我们已经很熟悉了。
一般的应用场景,有这些简单的数据结构,也已经够用了。如果是比较复杂的场景,需要其他更高级的
数据结构,Python 也为我们提供了支持。本文先学习如下几种:

  • collections
  • array
  • heapq
  • bisect
  • weakref
  • copy
  • pprint
  • singly linked-list

Collections

collections 模块中,包含了一些非常有用的数据结构,比如 Counter, defaultdict, OrderedDict, deque, namedtuple 等,其中最常用的就是 Counter, deque, defaultdictCounter 顾名思义,是一个计数器,
继承自内置的 dict,常用的方法除了与 dict 相应的方法之外,就是 most_common 了。

1
2
3
4
5
6
>>> from collections import Counter
>>> Counter(['A', 'A', 'B', 'C', 'D', 'B', 'C', 'A'])
Counter({'A': 3, 'C': 2, 'B': 2, 'D': 1})
>>> d = Counter(['A', 'A', 'B', 'C', 'D', 'B', 'C', 'A'])
>>> d.most_common(3)
[('A', 3), ('C', 2), ('B', 2)]

下面这段代码,用于统计一段文本中每个单词的使用频率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
>>> from collections import Counter
>>> string = """The Zen of Python, by Tim Peters
...
... Beautiful is better than ugly.
... Explicit is better than implicit.
... Simple is better than complex.
... Complex is better than complicated.
... Flat is better than nested.
... Sparse is better than dense.
... Readability counts.
... Special cases aren't special enough to break the rules.
... Although practicality beats purity.
... Errors should never pass silently.
... Unless explicitly silenced.
... In the face of ambiguity, refuse the temptation to guess.
... There should be one-- and preferably only one --obvious way to do it.
... Although that way may not be obvious at first unless you're Dutch.
... Now is better than never.
... Although never is often better than *right* now.
... If the implementation is hard to explain, it's a bad idea.
... If the implementation is easy to explain, it may be a good idea.
... Namespaces are one honking great idea -- let's do more of those!
... """

>>> import re
>>> words = [w for w in re.split(re.compile(r'[ \n,\.\*-]'), string) if w]
>>> rate = Counter(words)
>>> rate.most_common(10)
[('is', 10), ('better', 8), ('than', 8), ('to', 5), ('the', 5), ('idea', 3), ('be', 3), ('never', 3), ('of', 3), ('one', 3)]
>>> Counter({'is': 10, 'better': 8, 'than': 8, 'to': 5, 'the': 5, 'idea': 3, 'be': 3, 'never': 3, 'of': 3, 'one': 3, 'Although': 3, 'implementation': 2, 'explain': 2, 'should': 2, 'do': 2, 'way': 2, 'obvious': 2, 'it': 2, 'may': 2, 'a': 2, 'If': 2, 'Errors': 1, 'nested': 1, 'only': 1, 'explicitly': 1, 'easy': 1, 'Now': 1, 'good': 1, 'rules': 1, 'Explicit': 1, 'break': 1, 'not': 1, 'now': 1, 'Simple': 1, 'bad': 1, 'silently': 1, 'right': 1, 'often': 1, 'hard': 1, 'Complex': 1, 'are': 1, 'pass': 1, 'special': 1, 'Flat': 1, 'dense': 1, 'temptation': 1, 'enough': 1, 'Namespaces': 1, 'complicated': 1, 'Sparse': 1, "aren't": 1, 'by': 1, 'great': 1, 'those!': 1, 'first': 1, 'There': 1, 'counts': 1, "you're": 1, 'guess': 1, 'Unless': 1, "it's": 1, 'implicit': 1, 'ambiguity': 1, 'more': 1, 'that': 1, 'cases': 1, 'Beautiful': 1, 'honking': 1, 'practicality': 1, 'ugly': 1, 'Special': 1, 'at': 1, 'and': 1, 'Peters': 1, 'Tim': 1, 'beats': 1, 'purity': 1, 'Dutch': 1, 'preferably': 1, 'Python': 1, 'silenced': 1, 'complex': 1, 'Readability': 1, 'unless': 1, "let's": 1, 'The': 1, 'refuse': 1, 'Zen': 1, 'face': 1, 'In': 1})

Deque

Python 中 deque 是双端队列,可以从前后两端添加或删除元素,且都是线程安全的,时间复杂的都近似O(1)
虽然 list 也都支持类似的操作,但 list 内部为固定长度的列表操作做了优化,而且对 list 执行 pop(0)
insert(0, value) 会在内存中移动原来的整个 list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
!/usr/bin/env python -tt
# -*- coding: utf-8 -*-

import time
import functools
from collections import deque

n = 10 ** 5

def timeit(func):
@functools.wraps(func)
def decorated(c):
t0 = time.time()
func(c)
t1 = time.time()
print('{0:<30} {1:<20} {2:<20}'.format(type(c), func.__name__, t1-t0))
return decorated


@timeit
def append(c):
for i in range(n):
c.append(i)


@timeit
def append_left(c):
if isinstance(c, deque):
for i in range(n):
c.appendleft(i)
else:
for i in range(n):
c.insert(0, i)


@timeit
def pop(c):
for __ in range(n):
c.pop()


@timeit
def pop_left(c):
if isinstance(c, deque):
for __ in range(n):
c.popleft()
else:
for __ in range(n):
c.pop(0)


def test():
print('{0:<30} {1:<20} {2:<20}'.format('type', 'operation', 'time(s)'))
print('-' * 70)
for container in [deque, list]:
for operation in [append, append_left, pop, pop_left]:
c = container(range(n))
operation(c)


if __name__ == '__main__':
test()

其输出结果:

1
2
3
4
5
6
7
8
9
10
type                           operation            time(s)             
----------------------------------------------------------------------
<type 'collections.deque'> append 0.0305550098419
<type 'collections.deque'> append_left 0.0282950401306
<type 'collections.deque'> pop 0.0276329517365
<type 'collections.deque'> pop_left 0.0309929847717
<type 'list'> append 0.0249011516571
<type 'list'> append_left 31.5822160244
<type 'list'> pop 0.0356750488281
<type 'list'> pop_left 5.80585002899

Defaultdict

除了对试图获取一个不存在的键时进行了异常处理(由函数 dafault_factory 提供一个默认值)外,defaultdict 几乎与 Python 内置的 dict 完全一致。

事实上,Python 内置的 dict 也可以通过 setdfault 方法实现类似功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> from collections import defaultdict
>>> s = 'This is a test for defaultdict in Python'
>>> words = s.aplit()
>>> location = defaultdict(set)
for i, w in enumerate(words):
... location[w].add(i)
...
>>> location
defaultdict(<type 'set'>, {'a': set([2]), 'defaultdict': set([5]), 'for': set([4]), 'This': set([0]), 'is': set([1]), 'Python': set([7]), 'in': set([6]), 'test': set([3])}))

>>> # Use builtin dict
>>> d = {}
>>> for k, v in enumerate(words):
... d.setdefault(v, []).append(k)
...
>>> d
{'a': [2], 'defaultdict': [5], 'for': [4], 'This': [0], 'is': [1], 'Python': [7], 'in': [6], 'test': [3]}

通过,扩展内置 dict 的一种实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> class MyDict(dict):
... def __getitem__(self, key):
... try:
... return super(MyDict, self).__getitem__(key)
... except KeyError:
... value = self[key] = type(self)()
... return value
...
>>> d = MyDict()
>>> d[1][2] = 3
>>> d[2][3] = 6
>>> d[3]['test'] = 'dict'
>>> d
{1: {2: 3}, 2: {3: 6}, 3: {'test': 'dict'}}

Array

array 模块里定义了一种新的数据结构 array,功能长得跟 list 很像,但保存在
其中的元素必须是相同类型的,因而在存储效率上大大高于 list。尽管如此,对 array
的操作都不会比 list 相应的操作快。另外,需要注意的是,当对 array 做列表推导
时,会自动将整个 array 转换成 list,而用 generator expression 则可以避免。

1
2
3
4
5
6
7
>>> from array import array
>>> a = array('i', [1,2,3,4,5])
>>> b = array(a.typecode, (x**2 for x in a))
>>> a
array('i', [1, 2, 3, 4, 5])
>>> b
array('i', [1, 4, 9, 16, 25])

array 的 size 非常大时,对其元素进行就地(in-plaace)操作,很有助于节省存储空间,
通常用 enumerate 来实现。

1
2
3
4
>>> from array import array
>>> a = array('i', range(10000))
>>> for i, x in enumerate(a):
... a[i] = x ** 2

对于大 array, 就地修改元素的值,比用 generator expression 创建一个新 array
大约能快 15% 。

那么,思考一下,何时需要用到 array 呢?

Heapq

Bisect

Weakref

Copy

Pprint

Singly Linked-List

参考资料:

今天,在更新 Scrapy 时,出现了一个错误:

1
2
3
4
5
6
7
building '_openssl' extension
gcc -pthread -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -fPIC -I/usr/local/include/python2.7 -c build/temp.linux-x86_64-2.7/_openssl.c -o build/temp.linux-x86_64-2.7/build/temp.linux-x86_64-2.7/_openssl.o
gcc -pthread -shared -fPIC build/temp.linux-x86_64-2.7/build/temp.linux-x86_64-2.7/_openssl.o -lssl -lcrypto -o build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings/_openssl.so
/usr/bin/ld: /usr/local/lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../lib64/libssl.a(s2_meth.o): relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/local/lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../lib64/libssl.a: error adding symbols: Bad value
collect2: error: ld returned 1 exit status
error: command 'gcc' failed with exit status 1

很显然,是 openssl 出了问题。于是

1
2
3
$ sudo aptitude show openssl
$ openssl version
OpenSSL 1.0.2d 9 Jul 2015

查看openssl包状态,没有问题啊。再用命令 which openssl 一看,

1
2
$ which openssl 
/usr/local/bin/openssl

原来这个版本的 openssl 根本不是系统自带的版本,而系统自带的 openssl 根本没有问题。之前在 nginx 上折腾 HTTP/2 时,自己编译安装了一个新版本的 openssl 在 /usr/local/ 目录下,而 /usr/bin/ld 加载动态链接库时,根据 /etc/ld.so.conf 里面的配置, 先在 /usr/local/lib 目录查找。根据上面的错提示,此版本的 openssl 需要用 -fPIC 参数重新编译才行。

  • -fPIC 参数重新编译 openssl;
  • 编辑 /etc/ld.so.conf,将 /usr/local/lib/usr/local/lib64 两行换到该文件末尾。完了再换回来,否则影响其他自编译安装的动态库的加载顺序。

当然,一劳永逸的方法,就是重新编译 openssl 了。

众所周知, Python 不支持函数重载,因此无法创建一个通用函数,针对不同的参数类型作出不同的处理。例如,创建一个函数 htmlize,接受一个任意类型的 Python 对象作为参数,根据不同的参数类型,将其“序列化”为不同的 HTML 格式:

  • 默认使用<pre>标签;
  • str: 将换行符替换为’
    \n’,然后将其放在<p>标签中;
  • int: 同时显示 10 进制和 16 进制,如: 12 (0xC);
  • list: 以 HTML 列表的形式显示,每个元素按其类型分别格式化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> htmlize({1, 2, 3})
'<pre>{1, 2, 3}</pre>'
>>> htmlize(abs)
'<pre>&lt;built-in function abs&gt;</pre>'
>>> htmlize('Heimlich & Co.\n- a game')
'<p>Heimlich &amp; Co.<br>\n- a game</p>'
>>> htmlize(42)
'<pre>42 (0x2a)</pre>'
>>> print(htmlize(['alpha', 66, {3, 2, 1}]))
<ul>
<li><p>alpha</p></li>
<li><pre>66 (0x42)</pre></li>
<li><pre>{1, 2, 3}</pre></li>
</ul>

常见的做法是同if/elif/elif结构,针对不同类型,分别调用相应的“序列化”函数。这样做主要的问题是缺乏灵活性和可扩展性。Python 3.4 引入的 functools.singledispatch 装饰器,可以使普通函数,成为通用函数(generic function),达到像 C++ 函数重载的效果。Python 3.3及以下的版本,在 PyPI 上有 singledispatch 包提供相应功能。

于是,其实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3 -tt
# -*- coding: utf-8 -*-

from functools import singledispatch
from collections import abc
import numbers
import html

@singledispatch
def htmlize(obj):
return '<pre>{0}</pre>'.format(html.escape(repr(obj)))


@htmlize.register(str)
def _(text):
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)


@htmlize.register(numbers.Integer)
def _(num):
return '{0} (0x{0:X})'.format(num)


@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>{0}</li>\n</ul>'.format(inner)

上述例子来自《O’Reilly Fluent Python》, PEP 443singledispatch 有详细描述。

JavaScript 是目前使用最广泛的客户端脚本语言,越来越多的站点通过 JavaScript 来实现页面动态加载。有统计显示,全球最受欢迎的站点中,有 70% 都使用了 jQuery,一个非常流行的用于DOM操作的JavaScript库。在抓取这类站点时,采用传统的,直接解析HTML页面的方式就行不通了。此时,你会发现通过Python获取到的页面内容,与在浏览器里看到的截然不同;另外,有时 JavaScript 加载页面会有页面重定向,而只有当重定向发生时,页面 URL 才会发生变化。网络爬虫就会抓取失败。So, Ajax or DHTML?

解决方案只有两个:

  • 直接从 JavaScript 中抓取感兴趣的内容
  • 在 Python 中执行相应的 JavaScript,在抓取内容

这里就需要用到两个非常强大的工具了:SeleniumPhantomJS。Selenium 本身不自带浏览器,所以在使用时会自动加载一个浏览器实例。而 PhantomJS 号称 “headless” browser,在加载页面并执行 JavaScript 时,并不会将页面渲染成图形展示出来。而在结合使用,堪称神器。

具体使用实例,且听下回分解。

通常需要修改 SSH 服务器的配置文件/etc/ssh/sshd_conf 来增强其安全性,然后重启 SSH 服务器使之生效。

###1. 更改默认端口

SSH 的默认监听 22 端口,将其修改为大于 1024 的端口号,有助于提高 SSH 安全性,因为一些端口扫描工具不会扫描大于 1024 的端口。

1
2
# Port 22
Port 4096

###2. 只允许 SSH protocol 2

SSH 协议有两个版本,然而版本 1 存在一些安全隐患,包括中间人攻击注入攻击

1
2
# Protocol 2,1
Protocol 2

###3. 只允许指定用户通过 SSH 登录

首先,应该禁止 root 用户通过 SSH 登录系统,需要 root 权限时使用 sudosu命令来获取;然后,指定允许登录的用户。

1
2
3
# PermitRootLogin yes
PermitRootLogin no
AllowUsers john alice bob

###4. 使用 DSA 公钥授权

DSA 公钥授权方式,由于不需要用户名和密码,可以避免字典攻击(dictionary attacks)。
首先,在 client 端用命令ssh-keygen -t dsa 生成私钥-公钥对, Private Key: ~/.ssh/id_dsa, Public Key: ~/.ssh/id_dsa.pub。
然后用ssh-copy-id -p port user@hostname 将公钥复制到 SSH Server。
配置 SSH server 使用DSA 授权,同时取消用户名密码授权:

1
2
3
4
PasswordAuthentication no
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile %h/.ssh/authorized_keys

###5. 用 TCP wrappers 限定可连接的主机

当只运行指定网络内的主机登录时,可以用 TCP wrappers 进行过滤。需编辑 /etc/hosts.deny/etc/hosts.allow

1
2
# echo "sshd: ALL" >> /etc/hosts.deny
# echo "sshd: 192.168.1.0/24 192.168.5.123" >> /etc/hosts.allow

###6. 用 iptables 限制 SSH 访问

可以与 TCP wrappers 同时使用。例如,只允许特定主机访问 SSH 服务并禁止其他主机访问:

1
2
# iptables -A INPUT -p tcp -m state --state NEW --source 193.180.177.13 --dport 22 -j ACCEPT
# iptables -A INPUT -p tcp --dport 22 -j DROP

###7. SSH time-lock tricks

还可以使用不同的 iptables 参数,来实现特定时间内对 SSH 的访问控制,比如 /second, /minute, /hour/day

例1. 实现输入错误密码后锁定 1 分钟,此后每分钟内只允许尝试 1 次:

1
2
# iptables -A INPUT -p tcp -m state --syn --state NEW --dport 22 -m limit --limit 1/minute --limit-burst 1 -j ACCEPT
# iptables -A INPUT -p tcp -m state --syn --state NEW --dport 22 -j DROP

例2. 实现只允许 192.168.1.101 主机连接 SSH 服务,连续 3 次登录错误后,每分钟只允许尝试 1 次:

1
2
# iptables -A INPUT -p tcp -s 193.180.177.13 -m state --syn --state NEW --dport 22 -m limit --limit 1/minute --limit-burst 3 -j ACCEPT
# iptables -A INPUT -p tcp -s 193.180.177.13 -m state --syn --state NEW --dport 22 -j DROP

本文参考:https://www.linux.com/learn/tutorials/305769-advanced-ssh-security-tips-and-tricks