2014年3月29日星期六

详解并改进Linux终端的clear命令

clear做了啥?

clear做的工作很简单:把待输入的下一行移动到左上角。
所以你看,它根本没有真正对终端的内容进行清除,只不过是将其藏起来了。

如何实现clear

Linux的终端控制序列

Linux控制台实现了VT102和ECMA-48/ISO 6429/ANSI X3.64终端控制的子集,详见:console_codes - ubuntu man page
Linux使用ANSI escape code方式来描述其控制序列,详见:ANSI escape code - Wikipedia
简而言之,使用ESC符号开始的一串字符序列会被解析成控制序列,多数控制序列由CSI(control sequence introducer)开始,由ESC [来表示。

使用printf命令实现clear

printf支持终端控制序列,其中ESC可以用\e转义符表示。

console_codes - ubuntu man page中给出了一个表(节选):

Sequences Name Describe
ESC @ ICH Insert the indicated # of blank characters.
ESC A CUU Move cursor up the indicated # of rows.
ESC B CUD Move cursor down the indicated # of rows.
ESC C CUF Move cursor right the indicated # of columns.
ESC D CUB Move cursor left the indicated # of columns.
ESC E CNL Move cursor down the indicated # of rows, to column 1.
ESC F CPL Move cursor up the indicated # of rows, to column 1.
ESC G CHA Move cursor to indicated column in current row.
ESC H CUP Move cursor to the indicated row, column (origin at 1,1).
ESC J ED Erase display (default: from cursor to end of display).
ESC [ 1 J erase from start to cursor.
ESC [ 2 J erase whole display.

维基百科中ANSI escape code - Wikipedia中也说到:

CSI 2 J — This clears the screen and, on some devices, locates the cursor to the y,x position 1,1 (upper left corner).

CSI 2 JESC [ 2 J指令会在清除屏幕后将光标移动到第一行第一列(左上角)。
结合printf,得出以下命令:

printf "\e[2J"

不知道是不是Linux水土的问题,这个命令执行效果和描述不太一样,其实际效果是将当前行顶出屏幕外(负一行),并打印若干空白行(尼玛)。

为了修正这个问题,我们看到有个指令ESC H,描述如下:

Move cursor to the indicated row, column (origin at 1,1).

于是我们可以借助这个指令来完善我们的功能,修改如下:

printf "\e[H\e[2J"

这下终于完成我们坑爹clear命令。

难道就只是这样吗?

clear命令这么坑爹Linus Torvalds知道吗?
enter image description here

实现一个真正意义上的clear

console_codes - ubuntu man page中发现了另一个东西:

Sequences Name Describe
ESC c RIS Reset

重置你的终端来达到清除内容的效果。

printf "\ec"

真是一个简单又暴力的方法。
推荐写成shell脚本并移动到/usr/bin/目录下。


@LYC
转载请注明出处

2014年3月23日星期日

使用Xshell登录到Koding

Koding简介

注册地址:koding.com
Koding是一个在线开发环境,其为每个人提供了免费的计算能力和开发环境,目标是简化全球性的开发。Koding提供了免费的虚拟机和一个真实的Ubuntu OS终端,你能在其上开发你的代码,无论是Python,PHP, C++, C都是可以的。甚至于它们能够在全世界任何(无须梯子)的地方来访问,并进行团队开发。

使用Xshell来登录你的VM

网页版终端提供的功能有限,推荐使用Xshell来进行远程登录。

配置会话

  1. 安装好Xshell,现已有中文版。
  2. Koding上设置你的SSH keys

    没有SSH keys的话需在Xshell中打开菜单栏工具中找到新建用户密钥向导,设置类型为RAS,长度为1024,来生成一个新的SSH keys

  3. 与你的VM建立一个SSH链接,用于端口转发

    1. 新建一个会话,点击连接选项,主机改为ssh.koding.com
    2. 切换到用户身份验证页,设置好Public Key(SSH Keys)。
    3. 切换到SSH,勾选使用ZLIB压缩不分配终端
    4. 切换到隧道,添加如下转移规则(斜体可修改):

      类型(方向):Local(Outgoing)
      源主机:127.0.0.1
      侦听端口:9527
      目标主机:vm-0.用户名.koding.kd.io
      目标端口:22

    5. 此会话侦听9527端口并转发到指定的主机。

  4. 新建交互会话来登录你的VM。

    1. 新建一个会话,点击连接选项,主机改为127.0.0.1端口号为上一步修改的端口号9527
    2. 切换到用户身份验证页,设置好password

使用Xshell

  • 当过一段时间没有使用VM时,你的终端会被关闭,此时需要从网页中手动打开。
  • 使用时应先打开端口转发会话,连接成功后再打开交互会话
  • 使用SSH有个缺点是每输入一个字符都会向服务器发送数据,因此会导致卡顿,可以打开查看中的撰写栏,在撰写栏中输入命令后再一次性将整行命令发送,可以提高响应速度。

Xshell扩展

使用lrzsz进行文件传输

安装lrzsz

sudo apt-get install lrzsz

从客户端向VM发送文件命令:

rz

此时XShell会打开文件浏览窗口,选择文件后会发送到当前目录下。

从VM获取文件:

sz path

Xshell会打开目录浏览窗口,选择要保存的位置后进行传输。


@LYC
转载请注明出处

2014年3月15日星期六

Python的流重定向要点

起因

今晚在写LYCBot的时候需要实现一个在独立环境中运行代码的模块YCPY,其中涉及到流的重定向问题,特此记录。

使用内存流

Python中没有C#MemoryStream,但是有个类似的StringIO,提供类似的功能。

StringIO是一个类文件对象

StringIO提供了和文件相同的接口。

sys.stdout和sys.stderr使用StringIO进行重定向

代码如下:

sout = StringIO.StringIO()
serr = StringIO.StringIO()
self.stdout = sout
self.stderr = serr

当需要从两者中读取时,使用下列方法即可:

out.getvalue()

sys.stdin使用StringIO进行重定向

与前两者不同,sys.stdin需要读取,因此需要些特殊处理:

sin = StringIO.StringIO()
sys.stdin = sin
sys.stdin.write(input_txt)
sys.stdin.seek(0)

需要对sys.stdin进行一个seek到流起始位置的操作。


@LYC
转载请注明出处

2014年3月8日星期六

网页访问相关库使用总结

url解析相关

统一资源定位符(url)

scheme://domain:port/[path][;params][?query][#fragment]
  1. scheme:协议类型。
  2. domain:域名,如:mrlyc.blogspot.com
  3. port:端口号,默认是80。
  4. path:资源在服务器的逻辑路径,使用/分割。
  5. params:特殊参数,很少见,如:http://en.wikipedia.org/wiki/Robotics;Notes
  6. query:查询字符串。以?字符为起点,每个参数以&隔开,再以=分开参数名称与数据,通常以utf-8url编码,中文以%开头的16进制表示。
  7. fragment:信息段,指定了后会定位到指定id的位置。
注意:url应先编码再合并,先拆分再解码。

urlparse模块

urlparse

参数:

  1. url:指定的url。
  2. [scheme]:指定的协议。
  3. [allow_fragments]:是否允许指定信息段。

接受一个url字符串并将其分割成对应的6个部分,返回一个6元组。结果访问方法有两种,下标访问和属性引用。

url = "http://www.noaurl.com:8080/index.html;params?name=query#main"
p = urlparse.urlparse(url)
print p
print p[1]
print p.netloc

输出:

ParseResult(scheme=’http’, netloc=’www.noaurl.com:8080’, path=’/index.html’, params=’params’,query=’name=query’, fragment=’main’)
www.noaurl.com:8080
www.noaurl.com:8080

urlunparse

参数:

  1. dataurlparse对应的6元组。

urlparse的逆操作,构造一个url字符串。

urlsplit

urlparse,区别是没有对params的处理,params会向前合并,因此不推荐使用。

urlunsplit

urlsplit的逆操作,接受一个5元组,构造一个url字符串。

urljoin

参数:

  1. base:基址。
  2. url:相对地址。
  3. [allow_fragments]:是否允许指定信息段。

将相对地址根据基址成绝对地址。

urllib模块

推荐使用urllib2,因此只简单带过。

urlencode

参数:

  1. query:可以是二元元组或者字典。
  2. [doseq]:对参数值为列表的项是否拆分为多项,默认为False

将数据按照URL中的要求组成一个参数字符串。

data = (("q", 123), ("t", ("r", "w")))
print urllib.urlencode(data)
print urllib.urlencode(dict(data))
print urllib.urlencode(data, True)
print urllib.urlencode(data, False)

输出:

q=123&t=%28%27r%27%2C+%27w%27%29
q=123&t=%28%27r%27%2C+%27w%27%29
q=123&t=r&t=w
q=123&t=%28%27r%27%2C+%27w%27%29

网络访问相关

urllib2快速使用

暂基于python 2.x

quote

参数:

  1. s:待转码字符串。
  2. [safe]:不转码字符,默认不转换/

对url中特殊字符进行转码,如中文、空格和特殊符号。

unquote

参数:

  1. s:待反转码字符串。

quote的反函数,将已转码字符反转码。

urlopen

参数:

  1. url:请求的url,也可以是Request对象,会抛出urllib2.URLError异常。
  2. [data]:请求参数,字典类型。
  3. **timeout:设置超时,单位为秒,超时后抛出socket.timeout异常。

urlopen用于快速打开一个url,根据参数提交方式不同能够选择两种方式:

  • GET:参数位于url之后,data为空。
  • POST:将参数作为data提交。

返回值是一个类文件对象。

Request对象

构造参数:

  1. url:请求的url。
  2. [data]:查询字符串。
  3. [headers]:请求头。
  4. [origin_req_host]:请求的服务的地址,当服务器请求一个网页中的一幅图像时,origin_req_host就是该页面的url。
  5. [unverifiable]:说明请求是否是无法验证的。

参考文档:urllib.request — Extensible library for opening URLs
Request对象是一个url请求的抽象。

urllib2高级应用

Handlers一览

处理器类名 作用
ProxyHandler 处理代理操作
UnknownHandler Raise URLError异常
HTTPHandler 处理HTTP的GET和POST操作
HTTPDefaultErrorHandler 处理HTTP Error的通用处理,所有的响应都会抛出HTTPError异常
HTTPRedirectHandler 处理HTTP重定向操作,如301、302、303等和HEAD请求的307都会执行重定向操作
FTPHandler 处理FTP操作
FileHandler 处理文件
HTTPErrorProcessor 处理非200异常
HTTPCookieProcessor 处理cookie
HTTPBasicAuthHandler 处理Auth
ProxyBasicAuthHandler 处理Proxy和Auth
HTTPDigestAuthHandler 处理DigestAuth
ProxyDigestAuthHandler 处理ProxyDigest
HTTPSHandler 处理HTTPS请求
CacheFTPHandler 比FTPHandler多点功能

ProxyHandler

使用代理来访问网络。
构造参数:

  1. [proxies]:需要代理的协议和地址。
import urllib2
proxy_handler = urllib2.ProxyHandler({"http":"127.0.0.1:8087","https":"127.0.0.1:8087"})

HTTPCookieProcessor

添加对cookies的处理,依赖cookielib库。添加后能从HTTP请求中提取cookies,并在HTTP响应中回发。

import urllib2
import cookielib

cj = cookielib.CookieJar()
cookie_processor = urllib2.HTTPCookieProcessor(cj)

opener

build_opener

生成opener。

opener = urllib2.build_opener(cookie_processor,proxy_handler)

open

使用opener打开网址或请求。

response = opener.open(url)

install_opener

将指定opener装载为全局opener,影响urllib2.urlopen

urllib2.install_opener(opener)

更多资料

Python 标准库 urllib2 的使用细节
python模块学习之—- urllib2模块详解
Python Urllib2使用:代理及其它


@LYC
转载请注明出处

2014年3月3日星期一

哀悼昆明事件,本博客将黑白展示一天

事件追踪

折叠 新窗口打开

博客灰度效果实现技术

使用grayscale.js

项目网站:http://james.padolsey.com/javascript/grayscaling-in-non-ie-browsers/
Demo:http://james.padolsey.com/demos/grayscale/

动态配置

为了博客能够定时切换灰度效果和彩色效果,做了一个能够根据配置和时间切换的脚本。

切换脚本

function checkDateList(){

    function parseDateLstItem(d_str){
        return $.extend(['*','*','*','*','*','*'],d_str.split(/[-:\sT./]+/));
    }

    function parseDateObject(){
        var date = new Date();
        var d_str = date.toISOString();
        return parseDateLstItem(d_str);
    }

    var date = parseDateObject();
    for(var i in Lyc.DateList){
        var pattern = parseDateLstItem(Lyc.DateList[i]);

        for(var j=0;j<6;j++){
            if(pattern[j] == "*") continue;
            if(parseInt(pattern[j],10) != parseInt(date[j],10)) break; //parseInt最好指定基数,否则在ie中遇到0开头的数字会当作八进制转换
        }
        if(j == 6){
            $.getScript("http://james.padolsey.com/demos/grayscale/grayscale.js",function(){
                grayscale(document.body);
            });
            break;
        }
    }
}

配置代码

Lyc = {
    DateList: [
        "2014-3-3",
        "*-5-12",
    ],
}

配置格式说明

  • 时间表示方式顺序按ISO方式表示。

    2014-03-03 06:06:22

  • 分隔符可以使用- . : T / 和空格。

  • *代表通配符,代指一切时间。
  • 时间可以向后缺省,最多表示到秒。

@LYC
转载请注明出处

2014年3月2日星期日

用gae为你的blogger作反向代理

原因

Blogger是个好东西,但是因为一些奇怪的原因,在中国总是访问不了。
偶尔看到一个博文:《利用GAE给Blogger做一个国内可以访问的镜像站》,十分高大上。
因此跟着文章思路瞎琢磨了一番,仅此记录。

bs2grproxy

项目主页:https://code.google.com/p/bs2grproxy/
源代码:http://bs2grproxy.googlecode.com/svn/
工具下载地址:https://code.google.com/p/bs2grproxy/downloads/list
工具备份:http://yunpan.cn/Q4qLULxwErWKm
作者的描述:

How to use
1. Download the source package
2. Modify app name in app.yaml
3. Upload the app
Enjoy

由此看来,这是一个基于GAE的轻量级反向代理程序。

前期步骤

不多赘述,引用Luke在博文的步骤,略修改。

步骤
1. 在GAE中新建立一个空的app,并记录下app名称。
2. 下载资源包 (这部之前你应该已经做到了)
3. 修改app.yaml文件中的app名称:

application: 你的App名称
version: 1-2
runtime: python
api_version: 1

handlers:

- url: /(.*)
script: bs2grproxy.py
secure: optional

下载bs2grproxy-1.2.zip后解压,然后用记事本打开app.yaml文件。其中application:后的斜体字部分就是你需要修改的地方,这里你应该换成自己的app名。

同时Luke建议大家再编辑下bs2grpconfig.py这个文件:

# You can set them later directly in Database
# HTML target url
TARGET_HOST = “你的博客地址”
# Cache check option. value can be ‘EOD’ or 0 <= number.

PS:如果没有配置bs2grpconfig.py,也可以在上传完程序后按以下步骤修改,只是等其生效需要很长很长时间。

  1. 进入相应app的管理页面 - Data - Datastore Viewer - 进入“name=www”项目。
  2. 在页面的最下方修改“target_host”的Value值。
  3. 保存等其生效。

4.上传配置好的bs2grproxy

另,Luke推荐的SDUpload已失效,作者在趣客中有提到。
可以设置secure让APP支持https。secure共有三个属性:

  1. never: 当我们使用https前缀访问时,会自动重定向到http。
  2. always: 当使用http访问时,会自动重定向到https。
  3. ptional: 自由选择,http and https均可使用。

后期的坑

其实原理和前期步骤并不复杂,但是由于自己之前并没有了解GAE,加之SDUpload的失效,因此在上传App的时候十分不顺利,在此记录。

SDUpload的失效

下载了SDUpload,使用SDUpload update ../app/命令,返回302错误代码。
后查得作者博文得知,由于google 已经提供了界面化的上传工具GoogleAppEngineLauncher,所以作者停止了更新。

GoogleAppEngineLauncher的未知错误

SDUpload作者处得知有了GoogleAppEngineLauncher这个工具,因此焦点转到这个工具上。SDUpload作者说已经将这个工具分离出来了,但是貌似地址失效,因此转到GAE官网下载SDK(python版本 备份)。
但是事情并没有那么顺利,用GoogleAppEngineLauncher打开修改后的App,点击Deploy后出了一个窗口,写着如下的东西:

2014-03-02 21:15:14 Running command:
“[‘G:\\xb1\xe0\xb3\xcc\python2.7\pythonw.exe’, ‘-u’, ‘C:\Program
Files\Google\google_appengine\appcfg.py’, ‘–no_cookies’,
u’–email=saber_zodiac@163.com’, ‘–passin’, ‘update’,
‘C:\Users\saber_000\Desktop\bs2grproxy\app’]”

然后就没有反应了,试了好几次也是如此,于是将该列表用空格join了一下,得到如下命令:

G:\编\python2.7\pythonw.exe -u C:\Program Files\Google\google_appengine\appcfg.py --no_cookies --email=saber_zodiac@163.com --passin update C:\Users\saber_000\Desktop\bs2grproxy\app

看来还是需要命令行了。

终于搞掂,命令行

一开始使用命令行执行上面的命令发现没有反应。
于是cdappcfg.py所在目录,直接执行:

appcfg.py --no_cookies --email=saber_zodiac@163.com --passin update C:\Users\saber_000\Desktop\bs2grproxy\app

提示:

Another transaction by user saber_zodiac@163.com is already in progress for app: s~lyc-blog, version: 1-2. That user can undo the transaction with “appcfg rollback”.

哦,那就rollback吧。

appcfg.py rollback C:\Users\saber_000\Desktop\bs2grproxy\app

完了之后,再执行update,终于顺利好了。

大功告成

我的博客http://mrlyc.blogspot.com/ 终于有了个怕被墙的镜像站http://lyc-blog.appspot.com/ 了。


@LYC
转载请注明出处

Logging 模块学习笔记

快速开始

日志级别 权值
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

NOTSET外,logging模块提供了与级别名称对应的小写函数以便快速调用。
样例代码:

import logging

def main():
    logging.critical("critical")
    logging.error("error")
    logging.warning("warning")
    logging.info("info")
    logging.debug("debug")

if __name__ == '__main__':
    main()

输出:

CRITICAL:root:critical
ERROR:root:error
WARNING:root:warning

从输出可以注意到WARNING以下级别的信息并没有打印出来。
原因在于logging默认的级别是WARNING,可以通过logging.basicConfig函数或修改默认Logger对象来改变级别。
默认输出格式为:

级别:Logger对象名:日志

basicConfig函数

basicConfig函数可以快速定制默认Logger对象。

参数名

filename

指定日志文件的名称。

filemode

指定日志文件的打开方式,有wa两个选项,默认为a

format

指定输出的格式和内容,格式为:

%(名称)格式控制字符串

格式列表与其对应的类型

  • levelno s: 打印日志级别的数值
  • levelname s: 打印日志级别名称
  • pathname s: 打印当前执行程序的路径,其实就是sys.argv[0]
  • filename s: 打印当前执行程序名
  • funcName s: 打印日志的当前函数
  • lineno d: 打印日志的当前行号
  • asctime s: 打印日志的时间
  • thread d: 打印线程ID
  • threadName s: 打印线程名称
  • process d: 打印进程ID
  • message s: 打印日志信息

datefmt

指定时间格式,格式同time.strftime,影响formatasctime的格式。

level

设置日志级别,默认为logging.WARNING

stream

指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当streamfilename同时指定时,stream被忽略。

Logger对象

logging.getLogger函数返回一个Logger对象,函数接收一个字符串用以描述希望返回的Logger对象的名称,相同的名称全局唯一对应一个Logger对象。缺省参数为root,返回默认Logger对象。

Logger对象的方法

setLevel

设置该Logger对象的记录级别。
参数:

  1. level:级别枚举

log

打印指定级别的日志,须先设置handler。
参数:

  1. level:级别枚举。
  2. msg:希望打印的对象,会自动转为字符串。如果参数已是字符串,可以是格式字符串。
  3. *args:与msg结合成待打印内容(msg % args)

critical / error / warning / info / debug

指定对应级别的调用log
参数:

  1. msg:希望打印的对象,会自动转为字符串。如果参数已是字符串,可以是格式字符串。
  2. *args:与msg结合成待打印内容(msg % args)。

addHandler

Logger对象添加处理器,可添加多个。
参数:

  1. hdlr:处理器对象。

removeHandler

移除Logger对象的指定处理器。
参数:

  1. hdlr:处理器对象。

日志处理器

处理器用来扩展Logger对象,通过logger.addHandler(hdlr)方法加载到Logger对象。

模块集成处理器对象

  • logging.StreamHandler:日志输出到流,可以是sys.stderrsys.stdout或者文件。
  • logging.FileHandler:日志输出到文件。
  • logging.handlers.SocketHandler:远程输出日志到TCP/IP sockets。
  • logging.handlers.DatagramHandler: 远程输出日志到UDP sockets。
  • logging.handlers.SMTPHandler: 远程输出日志到邮件地址。
  • logging.handlers.SysLogHandler:日志输出到syslog。
  • logging.handlers.NTEventLogHandler:远程输出日志到Windows NT/2000/XP的事件日志。
  • logging.handlers.MemoryHandler:日志输出到内存中的制定buffer。
  • logging.handlers.HTTPHandler:通过GETPOST远程输出到HTTP服务器。
  • logging.handlers.BaseRotatingHandler:日志回滚。
  • logging.handlers.RotatingFileHandler:日志回滚,实际用。
  • logging.handlers.TimedRotatingFileHandler:日志回滚,实际用。

处理器使用简示

logging.StreamHandler

logging.StreamHandler对象构造参数缺省为sys.stdout,参数可以接收一个流对象(sys.stderrsys.stdout或者文件)。

sh1 = logging.StreamHandler()
sh2 = logging.StreamHandler(sys.stdout)
sh3 = logging.StreamHandler(open(file_path,"w")) # w 和 a 皆可

logging.StreamHandler 对象能够设置记录的日志级别,但实际输出受Logger对象的级别限制。Logger对象的级别制约着消息的派发,Handler对象的级别制约着记录。

sh2 = logging.setLevel(logging.WARING)
sh3 = logging.setLevel(logging.INFO)

logging.StreamHandler 对象能够自定义输出格式。

fmt = logging.Formatter("%(name)s:%(levelname)s:%(message)s")
sh1.setFormatter(fmt)

更多资料


@LYC
转载请注明出处