1
6
2017
3

如何快速高效地修 bug?

看到知乎上的一个问题,心血来潮,随意写写,请读者不要太较真。

看回答,有一些可操作性很强的答案。但是呢,你知道的,考试好不代表能力强,如果你只是学习别人的方法而并不理解,那么学来之后只会是东施效颦而不能融会贯通。所以呢,我也来发表一下自己的见解。

首先,你要定位 bug。这时,你需要:

  1. 注重逻辑性。不要做没有证据的结论。如果你有猜测,就去证实或者否定它。比如某次,同事代码返回的数据有问题,认为是缓存用的 Redis 有问题,返回了错误的数据。然而没人去对此猜测进行求证……我去确认了一下,Redis 收到了请求,并且响应正常。接下来,排除所有其它可能的原因之后,最后剩下的那个就是真相。真相就是,代码里有个 } 的位置放错了,因为它刚好在一屏之后的位置,所以没有人发现……(是 Vim 帮我找到的)
  2. 基本的方法论。比如二分法。比如最小化测试用例。如果你要提问,要懂得提问的智慧,不管是向搜索引擎还是向人,你都需要提出正确的问题
  3. 知识面。你写 Web 后端的话,普通的 HTTP 得懂,浏览器的开发者工具得会用。简单的 JavaScript 也有会点儿。简单地说就是,你要精于你自己主攻的部分,然后要熟悉你的上下游。再比如如果你使用 CPython 的话,你要准备一份 CPython 的源码,并且要能够流畅地阅读 C 代码。
  4. 工具。工欲善其事,必先利其器。一大堆调试用的工具,你至少得知道它们能干什么,需要的时候能用。比如 strace、lsof、gdb、git bisect,还有高级点的 sysdig、systemtap、perf 等等。当然还有一堆不是专门为调试而设计的通用工具,比如 the silver searcher 或者 ripgrep。一个快速的全文搜索工具能帮你在最短时间内找到相关的代码或者日志。你不必成为正则表达式大师,但是简单的一定要会,不然面对上千个匹配结果你要怎么办呢?Vim 有一个插件 Mark,能够同时高亮多个模式,非常利于调试期间阅读代码和日志。投入时间学习使用高效的工具,不要把时间浪费在等待和人工搜索上,也不要让自己忙于琐事而断了灵感和线索。

最后,不要不断地、毫无目的地换个环境啦,换个版本啦,换个用户啦,这样子找问题。如果这样做很有效的话,大家都去买彩票去了。

找到 bug 之后,理解它是如何产生的。当你理解之后才能真正修好它。就像你感冒了吃抗生素,根本没有用。

Category: 编程 | Tags: 编程 软件开发
1
5
2017
17

我使用的 Xposed 模块

开始使用 Xposed 之后,我对我的手机又多了一份拥有感,然后呢,装的模块越来越多了。以下是我正在使用的模块的列表,以及我为什么使用它们。链接我就懒得放了,想要安装的读者可以自己去 Xposed Installer 里搜。

绿色守护。装上这个我才敢装各种国产应用。它的 Xposed 模块用于功能增强,最重要的一点是,它可以切断唤醒途径。这样就不会总也杀不死那些耗电又耗流量的应用们了。

去你大爷的内置浏览器。我发现 Android 6 里,好多 Google 家的应用都开始默认使用内置浏览器了呢……不过国外的应用一般都是可以选择在内置浏览器里打开,还是外部浏览器里打开的。我更喜欢在外置浏览器里打开,一来减少相同的缓存和数据文件,节约存储空间,因为缓存共享而加快加载速度,二来能够使用自己的配置(比如广告拦截),并且能够使用最新的浏览器特性。上次听说了一个很棒的浏览器特性来着,然后就有人告诉我微信里不支持……当然了,微信这种应用,为它的内置浏览器增加了接口,有些网页必须在它里边打开。所以这个模块是有白名单的。

微信防撤回模块。顾名思义啦。

Android通话振动。Android 4.0.4 有一个很贴心的功能:在拨出电话接通时,它可以振动一下,告诉用户已经接通了。所以就不用一直把电话放在耳边等着啦,有时候等着等着,因为某些原因呼叫终止了还傻傻地等着……不知道为什么,后来的版本就没有这个功能了。还好我们有 Xposed。这个模块不仅把接通时振动给加回来了,还可以挂断时振动,以及如果对话费敏感的话,可以定时振动。

