Instagram 的两种ID

Instagram 在他的API文档和网站地址栏中对同一个 Post,使用了两种不一样的ID。在他们的 API 文档中,ids 类似于 908540701891980503_1639186 , 但是在网站浏览器中则使用的是 ybyPRoQWzX 这样的ID。如果查看几组 ids,能够确性这两种类型的 ids 之间存在关联,Instagram 内部也不可能为同一个帖子存储两套 ids。

在仔细看一下数字的ID,908540701891980503 显然下划线分割的后面为作者的id,前面18位的id 被转为 ybyPRoQWzX 10 位的长度。URL 中的ID信息的密度显然要比数字的id要高。

如果做一个测试,将数字id由base10转为 base64,就得到

9:0:8:5:4:0:7:0:1:8:9:1:9:8:0:5:0:3_10
50:27:50:15:17:40:16:22:51:23_64

这样两组数据,如果将 base64 的数组和10位的id比较,得到

number letter
50 y
27 b
50 y
15 P
17 R
40 o
16 Q
22 W
51 z
23 X

如果对该映射重新排序,则得到

number letter
15 P
16 Q
17 R
22 W
23 X
27 b
40 o
50 y
50 y
51 z

看到该映射,很容易联想到由 A-Za-z 的关系

number letter
00 A
01 B
02 C
03 D
07 H
08 I
09 J
10 K
11 L
13 N
15 P
16 Q
17 R
20 U
22 W
23 X
24 Y
26 a
27 b
29 d
32 g
34 i
36 k
37 l
40 o
42 q
43 r
47 v
50 y
51 z
56 4
62 -
63 _

这个表和标准 base64 的加密表几乎一致,除了将 +/ 变为了 -_ 。这个变动大概因为 / 在URLs中是特殊字符,而 + 号在 URL 中是特殊的请求字符。

启示

知道了两种ids之间的关系,联系一些已知的事实可以知道

唯一性

Instagram 在全平台使用唯一的ID,在 Web 端是 https://instagram.com/p/ybyPRoQWzX , 而这个 ID 能够被转成数字ID,因此我们也知道这个 ID 也是唯一的。

更加惊奇的是数字ID也能够被用于请求 Instagram 内部的接口,比如查看 Instagram 网站的请求,可以发现请求特定用户的 Posts 的接口为

https://instagram.com/<username>/media/?max_id=<numeric id>

比如说 https://instagram.com/gitamba/media/?max_id=915362118751716223_7985735 而事实上,省略了后面的用户ID 也能更能够照样获得结果。而后面加任意内容都会被忽略

https://instagram.com/gitamba/media/?max_id=915398248830305252_whatever

这个请求的到内容和之前的内容一样。

进一步分析

如果知道 Instagram 产生 posts 的唯一ID 的原理 那么可以从 ID 中获取更多信息。在 Instagram 的文章中,没有提及内部的 epoch 是什么时候,但是可以通过计算来获取一个比较准确的值。

post id (base 10) known post created time (unix time)
908540701891980503 1422526513s
936303077400215759 1425836046s
188449103402467464 1336684905s

上面是三个已知发布时间的IDs, post 的id 是64bit ,如果转成 64 bits

post id (base 10) post id (base 2)
908540701891980503 0000110010011011110010001111010001101000010000010110110011010111
936303077400215759 0000110011111110011010101011000000101010100010111000000011001111
188449103402467464 0000001010011101100000010111011000001010100010111000000010001000

截取前41bits,表示的从 Instagram 内部 epoch 开始流逝的时间,毫秒

first 41 bits of post id time since Instagram epoch
00001100100110111100100011110100011010000 108306491600ms
00001100111111100110101010110000001010101 111616024661ms
00000010100111011000000101110110000010101 22464883733ms

最后在用创建post 的时间 unix time 来减去从 id 中获取的时间,得到

created time time since Instagram epoch Instagram epoch (unix time)
1422526513s 108306491600ms 1314220021.400s
1425836046s 111616024661ms 1314220021.339s
1336684905s 22464883733ms 1314220021.267s

如果计算一切都正确,得到的值应该是相等的。微小的误差来自,创建 post 的时间是秒,而 id中获取的时间是毫秒。

经过一系列计算,大致可以知道 Instagram epoch 时间开始于 1314220021 ,也就是 9:07pm UTC on Wednesday, August 24, 2011 看起来是一个随机的 epoch,但猜测可能是 Instagram 内部将 id 由自增长模式转变为现在模式的时间。

ids for testing

