使用 rebase 来合并多个 commits

Git 作为分布式版本控制系统,所有修改操作都是基于本地的,在团队协作过程中,假设你和你的同伴在本地中分别有各自的新提交,而你的同伴先于你 push 了代码到远程分支上,所以你必须先执行 git pull 来获取同伴的提交,然后才能 push 自己的提交到远程分支。而按照 Git 的默认策略,如果远程分支和本地分支之间的提交线图有分叉的话(即不是 fast-forwarded),Git 会执行一次 merge 操作,因此产生一次没意义的提交记录。

在 pull 操作的时候,使用 git pull --rebase 选项即可很好地解决上述问题,使用 -r 或者 --rebase 的好处是,Git 会使用 rebase 来代替 merge 的策略。

使用 man git-merge 中的示例图说明:

             A---B---C  remotes/origin/master
            /
       D---E---F---G  master

如果执行 git pull 之后,提交线是:

             A---B---C remotes/origin/master
            /         \
       D---E---F---G---H master

结果是多出了 H 这个 无意义的提交。如果执行 git pull -r 的话,提交就是:

                   remotes/origin/master
                       |
       D---E---A---B---C---F'---G'  master

本地的两次提交就使用 rebase 重新添加到了远端的提交之后,多余的 merge 无意义提交消失。

在了解 git pull -r 的前提下,来看一下如何使用 rebase 命令来将本地的多个提交合并为一次提交。

假设本地 Git 仓库中因为临时提交产生了一些 commits

commit 8b465db3672a24710207d91af74d61cee975b208
Author: Ein Verne
Date:   Thu Nov 30 20:25:52 2017 +0800

    Third commit

commit 821476d2b043e85d131483279e23778aa3fd1241
Author: Ein Verne
Date:   Thu Nov 30 14:07:08 2017 +0800

    Second commit


commit 51912266c1634dd2f0848071cc311975b6aad730
Author: Ein Verne
Date:   Thu Nov 23 20:39:42 2017 +0800

    Init commit

假设我们需要将第二次提交 821476d2b043e85d131483279e23778aa3fd1241 和 第三次提交 8b465db3672a24710207d91af74d61cee975b208 合并为一次提交,可以先使用

git rebase -i 5191226

最后一次不需要修改的 commit id,然后进入 vi 的提交信息的编辑模式。

pick 821476d Second commit
pick 8b465db Third commit

# Rebase 5191226..8b465db onto 5191226 (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

这里可以看到,上方未注释部分填写要执行的命令,下方注释部分为支持的指令说明。指令部分由命令,commit hash 和 commit message 组成。

这里

  • pick 为选择该 commit
  • squash,将这个 commit 合并到前一个 commit
  • edit 选中提交,rebase 暂停,修改该 commit 提交内容
  • reword 选中提交,并修改提交信息
  • fixup 与 squash 相同,但不会保存当前 commit 的提交信息
  • exec 执行其他 shell 命令
  • drop 抛弃提交

这里只要将第三次提交前的 pick 修改为 squash,就可以将该 commit 合并到第二次提交。修改之后保存 :wq 退出。

pick 821476d Second commit
squash 8b465db Third commit

然后会进入 commit message 界面,在该界面中修改合适的提交信息,将两次的 commit 合并为一次,保存退出即可完成合并。

注意:git rebase 是一个比较危险的命令,如果一旦中途出现错误,可以使用 git rebase --abort 来终止 rebase,回到没有合并之前的状态。

TIPS

合并本地多次提交

如果想要合并最近的多次提交,在 rebase 进入交互模式时,可以指定范围比如

git rebase -i HEAD~8

选取最近的 8 次提交。

更换本地提交的顺序

在进入 rebase -i 交互模式时,更换提交信息的顺序,保存即可修改本地提交的 commit 顺序。比如

pick 821476d Second commit
pick 8b465db Third commit

修改为

pick 8b465db Third commit
pick 821476d Second commit

可以更换次序。

注意

git rebase 操作应该只用于本地尚未提交到远程仓库的 commit,一旦 push 到远端仓库,则不再允许修改 commit,否则可能会给其他开发者带来很多麻烦。尤其是多人协作时,千万要注意。

reference


2017-11-25 git , linux , verson-control , rebase , git-rebase

每天学习一个命令:zgrep 不解压过滤压缩包中文本

Linux 下按照正则过滤文本的命令 grep 非常强大,grep 能够把正则匹配的行打印出来。而 zgrep 则能够对压缩包内容进行正则匹配。zgrep 全称是 search compressed files for a regular expression

