Dec
18
1. Get character count in a line
$ xxd packets | head -n 1 | wc -c
68
2. Join lines
xxd packets | tr -d '\n' && echo
Save the output to local file "packets.xxd".
3. Fold lines and decode
fold packets.xxd -w 67 | xxd -r > packets
引用
$ xxd packets | head -n 1 | wc -c
68
2. Join lines
引用
xxd packets | tr -d '\n' && echo
Save the output to local file "packets.xxd".
3. Fold lines and decode
引用
fold packets.xxd -w 67 | xxd -r > packets
Dec
16
// 日常吐槽莎币苹果。
MacOS 有很多很蠢的地方,比如没有快捷键可以让窗口在屏幕间移动;比如不能右键直接创建空文件;比如右键菜单有「复制」和「拷贝」两个选项;比如会莫名其妙出现左右声道不平衡的 bug 并且长期未修复;
再比如,不能在外接显示屏的情况下关闭内屏。
当然也不是完全不能,网友也有一些解决方案:
1. 合上屏幕当主机用,自然就息屏了。不过我需要用 mac 的键盘、触摸板和 TouchID 解锁。过。
2. 随身携带磁铁,欺骗 macOS 屏幕已经关闭。这可能是种行为艺术,可惜我不是艺术家。过。
3. SwitchResX,试了下,确实可以把内屏关闭,只不过拔了外显内屏还是关着的,折腾了几下甚至 mac 都不能识别外显了,重启才解决。卸。
4. 在「系统设置 -> 显示」里,将背光拉到 0;不能直接用 Fn 调节屏幕亮度,因为会把外接屏亮度一起调低(莎币值+1);而且每次接上外显或 wakeup 时,都要重新设置。过……吧?
好在作为一名码农,可以想一些自动化的方式来把方案 4 利用起来。
首先是用 brew install brightness (update: M3 Macbook can not use this. try Lunar, details below),可以用 `brightness -d 1 0` 把 display 1 的亮度调到 0 (注意把主屏设置为外接屏,且把内屏设置为拷贝外屏,这样内屏的编号就是 1,外显是 0)。
其次是有一个叫 hammerspoon 的软件(https://www.hammerspoon.org/),可以在 unlock 的时候自动执行一个 lua 脚本。
把下面这个 lua 脚本存为 ~/.hammerspoon/init.lua
然后再 touch ~/.unlock && chmod +x ~/.unlock,内容为:
sleep 5 是因为从睡眠状态恢复时,识别外屏会比较慢,可按需调整 sleep 时长。
如果你看到这里,说明你也需要注意日常用电脑的姿势了,避免头颈前倾。
(完)。
update @ 2023-12-12
实际体验下来还有一些问题,例如去倒杯水,锁屏但没有睡眠,解锁后就不会息屏。
最好用的方案往往只需要最简单的工具:crontab。
MAILTO=""
* * * * * /Users/bytedance/bin/TurnOffScreen.sh
* * * * * sleep 15 && /Users/bytedance/bin/TurnOffScreen.sh
* * * * * sleep 30 && /Users/bytedance/bin/TurnOffScreen.sh
* * * * * sleep 45 && /Users/bytedance/bin/TurnOffScreen.sh
update @ 2024-01-26
brightness seems to be not working on M3 Macbooks, so I switched to lunar:
1. Download and install lunar
2. Run in terminal: /Applications/Lunar.app/Contents/MacOS/Lunar install-cli
3. Update the TurnOffScreen.sh to be (remember to replace with your monitor model or serial id in the pattern):
MacOS 有很多很蠢的地方,比如没有快捷键可以让窗口在屏幕间移动;比如不能右键直接创建空文件;比如右键菜单有「复制」和「拷贝」两个选项;比如会莫名其妙出现左右声道不平衡的 bug 并且长期未修复;
再比如,不能在外接显示屏的情况下关闭内屏。
当然也不是完全不能,网友也有一些解决方案:
1. 合上屏幕当主机用,自然就息屏了。不过我需要用 mac 的键盘、触摸板和 TouchID 解锁。过。
2. 随身携带磁铁,欺骗 macOS 屏幕已经关闭。这可能是种行为艺术,可惜我不是艺术家。过。
3. SwitchResX,试了下,确实可以把内屏关闭,只不过拔了外显内屏还是关着的,折腾了几下甚至 mac 都不能识别外显了,重启才解决。卸。
4. 在「系统设置 -> 显示」里,将背光拉到 0;不能直接用 Fn 调节屏幕亮度,因为会把外接屏亮度一起调低(莎币值+1);而且每次接上外显或 wakeup 时,都要重新设置。过……吧?
好在作为一名码农,可以想一些自动化的方式来把方案 4 利用起来。
首先是用 brew install brightness (update: M3 Macbook can not use this. try Lunar, details below),可以用 `brightness -d 1 0` 把 display 1 的亮度调到 0 (注意把主屏设置为外接屏,且把内屏设置为拷贝外屏,这样内屏的编号就是 1,外显是 0)。
其次是有一个叫 hammerspoon 的软件(https://www.hammerspoon.org/),可以在 unlock 的时候自动执行一个 lua 脚本。
把下面这个 lua 脚本存为 ~/.hammerspoon/init.lua
-- init.lua
local log = hs.logger.new("", "info")
local function ok2str(ok)
if ok then return "ok" else return "fail" end
end
hs.caffeinate.watcher.new(function(event)
local eventName = hs.caffeinate.watcher[event]
log.f("got caffeinate event:%s (id:%d)", eventName, event)
local script
if event == hs.caffeinate.watcher.screensDidLock then
script = "${HOME}/.lock"
elseif event == hs.caffeinate.watcher.screensDidUnlock then
script = "${HOME}/.unlock"
else
log.f("ignored event:%s (id:%d)", eventName, event)
return
end
local ok, st, n = os.execute(script)
log.f("exec:%s -> %s, %s, %d", script, ok2str(ok), st, n)
end
):start()
local log = hs.logger.new("", "info")
local function ok2str(ok)
if ok then return "ok" else return "fail" end
end
hs.caffeinate.watcher.new(function(event)
local eventName = hs.caffeinate.watcher[event]
log.f("got caffeinate event:%s (id:%d)", eventName, event)
local script
if event == hs.caffeinate.watcher.screensDidLock then
script = "${HOME}/.lock"
elseif event == hs.caffeinate.watcher.screensDidUnlock then
script = "${HOME}/.unlock"
else
log.f("ignored event:%s (id:%d)", eventName, event)
return
end
local ok, st, n = os.execute(script)
log.f("exec:%s -> %s, %s, %d", script, ok2str(ok), st, n)
end
):start()
然后再 touch ~/.unlock && chmod +x ~/.unlock,内容为:
#!/bin/bash
export PATH=$PATH:/usr/local/bin/
brightness -d 1 0
sleep 5 && brightness -d 1 0
export PATH=$PATH:/usr/local/bin/
brightness -d 1 0
sleep 5 && brightness -d 1 0
sleep 5 是因为从睡眠状态恢复时,识别外屏会比较慢,可按需调整 sleep 时长。
如果你看到这里,说明你也需要注意日常用电脑的姿势了,避免头颈前倾。
(完)。
update @ 2023-12-12
实际体验下来还有一些问题,例如去倒杯水,锁屏但没有睡眠,解锁后就不会息屏。
最好用的方案往往只需要最简单的工具:crontab。
引用
MAILTO=""
* * * * * /Users/bytedance/bin/TurnOffScreen.sh
* * * * * sleep 15 && /Users/bytedance/bin/TurnOffScreen.sh
* * * * * sleep 30 && /Users/bytedance/bin/TurnOffScreen.sh
* * * * * sleep 45 && /Users/bytedance/bin/TurnOffScreen.sh
update @ 2024-01-26
brightness seems to be not working on M3 Macbooks, so I switched to lunar:
1. Download and install lunar
2. Run in terminal: /Applications/Lunar.app/Contents/MacOS/Lunar install-cli
3. Update the TurnOffScreen.sh to be (remember to replace with your monitor model or serial id in the pattern):
#!/bin/bash
pattern='S2721QS'
if ~/.local/bin/lunar displays | grep $pattern; then
~/.local/bin/lunar displays "Built-in" brightness 0
fi
pattern='S2721QS'
if ~/.local/bin/lunar displays | grep $pattern; then
~/.local/bin/lunar displays "Built-in" brightness 0
fi
Sep
1
2009年在 B 厂实习的时候,人手发一个硬件 token,它有个屏幕,实时显示 6 位 TOTP ,用于登录 relay 机器,再通过 relay 机器登录生产环境的机器。
由于 relay 机器禁用了端口转发等各种 fancy 的能力,所以除了 shell 上的输入和输出外无法实现额外的通信。
好在 SecureCRT 默认支持 zmodem 协议,大家都用 sz/rz 来上传/下载文件,也还算方便。
sz 是基于 zmodem 协议实现的,这个功能在早期的 BBS 上非常实用。
简单说就是,通过发送一个特殊的控制串,告诉 terminal 它想要发送一个文件,支持 zmodem 协议的 terminal 会将后续发送的数据保存到本地文件中。
当然具体的协议细节比较复杂,不是本文重点,就不展开了,详情可参考知乎这位老哥的整理 https://zhuanlan.zhihu.com/p/579720546 。
继续发散,我们可以想象一下,如果我们实现 2 个程序 A、B:
1. A 通过 ssh 连接到目标机器,打开 B
2. 用 B 的 stdin/stdout 来「模拟」一个 tcp 连接
3. 在这个「tcp 连接」上用 xtaci/smux 实现多路复用
4. 在 A 实现 socks5 的前端(监听),在 B 实现 socks5 的后端(转发)
这样我们就实现了一个穿透了 relay 的代理。
领导:明天去财务处结一下工资,后天不用来了。
由于 relay 机器禁用了端口转发等各种 fancy 的能力,所以除了 shell 上的输入和输出外无法实现额外的通信。
好在 SecureCRT 默认支持 zmodem 协议,大家都用 sz/rz 来上传/下载文件,也还算方便。
sz 是基于 zmodem 协议实现的,这个功能在早期的 BBS 上非常实用。
简单说就是,通过发送一个特殊的控制串,告诉 terminal 它想要发送一个文件,支持 zmodem 协议的 terminal 会将后续发送的数据保存到本地文件中。
当然具体的协议细节比较复杂,不是本文重点,就不展开了,详情可参考知乎这位老哥的整理 https://zhuanlan.zhihu.com/p/579720546 。
继续发散,我们可以想象一下,如果我们实现 2 个程序 A、B:
1. A 通过 ssh 连接到目标机器,打开 B
2. 用 B 的 stdin/stdout 来「模拟」一个 tcp 连接
3. 在这个「tcp 连接」上用 xtaci/smux 实现多路复用
4. 在 A 实现 socks5 的前端(监听),在 B 实现 socks5 的后端(转发)
这样我们就实现了一个穿透了 relay 的代理。
领导:明天去财务处结一下工资,后天不用来了。
Feb
24
Why: kitex framework is using gofumpt as linter in gitlab CI, but goland defaults to use gofmt, which enforces weaker rules.
Procedure:
0. $ go install mvdan.cc/gofumpt@v0.2.0 # or any version as you need
1. Install file watcher plugin in goland settings;
2. Go to settings -> Tools -> File Watchers:
2.1. Click on "+", and choose "go fmt"
2.2. Replace "Program:" to be the full path of your gofumpt binary
2.3. Modify "Arguments:" to be "-w $FilePath$"
2.4 OK & OK again.
Procedure:
0. $ go install mvdan.cc/gofumpt@v0.2.0 # or any version as you need
1. Install file watcher plugin in goland settings;
2. Go to settings -> Tools -> File Watchers:
2.1. Click on "+", and choose "go fmt"
2.2. Replace "Program:" to be the full path of your gofumpt binary
2.3. Modify "Arguments:" to be "-w $FilePath$"
2.4 OK & OK again.
Dec
26
先实现一个纯 Go 的版本:
修改上述 fac 方法,只保留函数定义(即声明存在该函数,由汇编代码实现):
新增 fac.s
编译运行:
$ go run .
3628800
说明:
1. TEXT ·fac(SB), $0-8
- TEXT 表示这个方法在 TEXT 段中
- · 是Unicode的「中点」(中文输入法,1左边的按键),前面省略了包名,表示这是 main 包的 fac 函数
- SB 是 stack base pointer,Go ASM 中的「伪寄存器」(不是硬件寄存器),大致等同于程序的起始地址
- $0-8:0 表示这个函数没有局部变量,8 表示返回值占用8个字节
2. MOVQ n+0(FP), CX
- MOVQ 的 Q 表示 8 个字节
- n+0(FP) 表示变量 n 在 FP(Frame Pointer,伪寄存器,表示这个函数的栈帧起始位置) + 0 的位置(即第一个参数)。注意这个写法形式上是必须得,但是变量名n没有实际意义,只是用来注记。
- CX 即 x86/x86_64 的 CX(16bit),ECX(32bit),RCX(64bit) 寄存器,具体多长取决于前面的指令(MOVQ是64bit)
- 这句的意思是把第一个参数的值写入 RCX
3. MOVQ $1, DX
- $1:$开头的是立即数
- 这句的意思是给 RDX 赋值为 1
4. IMULQ CX, DX
- DX = DX * CX
5. DECQ CX
- CX = CX - 1
6. JNZ LOOP
- JNZ: Jump if Not Zero
- 当 CX 不等于 0 时跳转到 LOOP
7. MOVQ DX, result+8(FP)
- 将 RDX 的值写入到 FP+8 的位置。
8. RET
- 返回到调用方。
p.s. 这个汇编版本的实现并不等同于原来 Go 版本,只是这样写会更简单(只要一个jump)。
参考:
- Golang ASM 简明教程:https://jiajunhuang.com/articles/2020_04_22-go_asm.md.html
- A Quick Guide to Go's Assembler:https://go.dev/doc/asm
// main.go
package main
import "fmt"
func fac(n uint) uint {
var result uint = 1
for n > 0 {
result = result * n
n -= 1
}
return result
}
func main() {
fmt.Println(fac(10))
}
package main
import "fmt"
func fac(n uint) uint {
var result uint = 1
for n > 0 {
result = result * n
n -= 1
}
return result
}
func main() {
fmt.Println(fac(10))
}
修改上述 fac 方法,只保留函数定义(即声明存在该函数,由汇编代码实现):
func fac(n uint) uint
新增 fac.s
TEXT ·fac(SB), $0-8
MOVQ n+0(FP), CX
MOVQ $1, DX
LOOP:
IMULQ CX, DX
DECQ CX
JNZ LOOP
MOVQ DX, result+8(FP)
RET
MOVQ n+0(FP), CX
MOVQ $1, DX
LOOP:
IMULQ CX, DX
DECQ CX
JNZ LOOP
MOVQ DX, result+8(FP)
RET
编译运行:
引用
$ go run .
3628800
说明:
1. TEXT ·fac(SB), $0-8
- TEXT 表示这个方法在 TEXT 段中
- · 是Unicode的「中点」(中文输入法,1左边的按键),前面省略了包名,表示这是 main 包的 fac 函数
- SB 是 stack base pointer,Go ASM 中的「伪寄存器」(不是硬件寄存器),大致等同于程序的起始地址
- $0-8:0 表示这个函数没有局部变量,8 表示返回值占用8个字节
2. MOVQ n+0(FP), CX
- MOVQ 的 Q 表示 8 个字节
- n+0(FP) 表示变量 n 在 FP(Frame Pointer,伪寄存器,表示这个函数的栈帧起始位置) + 0 的位置(即第一个参数)。注意这个写法形式上是必须得,但是变量名n没有实际意义,只是用来注记。
- CX 即 x86/x86_64 的 CX(16bit),ECX(32bit),RCX(64bit) 寄存器,具体多长取决于前面的指令(MOVQ是64bit)
- 这句的意思是把第一个参数的值写入 RCX
3. MOVQ $1, DX
- $1:$开头的是立即数
- 这句的意思是给 RDX 赋值为 1
4. IMULQ CX, DX
- DX = DX * CX
5. DECQ CX
- CX = CX - 1
6. JNZ LOOP
- JNZ: Jump if Not Zero
- 当 CX 不等于 0 时跳转到 LOOP
7. MOVQ DX, result+8(FP)
- 将 RDX 的值写入到 FP+8 的位置。
8. RET
- 返回到调用方。
p.s. 这个汇编版本的实现并不等同于原来 Go 版本,只是这样写会更简单(只要一个jump)。
参考:
- Golang ASM 简明教程:https://jiajunhuang.com/articles/2020_04_22-go_asm.md.html
- A Quick Guide to Go's Assembler:https://go.dev/doc/asm
May
23
手头项目每次 mvn package 得到的 jar 是 160M 左右,有时候需要替换到服务器上,上传时间较长。
Google 搜到这么个项目:xdelta
https://github.com/jmacd/xdelta-gpl/releases
可以对二进制文件做 patch,对 jar 的效果还挺好,两个相近的版本做 diff,生成的 patch 文件只有 500KB 左右。
用法:
有个小问题是,服务器是 centos 7 ,yum install 的是 xdelta 3.0.7 不支持最新的 lzma 压缩,因此生成 patch 的时候需要加上 -S djw 参数,指定为 djw 编码:
Google 搜到这么个项目:xdelta
https://github.com/jmacd/xdelta-gpl/releases
可以对二进制文件做 patch,对 jar 的效果还挺好,两个相近的版本做 diff,生成的 patch 文件只有 500KB 左右。
用法:
引用
# 生成 patch
xdelta.exe -es v1.jar v2.jar v1-v2.patch
# 应用 patch
xdelta.exe -ds v1.jar v1-v2.patch v2.jar
xdelta.exe -es v1.jar v2.jar v1-v2.patch
# 应用 patch
xdelta.exe -ds v1.jar v1-v2.patch v2.jar
有个小问题是,服务器是 centos 7 ,yum install 的是 xdelta 3.0.7 不支持最新的 lzma 压缩,因此生成 patch 的时候需要加上 -S djw 参数,指定为 djw 编码:
引用
xdelta.exe -S djw -es v1.jar v2.jar v1-v2.patch
May
16
踩了个小坑,记录一下。
swagger api 定义:
/upload:
post:
tags:
- "tag"
summary: "summary"
operationId: uploadFile
consumes:
- multipart/form-data
parameters:
- name: "data"
in: "formData"
type: "file"
required: true
description: "file content"
responses:
200:
description: "success"
schema:
$ref: "#/definitions/UploadFileResponse"
生成的 API:
直接用这个 API 请求会报错:
swagger Required request part 'file' is not present
细看发现是 swagger 生成的是 @RequestPart("file") 而不是 @RequestPart("data"),需要手动修改过来才能正确读取到文件字段。
swagger api 定义:
引用
/upload:
post:
tags:
- "tag"
summary: "summary"
operationId: uploadFile
consumes:
- multipart/form-data
parameters:
- name: "data"
in: "formData"
type: "file"
required: true
description: "file content"
responses:
200:
description: "success"
schema:
$ref: "#/definitions/UploadFileResponse"
生成的 API:
@RequestMapping(value = {"/sf/express/upload_channel_file" }, produces = { "application/json" }, consumes = { "multipart/form-data" }, method = RequestMethod.POST)
default ResponseEntity<UploadFileResponse> uploadFile(@ApiParam(value = "file detail") @Valid @RequestPart("file") MultipartFile data) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
default ResponseEntity<UploadFileResponse> uploadFile(@ApiParam(value = "file detail") @Valid @RequestPart("file") MultipartFile data) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
直接用这个 API 请求会报错:
swagger Required request part 'file' is not present
细看发现是 swagger 生成的是 @RequestPart("file") 而不是 @RequestPart("data"),需要手动修改过来才能正确读取到文件字段。
Feb
14
切到新语言就是不断地踩坑呀。
手头有一个函数,输入一个 itemList,统计不同类型的个数,核心代码如下:
其他 typeParser 在遇到异常数据时会返回 null。
重构的时候,稳妥起见为它写了个单测,结果就在上述代码的第二行 NPE 了。
debug 时确认了 typeParser 和 item 都不是 null,猜测是 switch 不能处理 null ,但不熟悉 java 语法,搜了一下,果然如此。
由于 case 数量不多,就暂且改成了 if ... else if ... else 的结构。
由此可见,单测对于代码的覆盖率确实是很有帮助。
手头有一个函数,输入一个 itemList,统计不同类型的个数,核心代码如下:
int xCount = 0, yCount = 0;
switch(typeParser.parse(item)) {
case X:
xCount++;
break;
case Y:
yCount++;
break;
default:
log.error("unknown type");
break;
}
switch(typeParser.parse(item)) {
case X:
xCount++;
break;
case Y:
yCount++;
break;
default:
log.error("unknown type");
break;
}
其他 typeParser 在遇到异常数据时会返回 null。
重构的时候,稳妥起见为它写了个单测,结果就在上述代码的第二行 NPE 了。
debug 时确认了 typeParser 和 item 都不是 null,猜测是 switch 不能处理 null ,但不熟悉 java 语法,搜了一下,果然如此。
由于 case 数量不多,就暂且改成了 if ... else if ... else 的结构。
由此可见,单测对于代码的覆盖率确实是很有帮助。