id in base 10 id in base64 converted to chars
936303077400215759 51:62:26:43:00:42:34:56:03:15 z-arAqi4DP
908540701891980503 50:27:50:15:17:40:16:22:51:23 ybyPRoQWzX
283455759575646671 15:47:02:24:11:51:02:56:07:15 PvCYLzC4HP
205004325645942831 11:24:20:37:36:23:34:56:00:47 LYUlkXi4Av
188449103402467464 10:29:32:23:24:10:34:56:02:08 KdgXYKi4CI
409960495 24:27:56:00:47 Yb4Av
167566273 09:63:13:47:01 J_NvB

翻译自: https://carrot.is/coding/instagram-ids


2017-11-13 id , instagram , decode , reverse

每天学习一个命令: iperf 带宽

iperf命令是一个网络性能测试工具。iperf可以测试TCP和UDP带宽质量。iperf可以测量最大TCP带宽,具有多种参数和UDP特性。iperf可以报告带宽,延迟抖动和数据包丢失。利用iperf这一特性,可以用来测试一些网络设备如路由器,防火墙,交换机等的性能。

安装

apt-get install iperf

实例

宽带测试通常采用 UDP 模式,首先以链路理论带宽作为数据发送速率进行测试,从客户端到服务器之间的链路理论带宽为 100 Mbps,先使用 -b 100M 测试,然后根据测试结果,以实际带宽测试

服务端:

iperf -u -s # UDP 模式

客户端第一种模式

iperf -u -c ip -b 100M -t 60

在 UDP 模式下,以 100Mbps 为数据发送速率,客户端到 IP 上传带宽测试,测试时间60秒。

客户端同时发起30个线程连接,以5Mbps为数据发送速率

iperf -u -c ip -b 5M -P 30 -t 60

或者客户端直接进行上下行带宽测试

iperf -u -c ip -b 100M -d -t 60

如果不加 -u 则使用 TCP 模式

iperf -s

客户端

iperf -c ip -t 60

2017-11-11 linux , iperf , network , bandwidth

分布式系统中唯一 ID 的生成方法

在分布式系统存在多个 Shard 的场景中, 同时在各个 Shard 插入数据时, 怎么给这些数据生成全局的 unique ID? 在单机系统中 (例如一个 MySQL 实例), unique ID 的生成是非常简单的, 直接利用 MySQL 自带的自增 ID 功能就可以实现.

但在一个存在多个 Shards 的分布式系统 (例如多个 MySQL 实例组成一个集群, 在这个集群中插入数据), 这个问题会变得复杂, 所生成的全局的 unique ID 要满足以下需求:

  • 保证生成的 ID 全局唯一
  • 今后数据在多个 Shards 之间迁移不会受到 ID 生成方式的限制
  • 生成的 ID 中最好能带上时间信息, 例如 ID 的前 k 位是 Timestamp, 这样能够直接通过对 ID 的前 k 位的排序来对数据按时间排序
  • 生成的 ID 最好不大于 64 bits
  • 生成 ID 的速度有要求. 例如, 在一个高吞吐量的场景中, 需要每秒生成几万个 ID (Twitter 最新的峰值到达了 143,199 Tweets/s, 也就是 10万+/秒)
  • 整个服务最好没有单点

在要满足前面 6 点要求的场景中, 怎么来生成全局 unique ID 呢?

数据库自增ID

数据库单表,使用 auto increment 来生成唯一全局递增ID。

优势是无需额外附加操作,定长增长,单表结构中唯一性,劣势是高并发下性能不佳,生产的上限是数据库服务器单机的上限,水平扩展困难,分布式数据库下,无法保证唯一性。

UUID