grep 的命令格式是

grep [option] pattern files

他的工作方式是,在一个或者多个文件中根据正则搜索匹配内容,将搜索的结果输出到标准输出,不更改源文件内容。

grep 常用的一些选项

-i   忽略字符大小写区别
-v   显示不包含正则的所有行

关于更多的 grep 的内容可以参考另外一篇文章,zgrep 和 grep 用法类似,不过操作的对象是压缩的内容。支持 bzip2,gzip,lzip, xz 等等。

zgrep 使用

但如果想要过滤 Nginx 的 access_log.gz 的压缩文件的内容,如果先解压,然后过滤出有用的文本,再把文件压缩回去,这就变的非常不方便。

gunzip access_log.gz
grep "/api" access_log
gzip access_log

需要使用三个命令来实现文件的过滤,其实 Linux 下可以使用 zgrep 来一步完成

zgrep "/api" access_log.gz

和 grep 类似, zgrep 也可以指定多个文件同时进行搜索过滤

zgrep "/api" access_log.gz access_log_1.gz

延伸

既然提到了不解压搜索压缩包内容,.gz 的文件可以使用 zgrep ,而对于 .tar.gz 文件

zcat access.tar.gz | grep -a '/api'
zgrep -a "/api" access.tar.gz

其实这些带 z 的命令都包含在 Zutils 这个工具包中,这个工具包还提供了

zcat  解压文件并将内容输出到标准输出
zcmp  解压文件并且 byte by byte 比较两个文件
zdiff 解压文件并且 line by line 比较两个文件
zgrep 解压文件并且根据正则搜索文件内容
ztest - Tests integrity of compressed files.
zupdate - Recompresses files to lzip format.

这些命令支持 bzip2, gzip, lzip and xz 格式。


2017-11-20 linux , grep , regex , gz

每天学习一个命令:jhead 操作照片 EXIF

jhead 是一个可以查看,修改照片 EXIF 信息的命令行工具,没有 GUI。

jhead 可以查看:

  • Time and date picture was taken
  • Camera make and model
  • Integral low-res Exif thumbnail
  • Shutter speed
  • Camera F-stop number
  • Flash used (yes/no)
  • Distance camera was focused at
  • Focal length and calculate 35 mm equivalent focal length
  • Image resolution
  • GPS info, if stored in image
  • IPTC header
  • XMP data

jhead 可以修改:

  • Up-right images according to rotation tag using jpegtran
  • Set or relative adjust internal exif timestamps
  • Fix date / time offsets in large batches of images
  • Re-name or date stamp files according to exif timestamp
  • Transfer exif headers between images
  • Replace thumbnails inside Exif headers
  • Edit jpeg comments (but not the Exif comments)
  • Delete exif or comment sections from jpeg images
  • Create new minimal exif header containing date and thumbnail

installation

sudo apt install jhead

usage

jhead 的使用官方网站 有着非常详细的说明,这里只挑重点。

查看

jhead demo.jpg
jhead -v demo.jpg

将 EXIF 信息保存到文本

jhead -v demo.jpg > exif.txt

设置 comment

jhead -cl <comment> path/to/file.jpg
jhead -ce file.jpg          # 执行命令后会调用外部编辑器,保存内容到 comment
jhead -cs <output-filename> file.jpg        # 将 comment 内容保存到 output-filename
jhead -ci <input-filename> file.jpg         # 将 input-filename 中的内容保存到照片 comment

删除信息

jhead -dc 删除 jpg 信息备注
jhead -de 完全删除 EXIF
jhead -du 删除非原始信息,比如 PS 携带的额外信息
jhead -purejpg      相当于 -dc -de -du
jhead -di 删除 IPTC 信息
jhead -dx 删除 XMP 信息

其实命令行工具最大的作用就是批处理,用正则,可以同时处理非常多的文件。

其他

更强大的工具 exiftool

如果寻找编程接口 可以查看 libexif 或者 exifv2

reference


2017-11-20 linux , photo , exif , DSLR , camera

Vim 行选择复制和移动

在熟悉 Vim 基本的 yy (yank current line) 和 p (paste) 的操作前提下,如果现在 Vim 中批量的移动某一些行,或者批量的复制从第 50 行到100 行到第200行后面,以我们之前的知识,可以先跳转到第 50 行 (:50),然后进入 行选择模式 (V) ,往下选择 50 行 (50j),然后再对选中的 50 行进行复制操作 (y),然后再移动到 200 行 (:200) 粘贴 (p)。