App Settings。目前我用来强制QQ轻聊版出现在最近使用的应用列表中。以后还可能需要强制某些中文翻译拙劣的应用使用英文语言。

Battery Stats Plus。这是一个同名应用带的模块。用于电池使用统计。

Xposed Torch。不需要解锁屏幕然后点来点去的手电筒。在锁屏状态下长按音量上键就可以启动了,再按一下音量下就关闭了。方便好用~

XPrivacy。权限管理。也是使用国产应用时必备的功能。虽然 Android 6 里,很多权限都像 iOS 那样在运行的时候询问了。但是呢,流氓总有流氓的手段,你要拒绝授权?那好,一切功能免谈,你卸载我吧。

XuiMod。我用来让右上角的时间显示秒数的。

哇已经装了九个模块了呢。其实我是不希望用这么多 Xposed 模块的,毕竟是打补丁嘛。可是呢,毕竟不是自由软件,只能这样了。也幸好我们还能打补丁。

Category: Android | Tags: Android Xposed
1
2
2017
3

在 Android 上运行 sshd

新的 Z5C 到手。拿 root 装软件。然后发现一个很重要的事情:我之前在 Z3C + Android 4.4.4 上用得好好的 Rooted SSH/SFTP Daemon,在登录的时候报了这么个错:

CANNOT LINK EXECUTABLE: "/system/lib/libc++.so" is 32-bit instead of 64-bit
page record for 0xXXXXXXXXXX was not found (block_size=32)

网上搜了一下,解决方法是有的,要重新编译 dropbear。可我之前研究过,我这软件使用的 dropbear 是一个修改版,和我用的这个 app 一样,好久没更新了……

于是想找个新的 sshd。之前我是使用的 SSHDroid。后来它需要付费版才能使用密钥认证了……而我的要求就两点:可以以 root 身份登录,并且支持密钥认证。在 Play 商店里能找到的 sshd 我都试过了一遍,竟然没一个能满足这么基本的需求的…………

之所以需要以 root 身份登录,而不是登录之后再获取 root,是因为跑命令时 su 之后很容易出现奇怪的问题,和缓冲、终端控制有关。

于是我只好失望地放弃使用 app,转到自己熟悉的领域——Linux 系统,编译一个 sshd adb 进去跑好了。

一开始使用的是 socat + tinyssh 的方案。这个方案我之前在光猫上实现过,tinyssh 的代码很少,很容易编译和修改。socat 直接用之前编译的版本就可以了。因为 Android 毕竟不是完整的 Linux userland,所以得把 tinyssh 改一改,主要是用户主目录和默认 shell 的部分。我就直接硬编码进 root 的配置了。然后写个 shell 脚本来启动:

#!/system/bin/sh

export ANDROID_ROOT=/system ANDROID_DATA=/data
PATH=/system/xbin:/su/xbin:/su/bin:/sbin:/vendor/bin:/system/sbin:/system/bin
socat tcp-l:PORT,reuseaddr,fork exec:'tinysshd /data/tinyssh/keydir' &

这样就可以了。只支持 Ed25519 密钥登录,挺好的。

然而,用着用着就发现有点小问题:socat 对经由网络的数据进行转发,有点低效;tinyssh 不支持连接复用,在一个会话中收到新的连接请求时会直接退出;还没有 scp 命令……