如果没有上面这些限制, 问题会相对简单, 例如: 直接利用 UUID.randomUUID() 接口来生成 unique ID (http://www.ietf.org/rfc/rfc4122.txt). 但这个方案生成的 ID 有 128 bits, 另外, 生成的 ID 中也没有带 Timestamp 一般编程语言中自带 UUID 实现, Java 中 UUID.randomUUID().toString() 产生的ID 不依赖数据库实现。

优势是,本地生成ID,无需远程调用,全局唯一,水平扩展能力好。劣势是,ID 有 128 bits 长,占空间大,生成字符串类型,索引效率低,生成的 ID 中没有带 Timestamp 无法保证时间递增。

Flickr 全局主键

Flickr 的做法1 是使用 MySQL 的自增ID, 和 replace into 语法。但他这个方案 ID 中没有带 Timestamp, 生成的 ID 不能按时间排序

创建64位自增ID,首先创建表

CREATE TABLE `Tickets64` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `stub` char(1) NOT NULL default '',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM

SELECT * from Tickets64 假设表中有一行

+-------------------+------+
| id                | stub |
+-------------------+------+
| 72157623227190423 |    a |
+-------------------+------+

那么如果需要产生一个新的全局 64 bits 的ID,只要执行 SQL:

REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();

SQL 返回的ID就是要产生的全局唯一ID。使用 REPLACE INTO 代替 INSERT INTO 的好处是避免表行数太多。 stub 要设为唯一索引。

Flickr 内部运行两台 ticket servers,通过两台机器做主备和负载均衡。

TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1

TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2

Twitter Snowflake

Twitter 利用 Zookeeper 实现一个全局的 ID 生成服务 Snowflake: https://github.com/twitter/snowflake

Snowflake 生成的 unique ID 的组成 (由高位到低位):

  • 41 bits: Timestamp 毫秒级
  • 10 bits: 节点 ID datacenter ID 5 bits + worker ID 5 bits
  • 12 bits: sequence number

一共 63 bits ,其中最高位是 0

unique ID 生成过程:

  • 41 bits 的 Timestamp: 每次要生成一个新 ID 的时候, 都会获取一下当前的 Timestamp, 然后分两种情况生成 sequence number:

      - 如果当前的 Timestamp 和前一个已生成 ID 的 Timestamp 相同 (在同一毫秒中), 就用前一个 ID 的 sequence number + 1 作为新的 sequence number (12 bits); 如果本毫秒内的所有 ID 用完, 等到下一毫秒继续 (**这个等待过程中, 不能分配出新的 ID**)
      - 如果当前的 Timestamp 比前一个 ID 的 Timestamp 大, 随机生成一个初始 sequence number (12 bits) 作为本毫秒内的第一个 sequence number
    
  • 10 bits 的机器号, 在 ID 分配 Worker 启动的时候, 从一个 Zookeeper 集群获取 (保证所有的 Worker 不会有重复的机器号)

整个过程中, 只有在 Worker 启动的时候会对外部有依赖 (需要从 Zookeeper 获取 Worker 号), 之后就可以独立工作了, 做到了去中心化.

异常情况讨论:

在获取当前 Timestamp 时, 如果获取到的时间戳比前一个已生成 ID 的 Timestamp 还要小怎么办? Snowflake 的做法是继续获取当前机器的时间, 直到获取到更大的 Timestamp 才能继续工作 (在这个等待过程中, 不能分配出新的 ID)

从这个异常情况可以看出, 如果 Snowflake 所运行的那些机器时钟有大的偏差时, 整个 Snowflake 系统不能正常工作 (偏差得越多, 分配新 ID 时等待的时间越久)

从 Snowflake 的官方文档 (https://github.com/twitter/snowflake/#system-clock-dependency) 中也可以看到, 它明确要求 “You should use NTP to keep your system clock accurate”. 而且最好把 NTP 配置成不会向后调整的模式. 也就是说, NTP 纠正时间时, 不会向后回拨机器时钟.

下面是 Snowflake 的其他变种, Instagram 产生 ID 的方法也借鉴 Snowflake

Boundary flake

代码地址:https://github.com/boundary/flake

变化:

ID 长度扩展到 128 bits:

  • 最高 64 bits 时间戳;
  • 然后是 48 bits 的 Worker 号 (和 Mac 地址一样长);
  • 最后是 16 bits 的 Seq Number

由于它用 48 bits 作为 Worker ID, 和 Mac 地址的长度一样, 这样启动时不需要和 Zookeeper 通讯获取 Worker ID. 做到了完全的去中心化

基于 Erlang ,这样做的目的是用更多的 bits 实现更小的冲突概率,这样就支持更多的 Worker 同时工作。同时, 每毫秒能分配出更多的 ID。

Simpleflake

源代码:https://github.com/SawdustSoftware/simpleflake

Simpleflake 的思路是取消 Worker 号, 保留 41 bits 的 Timestamp, 同时把 sequence number 扩展到 22 bits;

Simpleflake 的特点:

  • sequence number 完全靠随机产生 (这样也导致了生成的 ID 可能出现重复)
  • 没有 Worker 号, 也就不需要和 Zookeeper 通讯, 实现了完全去中心化
  • Timestamp 保持和 Snowflake 一致, 今后可以无缝升级到 Snowflake

Simpleflake 的问题就是 sequence number 完全随机生成, 会导致生成的 ID 重复的可能. 这个生成 ID 重复的概率随着每秒生成的 ID 数的增长而增长.

所以, Simpleflake 的限制就是每秒生成的 ID 不能太多 (最好小于 100次/秒, 如果大于 100次/秒的场景, Simpleflake 就不适用了, 建议切换回 Snowflake).

Instagram 的做法

Instagram 参考 Flickr 的方案,结合 Twitter 的经验,利用 PostgreSQL 数据库的特性,实现了一个更加简单可靠的 ID 生成服务。 Instagram 的分布式存储方案: 把每个 Table 划分为多个逻辑分片 (logic Shard), 逻辑分片的数量可以很大, 例如 2000 个逻辑分片。然后制定一个规则, 规定每个逻辑分片被存储到哪个数据库实例上面; 数据库实例不需要很多. 例如, 对有 2 个 PostgreSQL 实例的系统 (instagram 使用 PostgreSQL); 可以使用奇数逻辑分片存放到第一个数据库实例, 偶数逻辑分片存放到第二个数据库实例的规则

每个 Table 指定一个字段作为分片字段 (例如, 对用户表, 可以指定 uid 作为分片字段)

插入一个新的数据时, 先根据分片字段的值, 决定数据被分配到哪个逻辑分片 (logic Shard) 然后再根据 logic Shard 和 PostgreSQL 实例的对应关系, 确定这条数据应该被存放到哪台 PostgreSQL 实例上

Instagram 在设计ID时考虑了如下因素:

  • 生成的IDs 需要按照时间排序,比如查询一组照片时就不需要额外获取照片更多的信息来进行排序
  • IDs 64bits 索引,或者存储在 Redis 中
  • The system should introduce as few new ‘moving parts’ as possible — a large part of how we’ve been able to scale Instagram with very few engineers is by choosing simple, easy-to-understand solutions that we trust.

Instagram unique ID 的组成:

  • 41 bits 表示 Timestamp (毫秒), 能自定义起始时间 epoch
  • 13 bits 表示 每个 logic Shard 的代号 (最大支持 8 x 1024 个 logic Shards)
  • 10 bits 表示 sequence number; 每个 Shard 每毫秒最多可以生成 1024 个 ID

假设2011年9月9号下午 5 点钟, epoch 开始于 2011 年 1 月1 号,那么已经有 1387263000 毫秒经过,那么前 41 bits 是

id = 1387263000 <<(64-41)

接下来13位由分片ID决定,假设按照 user ID 来分片,有 2000 个逻辑分片,如果用户的ID 是 31341 , 那么分片 ID 是 31341%2000 -> 1341 ,所以接下来的13位是:

id |= 1341 <<(63-41-13)

最后,每个表自增来填补剩下的 bits,假设已经为表生成了 5000 个 IDs,那么下一个值是 5001,然后取模 1024

id |= (5001 % 1024)

sequence number 利用 PostgreSQL 每个 Table 上的 auto-increment sequence 来生成。如果当前表上已经有 5000 条记录, 那么这个表的下一个 auto-increment sequence 就是 5001 (直接调用 PL/PGSQL 提供的方法可以获取到) 然后把 这个 5001 对 1024 取模就得到了 10 bits 的 sequence number。

instagram 这个方案的优势在于:

利用 logic Shard 号来替换 Snowflake 使用的 Worker 号, 就不需要到中心节点获取 Worker 号了. 做到了完全去中心化

另外一个附带的好处就是, 可以通过 ID 直接知道这条记录被存放在哪个 logic Shard 上。同时, 今后做数据迁移的时候, 也是按 logic Shard 为单位做数据迁移的, 所以这种做法也不会影响到今后的数据迁移

MongoDB ObjectID

MongoDB 的 ObjectID2 采用 12 个字节的长度,将时间戳编码在内。

  • 其中前四个字节时间戳,从标准纪元开始,单位秒,时间戳和后5个字节保证了秒级别的唯一性,保证插入顺序以时间排序。
  • 接着前四个字节时间戳的后面三个字节为机器号,这三个字节为所在主机唯一标识,一般为机器名散列值。
  • 接着两个字节为PID标识,同一台机器中可能运行多个Mongo实例,用PID来保证不冲突
  • 后三个字节为递增序号,自增计数器,来确保同一秒内产生的 ObjectID 不出现冲突,允许 256 的三次方 16777216 条记录。

reference

  1. http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/ 

  2. https://docs.mongodb.com/manual/reference/method/ObjectId/#objectid 


2017-11-08

flower 简单使用

Flower 是一个基于 Web 的监控和管理工具,可以用在 Celery 集群的监控和管理。和 Celery 配合使用非常不错。

Flower 可以查看 Celery 队列中 task 的数量,可以用来监控 worker 的状态并进行简单的管理,比如调整 worker 的 pool size 和 autoscale 设置,可以用来查看当前处理的 tasks, 等等。

安装

使用 pip 安装 Flower

pip install flower

或者安装开发版

pip install https://github.com/mher/flower/zipball/master

使用 #{Usage}

直接使用下面命令开启本地监听 http://localhost:5555

flower -A proj --port=5555

或者从 Celery 中开启

celery flower -A proj --address=127.0.0.1 --port=5555

如果要暴露给外网访问可以 --address=0.0.0.0:

celery flower -A worker --address=0.0.0.0 --port=5555 --basic_auth=name:password

使用 --basic_auth 来开启 HTTP 简单验证

reference


2017-11-07 flower , monitor , linux , celery , django

vim presentation

大纲

Vim 多模式编辑器

Normal mode
Insert mode
Visual mode
Command mode

插入模式

i 进入 insert mode,在光标为之前进入插入模式
I 行首非空字符前插入 , I 等同于 `^i`
s 删除光标下字符,并进入insert mode, 等同于 `cl`
S 删除光标所在一行,并进入insert mode行首 , 等同于 `^C`
a 光标之后进入insert mode
A 光标移动到行尾并进入insert mode , 等同于 `$a`
o 在光标下一行插入一行,并进入insert mode , 等同于 `A<CR>`
O 在光标上一行插入新行,并进入insert mode , O 等同于 `ko`
C 删除光标后到行尾内容,并进入insert mode , C 其实等同于 `c$` 
cc 删除行,并进入插入模式
Esc 退出 Insert 模式

Example: this is a text for testing mode.

移动

h,j,k,l

    k
    ^
h <   > l
    v
    j

H,M,L
w,W,e,E,b,B
%
0,^,$
gg, G
fx,Fx,tx,Tx,
'', g;     move cursor to last edit change

Example: This is a sentence for testing moving with a word and WO-RD test.

编辑

p   光标之后粘贴 (p)aste
P   paste before the cursor
yy  复制当前行
y   Yank 复制. Example: yw (yank word) 光标停留到词第一个字母上 yw 复制单词
y0  copy the data from cursor to begining of the line
y$  copy the data from cursor to end of the line
x   删除光标下单个字符,将其放到粘贴板,剪切
X   向前删除一个字符,相当于 Backspace
dd  删除光标所在一行,并把该行复制
dw  删除光标所在词 (d)elete (w)ord
d0  删除光标到该行最前
d^  删除光标到行首非空白字符
d$  删除光标到该行最后
J   删除光标所在行的换行符
r,R 替换

组合

Operator + Motion = Action

dw
da'

Example:
This is an 'example' for "word-to-delete".
add quote to this "word"

搜索

/pattern  - 正向搜索,从光标处开始向文件末搜索
?pattern  - 反向搜索,从光标处开始向文件首搜索
n  - 下一个,往下执行搜索命令
N  - 上一个
*  - Word under cursor - forward (bounded)
#  - Word under cursor - backward (bounded)
:n1, n2s/p1/p2/g -将第n1至n2行中所有p1均用p2替代
:g/p1/s//p2/g -将文件中所有p1均用p2替换

替换

:s/p1/p2/g -将当前行中所有p1均用p2替代
:%s/old/new/g 全局替换
:'<,'>s/old/new/g 选中之后替换
:g/^$/d 删除所有空行

Example:

Text Object

dit, di', di", di), di}, di],
yi), yi]
vi', vit
ciw, ciw)