计算一下,从选择到复制,上面的操作可以划分为 5 个步骤,需要敲击 :50V50jy:200p ,当然上面的例子只是一个极端的例子,一般这样的需求也不大可能会出现。而如果有命令可以直接拷贝某些行,那么就不需要这么复杂,Vim 提供了整行的复制和移动命令。

:[range]copy {address}

copy 命令, range 表示拷贝的范围,行号范围, address 表示要拷贝到的地方,比如上面的例子就可以使用 :50,100copy200 来完成。

copy 命令可以简写成 :co 或者 :t

几个常用的格式:

:t5   拷贝当前行到第5行的下一行
:t.   拷贝当前行到光标下一行,相当于 `Yp` 或者 `yyp`
:t$   拷贝当前行到文件最后一行
:'<,'>t0   拷贝选取的区域到文件开头,在 Visual 模式下选中文本,输入 `:`,再输入 `t0`

move 命令也和 copy 一样

:[range]m[ove] {address}	

这里只是将拷贝,换为移动。

可以通过 :help copy:help move 来查看帮助

快速选择行

遇到一个操作,比如在一个非常大的文件中,需要快速的选择 1000 行到 4500 行,那么可以用下面的方法快速选择。

方法一

  • 使用 :1000 快速移动到 1000 行
  • 行选择 V4500G 快速从当前行选择到 4500 行(包括)

G Goto line [count]