一开始我去 dropbear 那边编译了一个 scp。编辑好配置文件、开始 make 的时候,敲「make PROGRAMS=scp」就可以编译出一个 scp 命令了。然后我就想,既然都用上 dropbear 了,要不就都用了吧。于是把 dropbear 也编译出来了。不是很顺利,主要是以下几个事:

  • 改路径。各种路径,host key 的,pid 文件的,默认 PATH,还要禁用掉 lastlog 和 syslog 什么的
  • 改用户信息。默认 shell、主目录。不要检查 /etc/shells。刚刚发现我还不小心把其中两行代码交换了,是说怎么退出的时候会段错误呢 _(:з」∠)_
  • dropbear 的构建系统不支持 out-of-tree 构建,也就是不能像我习惯的那样,「mkdir build-android」然后进去「../configure」 :-(

另外就是,dropbear 不支持 Ed25519 key,于是我只好用 RSA key 了(DSS 有问题;openssh 的 ECDSA 实现也有问题) :-(

弄好之后同样写个 shell 脚本方便调用:

#!/system/bin/sh

export ANDROID_ROOT=/system ANDROID_DATA=/data
/system/xbin/dropbear -R -p PORT

然后,启动服务的事情。我发现改 /init.rc 不管用。这个是 initramfs,每次重启之后就恢复原状了……我懒得去重新打 initamfs 的包了,就每次重启系统之后接上 USB 线,然后 adb shell 进去跑脚本……还好 Z5C 跟 Z3C 不一样,USB 口在外边,很好插。

终于把 remote root shell 弄好了,接下来就是各种 rsync 和 scp 传文件改配置什么的了,一是复制各种软件的配置文件和数据,二是备份,三是把文件拿电脑上研究、编辑,方便啊!Sony 有个「换机助手」软件,但是它不能在加密了的手机上使用……

最后,还留下了一个问题:同样的环境,同样是 Wi-Fi 传输,我的电脑和 Z3C 之间传输速度能达到 4MiB/s,但是 Z5C 却只有 300KiB/s 左右的样子……

Category: Android | Tags: ssh Android 交叉编译
12
22
2016
9

利用 systemd 的 watchdog 功能重启卡住的服务

我在用 offlineimap。用着用着就发现一个问题:偶尔 offlineimap 会卡在网络上不动弹了。跟 getmail 一个德性……

但是 offlineimap 又跟 getmail 有点不一样,它是持续运行着的。虽然非要把之前那个 killhung 程序拿来用不是不可以,但我还是重新弄了一个更优雅的方案:systemd watchdog。

我的 offlineimap 本来就是用 systemd 服务的方式来跑的,所以很适合这样的改造呢。只是,当我瞅了一眼源码之后,我就放弃了 patch offlineimap 的打算。很难在合适的地方添加 watchdog 相关的代码。

既然从内部着手不好做,那就从外部写一个 wrapper 好了,反正 offlineimap 跟 getmail 不一样,正常情况下一直在输出东西,就把这个作为它的「心跳」特征好了。当然这个 wrapper 还可以给其它程序用。

于是,watchoutput 程序诞生了!稍微改一下 offlineimap 的 .service 文件,像这样子就好了:

[Unit]
Description=Offlineimap Service

[Service]
Type=notify
ExecStart=.../watchoutput /usr/bin/offlineimap
TimeoutStopSec=3s
SyslogIdentifier=offlineimap
Restart=on-failure
WatchdogSec=70
LimitCORE=0

[Install]
WantedBy=default.target

加上LimitCORE=0是为了阻止重启的时候由于 SIGABRT 信号导致 coredump,浪费磁盘空间。

用了几天之后,终于观察到一次由 watchdog 触发的重启:

12月 19 12:26:53 lilywork offlineimap[21623]:  Establishing connection to imap.exmail.qq.com:993 (main-remote)
12月 19 12:28:03 lilywork systemd[687]: offlineimap.service: Watchdog timeout (limit 1min 10s)!
12月 19 12:28:03 lilywork systemd[687]: offlineimap.service: Killing process 21623 (python3) with signal SIGABRT.
12月 19 12:28:03 lilywork systemd[687]: offlineimap.service: Killing process 21625 (offlineimap) with signal SIGABRT.
12月 19 12:28:03 lilywork systemd[687]: offlineimap.service: Main process exited, code=dumped, status=6/ABRT
12月 19 12:28:03 lilywork systemd[687]: offlineimap.service: Unit entered failed state.
12月 19 12:28:03 lilywork systemd[687]: offlineimap.service: Failed with result 'core-dump'.
12月 19 12:28:03 lilywork systemd[687]: offlineimap.service: Service hold-off time over, scheduling restart.
12月 19 12:28:03 lilywork systemd[687]: Stopped Offlineimap Service.
12月 19 12:28:03 lilywork systemd[687]: Starting Offlineimap Service...
12月 19 12:28:04 lilywork systemd[687]: Started Offlineimap Service.

没过几天,我又给这个 watchoutput 的脚本找到另外的用处:自动重启网络。

我家里的笔记本连 Wi-Fi 不知怎么,这些天经常会卡住(只发不收,一直处于 ARP 找网关的状态)。内核之前报过一次错,现在也没反应了。

于是:

[Unit]
Description=Watch for network availability

[Service]
Type=notify
ExecStart=.../watchoutput --retry-on-exit 2 --wait-before-retry 30 --ignore-stderr \
    -- ping -i 30 192.168.1.1
Restart=on-failure
WatchdogSec=70
StandardOutput=null
StandardError=journal
LimitCORE=0
SyslogIdentifier=watch-network

[Install]
WantedBy=default.target

拿 watchoutput 监控 ping 网关的输出,每30秒 ping 一次,如果70秒还没反应就重启它自己。然后我们还需要重新连接网络。在 /etc/systemd/system 下建立 netctl-auto@wlan0.service.d 目录,并在其下建立一个 watchdog.conf 文件,给 netctl-auto@wlan0.service 服务增加一项配置:

[Unit]
PartOf=watch-network.service

这样当 watch-network.service 重启的时候,netctl-auto@wlan0.service 就会自动重启了~

Category: Linux | Tags: linux systemd
12
14
2016
0

使用 RSS 订阅知乎用户的动态

之前做了一个知乎专栏转RSS的网关,这次又写了个针对知乎动态的。感兴趣的话去 https://rss.lilydjwg.me/ 看看用法吧。

此功能只支持用户的回答和发布文章两个操作。别的操作,比如赞了答案啦,关注了专栏啦,参加了 live 啦,没有实质性的内容,又可能会有非常多,不适合 RSS 这种面向内容发布的东西,所以就过滤掉了。即使如此,程序每次访问取最近40条动态,缓存有好几个小时,所以对于活跃的用户是可能漏掉一些动态的。反正现在信息这么多,漏了就漏了吧,去读读别的东西呗。

知乎这次暴露出来的 API 有点意思。它有一个 include 参数,看起来是指定要返回哪些字段的。看起来知乎也在解决 GitHub 遇到的同样的问题:RESTful API 太不灵活了。只是,为什么要造轮子啊,GraphQL 不是挺好的吗……

Category: 网络 | Tags: rss 知乎
11
10
2016
14

数据让 git 给吃了!

之前一直觉得 git 是很安全的,除非用户显式指定(比如 --force 啦,reset --hard 啦,checkout xxxx 啦),git 在用户会失去数据时都会停下来,让不小心的用户有机会处理被遗忘的修改。直到有一天,我们有个文件让 git 给吃了!

嗯,是「我们」,不是「我」。这是我们的代码部署服务器上出的事。这仓库不是我使用的,整个操作流程我也没有参与设计与评估。实际上我只是作为 troubleshooter 参与到这次神秘事件之中的。

要让 git 愉快地吃掉数据,只要这样就可以了:

  • 提交 A 不包含文件 f
  • 提交 B 包含文件 f
  • 当前工作区为提交 A,并且包含一份未被 git 管理的文件 f,并且 f 被 gitignore 忽略掉了

然后做如下操作,未被管理的那份 f 就会消失不见了:

  • 将工作区切换到提交 B。因为 f 被忽略,所以 git 不会报错(代码
  • 将工作区再切换回 A。因为 A 不包含 f,所以 f 被删掉了

正在吃 f 的 git:主人遗弃了的 f 就交给我好了~

要避免出现这种问题,当然是在 git 工作区会有修改的时候,不要依靠 git 来在多个版本间切换啦~btrfs 或者 zfs 的快照多好!如果文件系统不支持快照的话,那就用多个目录吧。

Category: 版本控制 | Tags: linux Git
11
3
2016
16

诡异多多的 bash

要说哪个 shell 最复杂难学,我肯定回答 zsh。而要说哪个 shell bug 最多,毫无疑问是 bash 了。shellshock 这种大家都知道的我就不说了。bash 有很多很诡异的角落,昨天我亲身碰到一个。

我有一个 Python 程序 A,会使用 subprocess 带 shell=True 跑一行 shell 命令。那条命令会在后台跑另外一个 Python 程序 B。诡异的事情是,当我向 B 的进程发送 SIGINT 时,无法结束它,以及它下边带的一个 tail 进程。一开始我还没注意到 B 的进程本身没有被 SIGINT 杀死,是在无效的情况下被 A 用 SIGKILL 杀死的。我只看到那个 tail 程序还活着。所以我去处理了一下 KeyboardInterrupted 异常,来结束掉那个 tail。

结果很诡异:KeyboardInterrupted 异常并没有发生。通过 strace 观察可以看到,B 进程在读 tail 的输出,然后收到了 SIGINT,然后接着读 tail 的输出……我一开始还以为这个和 PEP 475 相关,以为是 Python 自动重启了被中断的系统调用,所以没来得及处理信号(Python 的信号并不是及时处理的)。然后就去仔细看文档。结果文档告诉我,如果注册了信号处理函数,并且它抛出异常的话,那么被中断的系统调用是不会被重试的。所以这就不对了。

然后我又测试了直接在终端运行 B,而不是通过 A 去运行。本来我开发的时候就是这么测试它的,也没遇到什么怪异的现象。结果确实没有什么怪异的事情发生:即使我使用 kill 命令只给 B 发送 SIGINT 信号,Python 的 KeyboardInterrupted 逻辑会被触发,然后它主动杀掉 tail 进程。(使用 Ctrl-C 的话,B 和 tail 都会收到 SIGINT 信号的。)

疑惑的时候,我又想到了拿 SIGINT 去杀那个不死的 tail 进程,这才发现它也出现奇怪的行为了:正在读 inotify 的文件描述符呢,来了个 SIGINT 信号,然后它接着读 inotify 去了……跟 B 出现的问题一样。我又去查了 tail.c 的源码,也没发现它对 SIGINT 有特殊的处理啊。

难道是继承过来的?man 7 signal 了一下,果然:

During an execve(2), the dispositions of handled signals are reset to the default; the dispositions of ignored signals are left unchanged.

所以 tail 和 B 继承了一个「忽略 SIGINT」的行为。(nohup 就是用的类似的手段啊。)

于是 strace -f 了整个从 A 开始的进程树,最后发现这问题和 Python 并没有什么关系,而是 bash 的错!

A 是用 shell=True 调用的命令,所以它调用了 /bin/sh。系统是 CentOS,所以 /bin/sh 是指向 bash 的。所以这里实际上调用了 bash,而它的处理有问题。

要重现这个 bug 很容易:

bash -c 'sleep 1000 &'

然后这个 sleep 进程就会忽略 SIGINT 和 SIGQUIT 了。我也不明白 bash 这是想要做什么。

之前也遇到过另外几个 bash 的 bug(或者是 feature?)——

  1. 在终端中,在脚本中执行交互式 bash 时,第一个 bash 进程会将自己设为前台进程组,导致后来的进程收到 SIGTTIN 或者 SIGTTOU。很神奇,两行同样的命令,第一条和后边的行为不一致

  2. 在 bash 中,执行不带 shebang 的 shell 脚本时,脚本会在当前 bash 进程内执行,造成 history 命令的行为异常

  3. 这个是听说的。输出失败时,未写入目标的内容仍留在缓冲区内,会在奇怪的地方冒出来

以后还是尽量避开 bash 吧。有 zsh 用 zsh,有 dash 用 dash;它们都没有本文提到的这些问题。

Category: Linux | Tags: shell bash
10
21
2016
0

在 Python 里设置 stdout 的编码

有时候进程的运行环境里,locale 会被设置成只支持 ASCII 字符集的(比如 LANG=C)。这时候 Python 就会把标准输出和标准错误的编码给设置成 ascii,造成输出中文时报错。

一种解决办法是设置支持 UTF-8 的 locale,但是那需要在 Python 进程启动前设置。启动之后,初始化过了,再设置 locale 也不会重新初始化那些对象。

另一种办法是往 sys.stdout.buffer 这种地方直接写 bytes。理论上完全没问题,但是写起程序来好累……

我就去找了一下怎么优雅地弄一个新的 sys.stdout 出来。Python 3 的 I/O 不再使用 C 标准库的 I/O 函数,而是直接使用 OS 提供的接口。封装位于 io 这个模块里边,有带缓冲的,不带缓冲的,二进制的,文本的。

研究了一下文档可知,sys.stdout 是个 io.TextIOWrapper,有个 buffer 属性,里边是个 io.BufferedWriter。我们用它造一个新的 io.TextIOWrapper,指定编码为 UTF-8:

import sys
import io

def setup_io():
  sys.stdout = sys.__stdout__ = io.TextIOWrapper(
    sys.stdout.detach(), encoding='utf-8', line_buffering=True)
  sys.stderr = sys.__stderr__ = io.TextIOWrapper(
    sys.stderr.detach(), encoding='utf-8', line_buffering=True)

这里除了可以设置编码之外,也可以设置错误处理和缓冲。所以这个技巧也可以用来容忍编码错误、改变标准输出的缓冲(不需要在启动的时候加 -u 了)。

其实这样子还是不够彻底。Python 在很多地方都有用到默认编码。比如 subprocess,指定 universal_newlines=True 时 Python 会自动给标准输入、输出、错误编解码,但是呢,在 Python 3.6 之前,这里的编码是不能手动指定的。还有参数的编码,也是不能指定的(不过可以传 bytes 过去)。

所以,还是想办法去设置合适的 locale 更靠谱……

Category: python | Tags: Python 中文支持 linux
9
13
2016
7

Linux 下的 Wi-Fi 分享

首先看看你的网卡和驱动组合是否支持这样的操作。

>>> iw list | grep -A2 combinations:
        valid interface combinations:
                 * #{ managed } <= 1, #{ AP, P2P-client, P2P-GO } <= 1, #{ P2P-device } <= 1,
                   total <= 3, #channels <= 2

上边这个输出说明支持,并且频道可以不一样。

然后,添加一个用途 AP 的网络接口,并配置 IP 地址。我的无线网络接口名字是 wlan0,因为我通过创建空 /etc/udev/rules.d/80-net-setup-link.rules 文件的方式禁用了 systemd 的网络接口改名。

sudo iw dev wlan0 interface add wlan0_ap type __ap
sudo ifconfig wlan0_ap 192.168.17.1

配置 NAT:

echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
sudo iptables -w -t nat -A POSTROUTING -s 192.168.17.0/24 -j MASQUERADE

配置 DHCP。我用的是 dnsmasq。它本来是作为 DNS 缓存用的,但是也支持 DHCP,那就用它了:

interface=wlan0_ap
no-dhcp-interface=wlan0
dhcp-range=192.168.17.50,192.168.17.150,12h

注意不要在其它只提供 DNS 服务的接口上提供 DHCP 服务,以免出现冲突。

然后就可以开启热点啦。hostapd 配置如下:

interface=wlan0_ap
driver=nl80211
ssid=名字
channel=1
hw_mode=g
ieee80211d=1
country_code=cn
ieee80211n=1
ieee80211h=1
ignore_broadcast_ssid=0
auth_algs=1
wpa=2
wpa_passphrase=secret
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

最后把它们跑起来就可以了。

为了方便使用,我创建了个 systemd 服务 wlan0_ap.service:

[Unit]
Description=Setup wlan0_ap
Before=hostapd.service
After=sys-subsystem-net-devices-wlan0.device
After=iptables.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/iw dev wlan0 interface add wlan0_ap type __ap
ExecStart=/usr/bin/ip address add dev wlan0_ap 192.168.17.1/24
ExecStart=/usr/bin/iptables -w -t nat -A POSTROUTING -s 192.168.17.0/24 -j MASQUERADE
ExecStop=-/usr/bin/iptables -w -t nat -D POSTROUTING -s 192.168.17.0/24 -j MASQUERADE
ExecStop=/usr/bin/ip address delete dev wlan0_ap 192.168.17.1/24
ExecStop=/usr/bin/iw dev wlan0_ap del

[Install]
WantedBy=hostapd.service

systemctl enable wlan0_ap 之后就可以直接 systemctl start hostapd 来启动了~当然也很容易停止服务:systemctl stop hostapd wlan0_ap。我的 dnsmasq 总是开启的,所以就不用加依赖了。还有 ipv4_forward 我也是早就写到配置文件 /etc/sysctl.d/99-sysctl.conf 里的。

Category: Linux | Tags: linux 网络 systemd
9
10
2016
0

Jupyter + matplotlib = ♥

matplotlib 是很不错的数据可视化库,然而每次写一个脚本,跑出来看完又回头改,改完再跑,实在是累。所以就有 IPython Notebook 啦,后来改名叫 Jupyter 了,不光支持 Python,还支持 Julia 什么的样子(在下一盘很大的棋呢)。

Arch Linux 用户使用以下命令安装:

sudo pacman -S jupyter-nbconvert jupyter-notebook

我没有装 mathjax 这个包。我就用 MathJax 官方的 CDN 地址好了。所以我的启动命令是这样子:

jupyter notebook --NotebookApp.mathjax_url=https://cdn.mathjax.org/mathjax/latest/MathJax.js

然后界面就会在浏览器里打开啦~

Jupyter notebook 最令我不爽的一点是,它的编辑区用起来很不习惯: 不支持 readline 式快捷键(就是 Emacs / bash 风格那些啦),不支持选中复制、中键粘贴 * 不支持补全

我尝试过配置快捷键,但是还是不太会的样子,好像又没有现成而且可用的代码。

不过它的可视化和交互能力实在是太吸引人了~所以做一些交互式的数据处理时还是用用好了~

这里是演示。(当然只是导出的 HTML 页面~)

Category: python | Tags: python 数据分析

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com