macro

qw        开启record macro, 保存到寄存器 w
q         结束录制

Example:
int a = 1
int b = 2
int c = a+b
print a
print b
print c

register

共有9大类寄存器

  • The unnamed register “”
  • 10 numbered registers “0 to “9 数字寄存器中保存着 yank 的内容
  • The small delete register “-
  • 26 named registers “a to “z or “A to “Z 可供自定义使用
  • four read-only registers “:, “., “% and “#
  • the expression register “=
  • The selection and drop registers “*, “+ and “~
  • The black hole register “_
  • Last search pattern register “/

寄存器访问 “a 寄存器前加 “

其他操作

:reg a 查看寄存器
""   noname buffer last dcsxy
"_   blackhole register
"%   filename register
"/   last search register
":   last command
insert mode <C-r> a insert text in register a
"ap  Normal mode paste text in register a

Plugin

surround.vim

cs"'      change surround " to '
cs'<q>    change surround ' to <q>
ds'       delete surround '
ysiw"     add " to word
yss)      add ) to entire line

Example:
"Hello world!"  and other strings

start

vimutor

总结

更快的编码效率


2017-11-05 vim , linux

Vim 寄存器

Vim 的寄存器可以看成 Vim 中额外用来存储信息的区域,虽然看不见,但是如果使用 x, s, y, p 等等命令的时候都无意识的使用到了 Vim 的寄存器(register).