方法二

  • 使用 :1000 移动到 1000 行,然后按下 m + a,标记 a
  • 然后 :4500 移动到 4500 行,按下 V + \` + a 选择

方法三

  • 使用 :1000 移动到 1000 行,然后按下 V
  • 然后输入 3500j 向下移动 3500 行

2017-11-19 vim , linux , copy

精通正则表达式第三版读书笔记

这是 《精通正则表达式》第 3 版 的读书笔记。

技术图书的主要使命是传播专业知识,专业知识分为框架性知识和具体知识。框架性知识需要通过系统的阅读和学习掌握,而大量的具体知识,则主要通过日常生活的积累以及随用随查的学习来填充。

完整的正则表达式由两种字符构成,特殊字符,元字符,另外一种就是普通文本字符。

完整的正则表达式由小的构建模块单元 building block unit 构成,每个单元都很简单,不过他们能够以无穷多种方式组合,所以可以提供无限的可能。

字符组

匹配若干字符之一,使用中括号 [ea] 匹配 a 或者 e

gr[ae]y 表示匹配 gray 或者 grey

元字符 名称 匹配对象
. 单个任意字符
[abc] 字符组 列出的字符
[^abc] 排除字符组 未列出的字符
^   行起始
$   行尾
\<   单词起始
\>   单词结束
| 竖线
() 小括号 限制竖线的作用范围

其他元字符

元字符 描述
\d [0-9]
\D [^0-9]
\s [ \t\n\x0b\r\f]
\S 非空格
\w 单词字符
\W 非单词字符

量词,定义了元素可以发生的频率

元字符 次数下限 次数上限 含义
* 0 字符出现任意多次,或者不出现 {0,}
0 1 字符出现 0 次或者 1 次 ,{0,1}
+ 1 >=1, {1,}
{X}     匹配 X 个字符
{X,Y}     匹配 >=X<=Y

正则表达式可以使用多个括号,使用 \1, \2, \3 等来匹配括号的内容

([a-z])([0-9])\1\2 其中的 \1 代表的就是 [a-z] 匹配的内容,而 \2 就代表 [0-9] 匹配的内容

匹配引号内的字符串

"[^"]*"

两端引号用来匹配字符串开头和结尾的引号,中间 [^"] 用来匹配除引号之外的任何字符,* 用来表示任意数量的非引号字符

匹配 URL

grep, 和 egrep 的历史

正则表达式的流派

正则表达式的处理方式

集成式

Perl 中的例子

if ($line =~ m/^Subject: (.*)/i) {
    $subject = $1
}

取邮件标题的正则,内建在程序内,隐藏了正则表达式的预处理,匹配,应用,返回结果,减轻了常见任务的难度。

程序式处理和面向对象式处理

由普通函数和方法来提供

Java 中处理正则,Sun 提供了 java.util.regex 包来在 Java 中更加方便的使用正则。

import java.util.regex.*;

Pattern r = Pattern.compile("^Subject: (.*)", Pattern.CASE_INSENSITIVE);
Matcher m = r.matcher(line);
if (m.find()) {
    subject = m.group(1);
}

Perl 隐藏了绝大部分细节, Java 则暴露了一些正则的细节,编译正则表达式到 Pattern 对象,将正则和匹配的文本联系到一起,得到 Matcher 对象,在应用正则之前,检查是否存在匹配,返回结果,如果存在匹配,则捕获括号内的子表达式文本。

Java 也提供了函数式处理的例子, Pattern 类提供了静态方法

if (! Pattern.matches("\\s*", line)) {
    // 如果 line 不是空行
}

函数包装一个隐式的正则表达式,返回一个 Boolean。

Sun 也会把正则表达式整合到 Java 的其他部分,比如 String 类中 matches 函数

if (! line.matches("\\s*")) {
    // line 不为空行
}

String 中的方法不适合在对时间要求很高的循环中使用。

Python 中的处理,Python 也使用面向对象的方法

import re

r = re.compile("^Subject: (.*)", re.IGNORECASE)
m = r.search(line)
if m:
    subject = m.group(1)

这个例子和 Java 中的非常类似。

正则匹配规则

优先选择最左端匹配结果

从左往右匹配,左侧的结果优先于右侧

标准量词优先匹配

标准量词 ?, *, +, {m,n} 都是优先匹配 greedy 的。例如 a? 中的 a[0-9]+ 中的 [0-9],在匹配成功之前,进行尝试的次数是有上限和下限的,规则 2 表明,尝试总是获得最长的匹配。

标准匹配量词的结果可能并非所有可能中最长的,但是它们总是尝试匹配尽可能多的字符,直到匹配上限为止。如果最终结果并非该表达式的所有可能中最长的,原因肯定是匹配字符过多导致匹配失败。

举例, \b\w+s\b 来匹配包含 s 的字符串,比如 regexes\w+ 完全能够匹配整个单词,但如果 \w+ 来匹配整个单词 s 就无法匹配,为了完成匹配, \w+ 必须匹配 regexes ,最后把 s\b 留出来。

NFA 称为“表达式主导”引擎,对应的 DFA 称为 “文本主导” 引擎。

NFA 引擎

NFA 引擎中,每一个子表达式都是独立的,子表达式之间不存在内在的联系,子表达式和正则表达式的控制结构(多选分支、括号以及匹配量词)的层次关系控制了整个匹配过程。NFA 引擎是正则表达式主导,编写正则的人有充分的机会来实现期望的结果

DFA 文本主导

DFA 在扫描字符串时,会记录“当前有效”的所有匹配。比如正则

to(nite|knight|night)

来匹配文本

after ... tonight ...

当文本扫描到 t^onight 时,记录可能的匹配 t^o(nite|knight|night)

接下来扫描每一个字符都会更新可能的匹配序列,比如扫描到 toni^ght ... 时,可能的匹配就是 to(ni^te|knight|ni^ght)。此时 knight 就已经无法匹配。当扫描到 g 时只有一个匹配,等完成 h 和 t 的扫描之后,引擎发线匹配完成,报告成功。

对比

一般情况下,文本主导的 DFA 引擎要快一些,NFA 正则表达式引擎,因为需要对同样的文本尝试不同的表达式匹配,可能会产生不同的分支浪费时间。

NFA 匹配的过程中,目标文本中的某个字符串可能会被正则表达式中不同部分重复检查。相反,DFA 引擎是确定性,目标文本中的每个字符只会检查一遍。

这两种技术,都有对应的正式名字:非确定型有穷自动机 NFA,和 确定型有穷自动机 DFA。

正则引擎的分类

粗略分为三类

  • DFA 符合或者不符合 POSIX 标准的都属于此类
  • 传统 NFA
  • POSIX NFA

部分程序及其所使用的正则引擎

引擎 程序
DFA 大多数版本的 awk, egrep, flex, lex, MySQL
传统型 NFA GNU Emacs, Java, 大多数版本的 grep, less, more, .NET ,Perl, PHP,Python, Ruby, 大多数版本的 sed, vi
POSIX NFA mawk, GUN Emacs 明确指定时使用
DFA/NFA 混合 GNU awk, GNU grep/egrep, Tcl

实用技巧

匹配 IP 地址

匹配 IPv4 的地址,用点号分开的四个数组,0-255

[01]?[d\d?|2[0-4][d|25[0-5]

这个表达式能够匹配 0 到 255 之间的数,然后重复 4 遍。这样这个表达式会异常复杂,通常情况下,更合适的做法是不依赖正则完成全部的工作,使用

^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\$

来匹配,然后将匹配的数字拿出来,使用其他程序来进行验证。

匹配堆成括号

匹配括号的内容,或许会想到 \bfoo\([^)])

为了匹配括号

`(.*)`    括号及括号内任何字符
`([^)]*)`  从一个开括号到最近的闭括号
`([^()]*)` 从一个开括号到最近的闭括号,但是不容许其中包含开括号

对于文本

var = foo(bar(this), 3.7) + 2 * (that - 1);

第一个正则会匹配 (bar(this), 3.7) + 2 * (that - 1) , 而第二个正则表达式只会匹配 (bar(this) , 而第三个表达式能够匹配 (this) ,但是如果想要匹配 foo 后面的括号,则无能为力,所以三个表达式都不合格。

Java 正则

通过 java.util.regex 使用正则非常简单,一个接口一个 exception

java.util.regex.Pattern
java.util.regex.Matcher
java.util.regex.MatchResult
java.util.regex.PatternSyntaxException

通过 Pattern 构造编译正则表达式,通过正则匹配构建 Matcher 对象。

优化 Java 正则表达式

Java 的正则对性能影响非常大,如果在程序中大量使用正则,一定要对正则进行一定优化。

  • 多次使用同一个正则,Pattern.compile() 预先编译正则表达式
  • 选择 (X|Y|Z) 会显著降低正则的匹配速度,优先考虑 ac(cd|ef) 而不是 (abcd|abef)
  • 如果不需要分组内文本,尽量使用非捕获分组

reference


2017-11-17 regex , java

每天学习一个命令: mtr 查看路由网络连通性

常用的 ping,tracert,nslookup 一般用来判断主机的网络连通性,其实 Linux 下有一个更好用的网络联通性判断工具,它可以结合ping nslookup tracert 来判断网络的相关特性,这个命令就是 mtr。mtr 全称 my traceroute,是一个把 ping 和 traceroute 合并到一个程序的网络诊断工具。

traceroute 默认使用 UDP 数据包探测,而 mtr 默认使用ICMP报文探测,ICMP在某些路由节点的优先级要比其他数据包低,所以测试得到的数据可能低于实际情况。

安装

Debian/Ubuntu/Linux Mint 下

sudo apt install mtr-tiny
sudo apt install mtr    # with GUI

macOS 下:

brew install mtr

使用

简单使用,查看本地到 google.com 的路由连接情况:

mtr google.com

Screenshot from 2017-11-14 09-43-10

输出参数解释:

  • 第一列是IP地址
  • 丢包率:Loss
  • 已发送的包数:Snt
  • 最后一个包的延时:Last
  • 平均延时:Avg
  • 最低延时:Best
  • 最差延时:Wrst
  • 方差(稳定性):StDev

参数说明

report

使用 mtr -r google.com 来打印报告,如果不使用 -r or --report 参数 mtr 会不断动态运行。使用 report 选项, mtr 会向 google.com 主机发送 10 个 ICMP 包,然后直接输出结果。通常情况下 mtr 需要几秒钟时间来输出报告。mtr 报告由一系列跳数组成,每一跳意味着数据包通过节点或者路由器来达到目的主机。

一般情况下 mtr 前几跳都是本地 ISP,后几跳属于服务商比如 Google 数据中心,中间跳数则是中间节点,如果发现前几跳异常,需要联系本地 ISP 服务提供上,相反如果后几跳出现问题,则需要联系服务提供商,中间几跳出现问题,则两边无法完全解决问题。

packetsize

使用 -s 来指定ping数据包的大小

mtr -s 100

100 bytes 数据包会用来发送,测试,如果设置为负数,则每一次发送的数据包的大小都会是一个随机数。

指定发送数量

默认使用 -r 参数来生成报告,只会发送10个数据包,如果想要自定义数据包数量,可以使用 -c 参数

mtr -c 100 google.com

不进行主机解释

使用 -n 选项来让 mtr 只输出 IP,而不对主机 host name 进行解释

mtr -n github.com

常用例子

查看路由:

mtr -rn 1.1.1.1

延伸

在晚上或者 VPS 交流的时候经常能看到别人用可视化的方式展示路由跳转,其实都是使用的 best trace 这样一个软件。

官网地址: https://www.ipip.net/download.html

对于 Windows,Mac 和 Android 页面上都有相应的GUI客户端,Linux 下可使用命令行:

wget http://cdn.ipip.net/17mon/besttrace4linux.zip
unzip besttrace4linux.zip
chmod +x besttrace32
sudo ./besttrace -q 1 www.google.com

如果下载地址失效了,去官网上找最新的即可。

reference


2017-11-14 linux , network , mtr , ping , tracert , command

Instagram 的两种ID

Instagram 的 ID 生成策略是经过精心设计过的。1 每一秒 Instagram 都会收到无数用户上传的照片,在内部使用 [[PostgreSQL]] 分片存储到不同的服务器上。

这就产生了一个问题,要设计一个唯一 ID 生成方法用来标记系统中发布的每一张图片。

系统唯一 ID 需要满足如下条件:

  • ID 应该是时间有序的,一组照片 ID 列表不再需要外部信息就可以排序(UUID 就不合适,因为完全无序)
  • ID 最好是 64bit ,可以节省存储空间,索引也可以更小,也方便存储到 Redis 这样的系统中
  • 系统应该尽可能少引入外部依赖 (比如使用 Snowflake,就需要引入 Zookeeper,Snowflake 服务)

所以最后 ID 的构成:

  • 41 bit 用来存储毫秒时间(使用自定义 epoch 可以表示 41 年)
  • 13 bit 存储逻辑分片 ID
  • 10 bit 存储自增序列(对 1024 取模, id%1024),这意味着每一个 shard 每一毫秒可以生成 1024 个 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 这一特性,可以用来测试一些网络设备如路由器,防火墙,交换机等的性能。

iperf 在内存中运行,不会涉及到文件系统。iperf 存在非常多的版本,Windows,Linux,Android,iOS 都有对应的版本。可以到官网下载

安装

Linux 下安装:

sudo apt-get install iperf

其他系统到参考官网

iperf 是一个命令行工具,当然如果搜索也能发现 GUI 的工具,比如 jperf, xjerf 等等。不过还是推荐使用命令行。

iperf 使用非常简单的 C/S 架构,client 使用 -c 参数,服务端使用 -s

实例

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

服务端:

iperf -u -s # UDP 模式

客户端第一种模式

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

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

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

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

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

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

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

iperf -s

客户端持续 60 秒

iperf -c server_address -t 60

设置服务端端口

使用 -p port 来指定服务端端口,默认是 5201

间隔时间报告

使用 -i interval 参数来表示间隔时间

iperf -c 192.168.2.105(server address) -i 2

表示间隔 2 秒打印结果

iperf 版本区别

iperf 自身存在很多个版本,Ubuntu 默认源中可能有一个叫做 iperf, 也有一个叫做 iperf3, 这两个是不同版本的 iperf, 在使用 iperf 的时候要确保版本一致。不同的版本有不同的架构和不同的特性。

然而这两个版本 iperf3 是不兼容 iperf 2.x 的。可以这么理解 iperf 是内存中的网络性能测试工具,而 iperf3 是从头还是编写的一套新程序,目标是简化代码量,并且设计为工具库,可以嵌入到其他工具中。1

两者不同的区别

服务端:

功能 iperf iperf3
使用默认端口 iperf -s iperf3 -s
守护模式下,使用 TCP window iperf -s -w 32M -D iperf3 -s -D
Start UDP server on port 5003, and give 1 sec interval reports. Note that for iperf3 the -u option is passed to the server from the client. iperf -i1 -u -s -p 5003 iperf3 -s -p 5003

客户端:

功能 iperf / iperf3
执行 30 秒测试,每秒发送结果 iperf/iperf3 -c server_address -i 1 -t 30
执行逆向从服务端到本地测试 iperf/iperf3 -c server_address -i 1 -t 10 -R
使用 4 个并发,32M TCP buffer iperf/iperf3 -c server_address -i 1 -t 20 -w 32M -P 4
测试 200Mbps UDP iperf/iperf3 -c server_address -u -i 1 -b 200M

iperf3 增加了一些额外的功能,比如 -i 模式可以提供 TCP retransit (中继)结果,并且 verbose 模式可以打印更多有效的信息,比如 CPU 利用率等等。

更多的使用可以参考这里

更多 iperf 2.0.5 和 iperf 2.0.8+ 和 iperf 3.1.5+ 的区别可以在这里 查看。

使用

外延

  • 另外一个 TCP 性能测试工具 nuttcp

Android

在 Android 手机上可以使用一款叫做 Magic iPerf 的工具来进行测试。

或者使用 Termux 来直接使用命令测试。

reference


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

md5 散列

MD5 全称为 Message-Digest Algorithm ,一般用来保证信息传输的完整一致,MD5 的结果是固定长度 128bit 。MD5 算法最开始是为密码学而设计,但是后来被发现有很大的缺陷,所以后来就用来校验文件的完整性(无意识而造成的不完整,比如下载)。MD5 在非密码学领域也有其他用途,比如在一个数据库中快速检查一个 key 是否存在。

密码学的 hash 方法一个最基本的要求是,计算机难以通过计算来找到两个不同的内容拥有同一个 hash 值,MD5 无法满足该基本条件。MD5 算法无法防止碰撞,无法用于安全性验证,对于安全性高的资料,建议使用其他算法。

MD5 是 Ronald Rivest 在 1991 年设计用来代替 MD4, 具体的说明定义在 RFC 1321 中。

MD5 的用途

  • 校验文件完整性,比如下载一个文件包,或者发送一个文件,接受者收到之后用提供方提供的 md5 来校验
  • 将明文信息 hash 到密文信息,在只需要对比一致性时可以使用,比如密码校验(但一般不建议直接使用 md5 hash 密码)
  • git 的 commit ID 也是 MD5,未来可能慢慢被 SHA 代替

原理

MD5 的输入不定长,输出定长 128 bits。MD5 计算方式可以分为几个步骤:

  • 输入信息被分成每一块 512 bit 长的 (16 个 32 位子分组)组,此时必定有剩余,对剩余部分,先填充一个 1,然后是若干 0,最后预留出 64 位来保存前面信息的长度(这个信息的长度包括若干 512 bit 的区块 + 1 + 若干 0 组成),补充完成后整体一定是 512 bit 的倍数(包括三个部分,原始数据 + 补充数据 + 信息长度)。如果信息的长度超过了 64 位能够表示的范围,则取低 64 位。
  • 再得到 N*512 个数据组之后,初始 A B C D 四个 32 bit 长度的变量,里面依次存放十六进制

    A: 01 23 45 67 B: 89 ab cd ef C: fe dc ba 98 D: 76 54 32 10

  • 分组处理数据,已经将数据分成了 512 位一组,512 位又可以分成 16 * 32 位,小组中共有 16 个 32 位的子分组,用 M0 到 M15 来表示
  • 常数 Ti 是 4294967296*abs(sin(i))的整数部分,i 取值从 1 到 64
  • 接下来定义四个线性函数,每一个方法接受三个 32 位的 word 然后产生一个 32 位的 word

    F(X,Y,Z) = (XY) | (not(X) Z) if X then Y else Z G(X,Y,Z) = (XZ) | (Y not(Z)) if Z then X else Y H(X,Y,Z) = X xor Y xor Z I(X,Y,Z) = Y xor (X | not(Z))

伪代码:

主要分成这几部分,先针对第一个分组,处理 16 个子分组,通过初始 A B C D 计算得到新的 A B C D,然后将新的 A B C D 加到原来的 A B C D 中。如此往复直到计算完所有的分组,最终得到的 ABCD 就是 MD5 的值。

/* Process each 16-word block. */
For i = 0 to N/16-1 do

  /* Copy block i into X. */
  For j = 0 to 15 do
    Set X[j] to M[i*16+j].
  end /* of loop on j */

  /* 另存一份 */
  AA = A
  BB = B
  CC = C
  DD = D

  /* Round 1. */
  /* Let [abcd k s i] denote the operation
       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
  /* Do the following 16 operations. */
  [ABCD  0  7  1]  [DABC  1 12  2]  [CDAB  2 17  3]  [BCDA  3 22  4]
  [ABCD  4  7  5]  [DABC  5 12  6]  [CDAB  6 17  7]  [BCDA  7 22  8]
  [ABCD  8  7  9]  [DABC  9 12 10]  [CDAB 10 17 11]  [BCDA 11 22 12]
  [ABCD 12  7 13]  [DABC 13 12 14]  [CDAB 14 17 15]  [BCDA 15 22 16]

  /* Round 2. */
  /* Let [abcd k s i] denote the operation
       a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
  /* Do the following 16 operations. */
  [ABCD  1  5 17]  [DABC  6  9 18]  [CDAB 11 14 19]  [BCDA  0 20 20]
  [ABCD  5  5 21]  [DABC 10  9 22]  [CDAB 15 14 23]  [BCDA  4 20 24]
  [ABCD  9  5 25]  [DABC 14  9 26]  [CDAB  3 14 27]  [BCDA  8 20 28]
  [ABCD 13  5 29]  [DABC  2  9 30]  [CDAB  7 14 31]  [BCDA 12 20 32]

  /* Round 3. */
  /* Let [abcd k s t] denote the operation
       a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
  /* Do the following 16 operations. */
  [ABCD  5  4 33]  [DABC  8 11 34]  [CDAB 11 16 35]  [BCDA 14 23 36]
  [ABCD  1  4 37]  [DABC  4 11 38]  [CDAB  7 16 39]  [BCDA 10 23 40]
  [ABCD 13  4 41]  [DABC  0 11 42]  [CDAB  3 16 43]  [BCDA  6 23 44]
  [ABCD  9  4 45]  [DABC 12 11 46]  [CDAB 15 16 47]  [BCDA  2 23 48]

  /* Round 4. */
  /* Let [abcd k s t] denote the operation
       a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
  /* Do the following 16 operations. */
  [ABCD  0  6 49]  [DABC  7 10 50]  [CDAB 14 15 51]  [BCDA  5 21 52]
  [ABCD 12  6 53]  [DABC  3 10 54]  [CDAB 10 15 55]  [BCDA  1 21 56]
  [ABCD  8  6 57]  [DABC 15 10 58]  [CDAB  6 15 59]  [BCDA 13 21 60]
  [ABCD  4  6 61]  [DABC 11 10 62]  [CDAB  2 15 63]  [BCDA  9 21 64]

  /* Then perform the following additions. (That is increment each
     of the four registers by the value it had before this block
     was started.) */
  A = A + AA
  B = B + BB
  C = C + CC
  D = D + DD

end /* of loop on i */

最后每一轮更新之后进入下一轮更新,最后会在 ABCD 中保存 4 * 32 位最后 Hash 的结果。

检查文件 MD5

128 位的 MD5 一般表示为 32 位的十六进制数。

CyanogenMod 中有一段检查文件 MD5 的代码:

/*
 * Copyright (C) 2012 The CyanogenMod Project
 *
 * * Licensed under the GNU GPLv2 license
 *
 * The text of the license can be found in the LICENSE file
 * or at https://www.gnu.org/licenses/gpl-2.0.txt
 */

package com.cyanogenmod.updater.utils;

import android.text.TextUtils;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5 {
    private static final String TAG = "MD5";

    public static boolean checkMD5(String md5, File updateFile) {
        if (TextUtils.isEmpty(md5) || updateFile == null) {
            Log.e(TAG, "MD5 string empty or updateFile null");
            return false;
        }

        String calculatedDigest = calculateMD5(updateFile);
        if (calculatedDigest == null) {
            Log.e(TAG, "calculatedDigest null");
            return false;
        }

        Log.v(TAG, "Calculated digest: " + calculatedDigest);
        Log.v(TAG, "Provided digest: " + md5);

        return calculatedDigest.equalsIgnoreCase(md5);
    }

    public static String calculateMD5(File updateFile) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "Exception while getting digest", e);
            return null;
        }

        InputStream is;
        try {
            is = new FileInputStream(updateFile);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Exception while getting FileInputStream", e);
            return null;
        }

        byte[] buffer = new byte[8192];
        int read;
        try {
            while ((read = is.read(buffer)) > 0) {
                digest.update(buffer, 0, read);
            }
            byte[] md5sum = digest.digest();
            BigInteger bigInt = new BigInteger(1, md5sum);
            String output = bigInt.toString(16);
            // Fill to 32 chars
            output = String.format("%32s", output).replace(' ', '0');
            return output;
        } catch (IOException e) {
            throw new RuntimeException("Unable to process file for MD5", e);
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                Log.e(TAG, "Exception on closing MD5 input stream", e);
            }
        }
    }
}

reference


2017-11-10 md5 , linux , 密码,

LevelDB 使用

nodejs wrapper: https://github.com/Level/levelup

源码: https://github.com/google/leveldb

LevelDB 的原理 介绍 还有 这篇

LevelDB 的 C++ 接口 使用 LevelDB 在 iOS 下的 使用


2017-11-08 database , keyvalue , redis , google

电子书

本站提供服务

最近文章

  • 从 Buffer 消费图学习 CCPM 项目管理方法 CCPM(Critical Chain Project Management)中文叫做关键链项目管理方法,是 Eliyahu M. Goldratt 在其著作 Critical Chain 中踢出来的项目管理方法,它侧重于项目执行所需要的资源,通过识别和管理项目关键链的方法来有效的监控项目工期,以及提高项目交付率。
  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。