Vim 中每一个 register 都可以通过添加双引号的方式来访问,比如 "a 来访问 a 寄存器。

可以通过选择然后使用 y 来将内容放到寄存器中,比如 "ay 来将选择的内容 yank 复制到 "a 寄存器。 可以使用 "ap 来粘贴 a 寄存器中的内容。

同样可以再 Insert mode 下使用 Ctrl + r 再加 a 寄存器名字来插入。这会将寄存器内容粘贴进当前编辑位置。

可以使用 :reg 命令来查看所有寄存器及其内容,或者直接在后面添加寄存器名字来查看关心的内容 :reg a b c

:reg a b c
--- Registers ---
"a   register a content
"b   register b content
"c   register c content

默认寄存器 unnamed register

Vim 有自己的 unnamed 或者说默认寄存器,可以通过 "" 来访问。任何被 d, c, s, x 删除或者 y 复制的内容都会被存放在该寄存器中,使用 p 粘贴时也是中该寄存器中粘贴内容。直接使用 p 其实等效于 ""p

数字寄存器

当使用 yank 复制一些内容,然后又执行了 d 命令,会发现粘贴时,当时 yank 的内容被 d 命令中的内容替换了,这是 Vim 非常常见的一个问题,然而问题并不在 Vim,而是 Vim 把 yank 的内容 ,delete 的内容都放到了 unnamed register 中了,但是复制的内容并没有丢失,并不需要再次回到想要复制的地方再次 yank 一遍。

这里就要介绍 Vim 的另外一种寄存器 —- 数字寄存器 numbered registers , 正如他的名字一样,数字寄存器的名字就是 "0"9

"0 寄存器永远保存着 yank 的最新内容,其他寄存器保存这历史 9 个内容,从 1 到 9 从新到老。如果复制了新的文字,永远可以通过 "0p 来粘贴。

只读寄存器 read only register

Vim 中有四个只读寄存器 "., "%, ":, "# , 最后插入的文字被保存在 ". 中,如果想要在其他地方使用刚刚输入的文字,非常方便。

"% 保存 Vim 最开始打开时,到当前文件的完整路径。最常用的组合就是将当前的路径放到粘贴板

:let @+=@%

let 命令用来向寄存器写入, "+是粘贴板寄存器,将 "% 内容写入粘贴板

": 最近执行命令寄存器。如果最近保存过文件 :w ,那么 w 会保存在寄存器中。可以使用 @: 来执行前一次命令。再比如使用替换命令替换了一行中的内容 :s/foo/bar ,那么将光标移动到另一行再次使用 @: 就能够再次替换。

"# 保存 alternative file 名字。大致可以理解为 Vim 中编辑的上一个文件,可以通过 :h alternate-file 来查看更多。当使用 Ctrl + ^ 来切换文件时,使用的就是这个寄存器保存的文件名。同样可以使用 :e Ctrl-r # 来做同样的事情。不常用。

表达式寄存器 expression register

"= 用来处理表达式结果,如果在 Insert Mode 使用 Ctrl+r+= ,然后输入 2+2<Enter> ,结果 4 会显示出来。也可以用来执行外部命令,比如 Ctrl - r = 之后输入 system('ls') <enter> 来显示 ls 的结果。

搜索寄存器 search register

"/ 这个寄存器保存最近搜索过的内容,包括 /, ?, *, # 的内容。比如说最近搜索过 /example ,想要替换成 another, 可以使用

:%s/<Ctrl-r/>/another/g

输入 :%s/ 只有按下 Ctrl + r 再按 / 会自动插入寄存器保存的内容。

搜索寄存器也是可以写的,可以使用 let 命令写入:

:let @/="keywords"

寄存器和宏 Macro 的关系

或许很多不熟悉 Vim 寄存器的用户曾经使用过 Vim 的 Macro, Vim 可以使用 Macro 来录制一连串命令,然后重复。(可以使用 :h recording 来查看更多)。

Vim 使用寄存器来保存 Macro 的命令。比如使用 qw 来录制 Macro ,寄存器 "w 会被用来记录所有录制的内容,所有的内容都有文本的形式存放。

更加 Cool 的事情就是,因为所有的内容都以文本的形式存在寄存器中,我们可以轻易的修改其中的内容,而不至于因为一个疏忽录制错误,而重新录制整个操作。

比如忘记了给文字最后添加分号,可以

:let @W='i;'

来修改录制的内容,注意 W 是大写,这是追加到寄存器的意思,插入 i; 进入插入模式并输入 ;

再比如如果需要直接修改 register 的内容,可以使用

:let @w='<Ctrl-r w>

然后修改需要修改的内容,然后以 ' 结束。

另外一个 Cool 的事情就是,因为所有的命令都是以文本的形式保存的,所以可以轻易的将录制的 macro 移动到另外的寄存器中,或者将录制的 macro 分享给其他人。

比如将录制在 w 寄存器中的内容复制给粘贴板寄存器,然后就能在其他 Vim 中使用。

总结

Vim 中的寄存器一共分为 9 大类:

  1. The unnamed register “”
  2. 10 numbered registers “0 to “9
  3. The small delete register “-
  4. 26 named registers “a to “z or “A to “Z
  5. four read-only registers “:, “., “% and “#
  6. the expression register “=
  7. The selection and drop registers “*, “+ and “~
  8. The black hole register “_
  9. Last search pattern register “/

其中大部分的寄存器上文都有涉及,其中为涉及到的 selection and drop registers 和 GUI 中选取的内容有关系。

black hole register 是一个黑洞寄存器,当写入 black hole register 时,nothing happens . 像黑洞一样吞掉所有的输入,可以用来在大量删除文本时不影响任何寄存器。

reference


2017-11-03 Vim , Linux

vim normal 命令

替换::%s/^/#/g visual block:ggI# 注释第一行后用.重复执行每一行 我们可以在第三种方法之上用normal命令实现上述需求,步骤:

光标定位到首行,执行:I# jVG选中之后的所有行 :'<,'>normal .这样刚刚选中的行都将执行.代表的最后一次操作。注:只要输入:就能实现:'<,'>,你可以注意VIm的左下角的提示。 第四种方法::%normal I#,%代表这个文件,当然你可以选择具体的范围,如::1,4normal I#

总结::normal命令可以执行任何normal 模式下的命令,更多帮助::help normal。


2017-11-03 Vim , Linux

Vim 中的宏命令

Vim 的设计哲学中有这样一句话:”if you write a thing once, it is okay. However if you’re writing it twice or more times, then you should find a better way to do it”.

Vim 的 Macro 就是用来解决重复的问题。在 Vim 寄存器的文章里面已经对 macro 有所涉及, macro 的操作都是以文本的方式存放在寄存器中。

简单使用

录制 macro,使用 q + [a-z] 26个字母中的一个

q[a-z]

之后的命令都会被记录,然后结束时再按一下 q

执行 macro 的时候,在寄存器前加 @ ,比如记录在寄存器 a 中

10@a

执行 10 遍a寄存器中记录的命令。

@@

再执行一遍上一次的命令。

编辑 macro

假设已经有一个 macro 保存在了 a 中,可以使用

  • :let @a=’
  • 输入Ctrl + r + a 来插入 a 中内容;
  • 编辑内容然后以 ' 结束 Enter 退出

查看 macro

macro 内容保存在 a 中,直接使用 :reg a 来查看内容即可。

举例

经典的注释和尾部添加

注释,或者在每一行的末尾添加特定字符,比如在每行末加上分号”;” ,对于这个操作 Vim 中有太多的方式可以完成,比如说替换 :%s/$/;/g ,比如说 . 命令,再比如这里要使用的 macro :

int a = 1
int b = 2
int c = a+b
print a
print b
print c

如果是用 . 来实现的话,首先在第一行执行A;,然后重复5次执行j.,对于这种简单文件来说很容易使用,但是如果这个文件有 1000 行,那么显然 . 命令是不可行的。使用 macro , 可以先录制一遍,然后在 1000 行上执行便可。

比如可以在normal模式下使用 qaA;<Esc>jq

  • qa 开启录制,存入 a 寄存器
  • A 在行尾进入插入模式
  • ; 插入分号
  • <Esc> 退出插入模式
  • j 下一行
  • q 退出录制

此时 a 寄存器中就保存了当前行的操作,在当前行添加 ; 并将光标移动到下一行。

录制结束后就可以使用 @a

1000@a

执行1000遍 macro ,就能将下面1000行尾部添加 ;

递增数字

可以使用 macro 实现插入 1 到 100 个数字,每一行自增一:

1
2
3
...
100

首先在第一行插入1,然后光标定位了“1”处,进入normal模式

输入一下命令

qayyp<Ctrl>aq
  • yyp 拷贝一行再粘贴在新的一行,
  • <Ctrl>a 数字+1
  • q 结束录制

最后执行

98@a

2017-11-03 vim , Linux

headless chrome puppeteer

Headless 最早的时候在 PhantomJS 听说过这个概念,后来在 GitHub 各种项目中总有人不断提起这个概念,而最新看到的新闻便是 Chrome 开始支持 Headless,也正激起了我了解的欲望。

什么是 Headless Chrome

Headless Chrome 是一个没有前台界面,只在后台运行的浏览器。浏览器正常的所有解析,渲染都可以由其完成。而开发者可以通过 client 和这个浏览器建立连接,通过 Google 提供的 Chrome DevTools Protocol 协议 来进行交互。总的来说 Headless 浏览器提供了可编程化的浏览器工具,一切通过人工在 Chrome 中完成的事情,都可以通过编程来在 Headless Chrome 中实现。

Headless Chrome 和 Chrome 59 一起发布,Headless Chrome 将 Chromium 和 Blink 渲染引擎提供的现代WEB平台的特性带到了命令行。

Headless 浏览器能够提供自动化测试环境,服务于不需要UI界面的服务端。比如说你想要测试一个网页在真实的浏览器中的显示,并保存成PDF。

使用

在 Linux 下,我的 Chrome 安装在 /opt/google/chrome/ 目录下,创建一条 alias

alias chrome='/opt/google/chrome/chrome'

打印 DOM 结构

使用 --dump-dom 将文件内容打印到标准输出

chrome --headless --disable-gpu --dump-dom https://www.einverne.info/

将网页保存为 PDF

使用 --print-to-pdf flag 将网页文件保存成 pdf

chrome --headless --disable-gpu --print-to-pdf https://www.einverne.info

保存截图

使用 --screenshot flag 保存截图

chrome --headless --disable-gpu --screenshot https://www.einverne.info

使用 --window-size 来指定窗口大小

chrome --headless --disable-gpu --screenshot --window-size=1280,1920 https://www.einverne.info

使用 --screenshot 会在当前目录下生成一个名为 screenshot.png 的图片文件。

Debugging Chrome without UI

使用 --remote-debugging-port=9222 flag 来启动 Chrome 时,Headless Chrome 会开启 DevTools protocol 。使用该协议可以用来和 Chrome 通信,并使用指令来操作 Headless Chrome。因为没有界面,可以使用另外的浏览器访问 http://localhost:9222 来查看 Headless Chrome 的状态。

reference


2017-10-31 Chrome , Headless Chrome

Vim 键映射

Vim 本身有很多快捷键,vimrc 也可以配置很多快捷键,当然 Vim 也支持将不同的键映射到不同的键或者命令上。

最常见的键映射就是

  • nmap
  • vmap
  • imap

分别对应着修改普通模式(Normal) ,选择模式(Visual),和插入模式(Insert) 下的键映射。

对于这几种模式,可以参考 Vim 模式

Map 命令

其实对于 map 命令的种类远不止于此

  • noremap 非递归映射
  • nmap
  • vmap
  • imap
  • cmap 在命令模式下生效

递归映射,就是如果当快捷键 a 被映射成 b, b 又被映射成 c ,那么他们是递归的,那么 a 就是被映射成 c

map b a
map c b

效果等同于

map c a

默认的 map 是递归的,除了 noremap

unmap 解除映射

unmap 和 map 类似也可以添加很多前缀,表示影响的模式

mapclear 命令

mapclear 直接清除相关模式下所有的映射,也可以添加很多前缀,表示影响的模式

所以总结一下大概有如下命令:

:map   :noremap   :unmap   :mapclear 
:nmap  :nnoremap  :nunmap  :nmapclear
:vmap  :vnoremap  :vunmap  :vmapclear
:imap  :inoremap  :iunmap  :imapclear
:cmap  :cnoremap  :cunmap  :cmapclear

查看当前 Vim 配置的键绑定

通过在 vimrc 中配置不同的快捷键,影响不同模式下 Vim 的快捷键,那么可以在普通模式下使用 :map 来查看当前 Vim 配置的快捷键。

如何测试 map 生效

Vi stackoverflow 上有一篇文章 讲述的很详细。

解决的具体步骤:

  • 明确 map 的命令
  • 想要定义的快捷键具体做什么
  • 然后使用 :map 来检查定义的 map 是否已经被 Vim 识别
  • 如果还是不行,在那篇文章中检查是否遇到了常见的一些错误
  • 再不行的话就去 vi stackoverflow 等等论坛求助大神吧

2017-10-30 Vim , Linux

Google+

最近文章

  • Trello 简单使用 新工具 Trello
  • 优雅地使用命令行 使用快捷键 Ctrl+a Ctrl+e Ctrl+u Ctrl+r
  • Tmux Plugins layout: post title: “Tmux 的插件们” tagline: “” description: “介绍目前我在使用的 Tmux 插件们” category: 学习笔记 tags: [tmux, linux, terminal,] last_updated: –
  • log4j 配置 Log4j 是一个可靠的、高效的、快速可扩展的日志框架,Log4j 使用 Java 开发,已经被移植到了很多主流语言,比如 C, C++, Perl, Python, Ruby 等等。Log4j 可以通过外部文件配置来定义行为,Log4j 为日志输出提供了不同的目的地,比如可以将日志输出到控制台,文件,数据库等等。我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。这一切都可以通过一个配置文件来灵活地进行配置,而不需要修改应用代码。Log4j是Apache的一个开放源代码项目。
  • git不同阶段撤回 因为平时使用 SmartGit 这样一个 Git client,所以也没有太大注意 Git 中不同阶段撤回的方式,虽然平时接触过 git reset 的 --soft 和 --hard 来撤销已提交的 commit,但没有形成一个系统的知识体系。大家都知道 Git 是一个分布式版本控制,所以 Git 会有一个本地库,和一个远端库,而平时提交代码的时候,一般也都是先从本地工作区提交代码