关于 Cyrus Yip | 叶寻

自学者,对编程、学习新事物充满热情。喜欢阅读、写作、看动画、玩单机游戏。每天进行正念冥想。

RSS 地址: https://cyrusyip.org/post/index.xml

请复制 RSS 到你的阅读器,或快速订阅到 :

Cyrus Yip | 叶寻 RSS 预览

CSS Grid 布局教程

2024-09-16 00:00:00

CSS Grid 是二维布局方法,也就是用竖线和横线将内容划分成格子,像棋盘一样。本文只介绍常见用法,要了解全部用法请看 MDN Web Docs。推荐大家看完后做文末提到的习题。

概念 #

网格容器(Grid Container)、网格项(Grid Item) #

display: grid | inline-grid 使元素变成网格容器,其子元素叫网格项(其他后代不算),按照网格布局排列。下面代码的 .container 是网格容器,.item 是网格项,.sub-item 不是网格项。

1
2
3
.container {
 display: grid;
}
1
2
3
4
5
6
7
<div class="container">
 <div class="item"></div>
 <div class="item">
 <p class="sub-item"></p>
 </div>
 <div class="item"></div>
</div>

网格线(Grid Line) #

划分网格的竖线(vertical line)或横线(horizontal line)。横线方向和书写方向一致(英文是从左到右),竖线方向是从上到下。同一条网格线可以有多个名称。

虚线是网格线

网格单元(Grid Cell) #

网格布局的最小单位,就像电子表格的单元格和棋盘的格子,相邻的 2 个竖线之间和相邻的 2 条横线之间的区域。1 个网格项可以使用多个网格单元。

网格轨道(Grid Track) #

2 条相邻网格线之间的区域,也就是 1 个横排(row)或者 1 个竖排(column)。

网格区域(Grid Area) #

由 4 条网格线划分的区域,也就是长方形,比如 1 个网格单位、4 个网格单位、6 个网格单位。

示例(A 表示网格区域):

1
2
A???
????
1
2
AA??
AA??
1
2
AAA?
AAA?

显性网格(Explicit Grid)、隐性网格(Implicit Grid) #

显性网格有固定数量的网格轨道,比如 3×3。如果此时加入额外的网格项,网格将自动添加 auto 尺寸的隐性网格轨道,原来的显性网格轨道加上自动添加的隐性网格轨道就是隐性轨道。只定义竖网格线也可以产生隐性网格。

fr(fraction)1 份可用空间 #

fr 表示网格容器的 1 份可用空间。

1
2
3
4
5
6
7
.container {
 display: grid;
 /* 3 个竖排,比例为 1:2:1 */
 grid-template-columns: 1fr 2fr 1fr;
 /* 3 个横排,第 1 横排为 30px,剩余横排比例为 1:1 */
 grid-template-rows: 30px 1fr 1fr;
}

minmax() 最大最小值 #

参考资料:minmax() - CSS: Cascading Style Sheets | MDN

minmax() 函数设置最小值和最大值。下面代码表示第 1 横排的尺寸最小 50px,最大 auto(根据内容自动扩大)。

1
2
3
.container {
 grid-template-rows: minmax(50px, auto) 1fr 1fr;
}

repeat() 函数 #

参考资料:repeat() - CSS: Cascading Style Sheets | MDN

repeat() 函数用于表示重复的网格轨道片段。

在线示例

1
2
3
.container {
 grid-template-rows: repeat(3, 1fr); /* 1fr 1fr 1fr */
}

在线示例

1
2
3
.container {
 grid-template-rows: 2fr repeat(2, 1fr); /* 2fr 1fr 1fr */
}

除了使用固定的数字,还可用 auto-fitauto-fill

网格容器的属性 #

网格容器可以使用以下属性。

display 设置容器 #

对元素使用,使其变成网格容器。

1
2
3
.container {
 display: grid | inline-grid;
}

值:

grid-template-columns、grid-template-rows,网格线名称与网格轨道 #

参考资料:

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item6">6</div>
 <div class="item item7">7</div>
 <div class="item item8">8</div>
 <div class="item item9">9</div>
</div>
1
2
3
4
5
6
7
8
9
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 /* 3 个竖排,比例为 1:2:1 */
 grid-template-columns: 1fr 2fr 1fr;
 /* 3 个横排,第 1 横排为 30px,剩余横排比例为 1:1 */
 grid-template-rows: 30px 1fr 1fr;
}

现在网格项排列成这样:

1
2
3
1 2 3
4 5 6
7 8 9

可以用 [] 定义网格线名称,用空格分隔多个名称。

1
2
3
.container {
 grid-template-columns: [column1-start] 1fr [column2-start] 2fr [column3-start] 1fr [column-end another-name];
}

grid-column-startgrid-row-start 可以改变网格项的位置,在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.container {
 display: grid;
 grid-template-columns: [column1-start] 1fr [column2-start] 2fr [column3-start] 1fr [column-end];
 grid-template-rows: 30px 1fr 1fr;
}

.item9 {
 grid-column-start: column2-start;
 grid-row-start: 2; /* 第 2 条横线 */
}

现在 .item9 占据了 .item5 的位置。

1
2
3
1 2 3
4 9 5
6 7 8

grid-template-areas 网格区域名称 #

参考资料:grid-template-areas - CSS: Cascading Style Sheets | MDN

grid-template-areas 以网格区域名称表示网格的结构。grid-template-areas 的优点是放置网格项时不需要用网格线(数网格线或者命名真的太痛苦了)。相同名称可以用多次,表示占用多个网格单元。英文句号 . 表示不使用此网格单元。grid-area 定义元素对应的网格区域名称。

grid-template-areas 会自动使用 -start-end 命名网格线。header 网格区域的起始网格线(横线与竖线)都是 header-start,终止网格线(横线与竖线)都是 header-end。同一条网格线可以有多个名称。

示例(在线版):

1
2
3
4
5
6
<div class="container">
 <header>header</header>
 <aside>sidebar</aside>
 <main>main</main>
 <footer>footer</footer>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
header,aside,main,footer {
 border: 1px solid green;
}

.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template-areas:
 "header header header header"
 "main main . aside"
 "footer footer footer footer";
 grid-template-columns: 1fr 1fr 1fr 50px;
 grid-template-rows: auto;
}

header {
 grid-area: header;
}
aside {
 grid-area: aside;
}
main {
 grid-area: main;
}
footer {
 grid-area: footer;
}

grid-template(grid-template-rows、grid-template-columns、grid-template-areas 的缩写) #

参考资料:

grid-templategrid-template-rowsgrid-template-columnsgrid-template-areas 的缩写。grid-template 不会重置隐性网格属性。

只设置 grid-template-rowsgrid-template-columns 的用法是用 / 隔开两者。

示例(在线版):

1
2
3
4
5
6
7
8
9
.container {
 display: grid;
 /* 横排比例 1:1:1,竖排比例 1:3:1 */
 grid-template: 1fr 1fr 1fr / 1fr 3fr 1fr;
 /* 等价于
 grid-template-rows: 1fr 1fr 1fr;
 grid-template-columns: 1fr 3fr 1fr;
 */
}

同时设置 grid-template-rowsgrid-template-columnsgrid-template-areas 的用法:

  1. 先写 grid-template-areas
  2. 在每横排右边写上尺寸
  3. 在最后的横排加上 / 和竖排的尺寸(/ 从下一行开始写也行)

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template:
 "header header header" auto
 "main main aside " 1fr
 "footer footer footer" auto / auto auto 50px;
 /* 等价于
 grid-template-areas:
 "header header header"
 "main main aside"
 "footer footer footer";
 grid-template-columns: auto auto 50px;
 grid-template-rows: auto 1fr auto;
 */
}

row-gap、column-gap、gap 间隔 #

参考资料:

row-gap 表示横排之间的间隔,column-gap 表示竖排之间的间隔。gap 是前面两者的缩写,使用 1 个值表示横排间隔和竖排间隔一样,使用 2 个值分别表示横排间隔是竖排间隔。

示例:

1
2
3
4
5
6
.container {
 row-gap: 30px;
 column-gap: 10px;
 gap: 10px; /* 竖排间隔和横排间隔都是 10px */
 gap: 30px 10px; /* 横排间隔 30px,竖排间隔 10px */
}

以前这 3 个属性前面要加上 grid-,比如 grid-row-gap。带 grid- 前缀的属性已被弃用,浏览器为了保持兼容,仍然支持这些属性。

justify-items,网格项的 inline 轴(横轴)对齐 #

参考资料:

justify-items 设置网格项 inline 轴(横轴)对齐方式,默认值为 stretch(占满网格单元宽度)。

1
2
3
.container {
 justify-items: start | end | center | stretch;
}

align-items,网格项的 block 轴(竖轴)对齐 #

参考资料:

align-items 设置网格项 block 轴(竖轴)的对齐方式,默认值为 stretch(占满网格单元高度)。baseline 表示按基线对齐。内容有多行时,first baseline 表示按照首行的基线对齐,last baseline 表示按照尾行的基线对齐。

1
2
3
.container {
 align-items: start | end | center | stretch | baseline | first baseline | last baseline;
}

place-items(align-items、justify-items 的缩写) #

参考资料:

place-itemsalign-itemsjustify-items 的缩写。使用 1 个值同时设置 2 个属性,使用 2 个值分别设置两个属性。

1
2
3
4
.container {
 place-items: center; /* 正中间 */
 place-items: start end; /* 右上角 */
}

justify-content,网格项整体 inline 轴(横轴)对齐 #

参考资料:

如果网格项的总尺寸小于网格容器的尺寸,网格容器会有多于的空白,此时网格项被放置于左上角(使用从左向右的语言)。justify-content 设置 inline 轴(横轴)的对齐方式。Flex 布局也有 justify-content,用法类似,参看 CSS Flexbox 布局教程#justify-content-主轴对齐(发布前看看这个链接对不对)。

在线示例

1
2
3
.container {
 justify-content: start | end | center | stretch | space-around | space-between | space-evenly; 
}

align-content,网格项整体的 block 轴(竖轴)对齐 #

参考资料:

如果网格项的总尺寸小于网格容器的尺寸,网格容器会有多于的空白,此时网格项被放置于左上角(使用从左向右的语言)。align-content 设置 block 轴(竖轴)的对齐方式。Flex 布局也有 align-content,用法类似。

在线示例

1
2
3
.container {
 align-content: start | end | center | stretch | space-around | space-between | space-evenly; 
}

place-content(align-content、justify-content 的缩写) #

参考资料:place-content - CSS: Cascading Style Sheets | MDN

place-contentalign-contentjustify-content 的缩写。用 1 个值同时设置两者,用 2 个值分别设置两者。

示例(在线示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.container {
 place-content: start end;
 /*
 align-content: start;
 justify-content: end;
 */
 
 place-content: center;
 /*
 align-content: center;
 justify-content: center;
 */
}

grid-auto-columns、grid-auto-rows 隐性网格轨道大小 #

参考资料:

如果网格项数量多于已定义的网格项数量,那么多出了网格项就位于隐性网格轨道。grid-auto-columns 设置竖向隐性网格轨道大小,grid-auto-rows 设置横向隐性网格轨道大小,两者默认值都是 auto

在线示例

1
2
3
.container {
 grid-auto-rows: 100px;
}

grid-auto-flow 自动放置算法 #

参考资料:

1
2
3
.container {
 grid-auto-flow: row | column | row dense | column dense;
}

grid-auto-flow 设置网格项的放置算法。

row(默认值):横向放置网格项,必要时添加新的横排。

1
2
3
1 2 3
4 5 6
7 8 9

column:竖向放置网格项,必要时添加新的竖排,在线示例

1
2
3
1 4 7
2 5 8
3 6 9

dense 关键词表示后面的元素可以移动到前面的空位,示例(在线示例):

1
2
3
4
5
6
7
8
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item6">6</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
.item {
 border: 1px solid green;
}

.container {
 border: 1px solid red;
 height: 200px;
 width: 200px;
 display: grid;
 grid-template-columns: 1fr 1fr 1fr;
 grid-auto-flow: row dense; /* .item3 提前放置于 .item1 右边 */
}

.item1, .item2 {
 grid-column: span 2; /* 2fr */
}

grid( grid-template-rows、grid-template-columns、grid-template-areas、grid-auto-rows、grid-auto-columns、grid-auto-flow 的缩写) #

参考资料:

gridgrid-template-rowsgrid-template-columnsgrid-template-areasgrid-auto-rowsgrid-auto-columnsgrid-auto-flow 的缩写。

创建显性网格时用法与 grid-template 一样。

创建隐性网格时有 2 种用法(左边设置横排,右边设置竖排,中间以 / 分隔):

  1. 显性横排,隐性竖排,grid-auto-flowcolumn<grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>?
  2. 隐性横排,显性竖排,grid-auto-flowrow[ auto-flow && dense? ] <grid-auto-rows>? / <grid-template-columns>

显性横排隐性竖排示例(在线版):

1
2
3
4
.container {
 /* 横排比例 1:2:1,按照竖排方向放置网格项,竖排尺寸 50px */
 grid: 1fr 2fr 1fr / auto-flow 50px;
}

隐性横排显性竖排示例(在线版):

1
2
3
4
.container {
 /* 隐性横排,按照横排方向放置网格项,竖排比例 1:2:1 */
 grid: auto-flow / 1fr 2fr 1fr;
}

网格项的属性 #

网格项可以使用以下属性:

grid-column-start、grid-column-end、grid-row-start、grid-row-end 网格项位置 #

参考资料:

这几个属性通过网格线的起点与终点规定 1 个网格项的位置,默认值为 auto(自动放置)。span 数字 表示占用多少个网格轨道,不加数字时为 1。

示例(在线版):

1
2
3
4
5
6
7
8
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item-x">x</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template: 1fr 1fr 1fr / 1fr [col2-start] 1fr 1fr;
}

.item {
 border: 1px solid green;
}

.item-x {
 /* 从 col2-start 竖网格线到倒数第 1 条竖网格线 */
 grid-column-start: col2-start;
 grid-column-end: -1;
 /* 从第 2 条横网格线开始,占用 2 个横向网格轨道 */
 grid-row-start: 2;
 grid-row-end: span 2;
}

grid-column(grid-column-start、grid-column-end 的缩写)、grid-row(grid-row-start、grid-row-end 的缩写) #

参考资料:

grid-columngrid-column-start / grid-column-end 的缩写,使用 1 个值时只设置 grid-column-start

grid-rowgrid-row-start / grid-row-end 的缩写,使用 1 个值时值设置 grid-row-start

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.item-x {
 grid-column: col2-start / -1;
 grid-row: 2 / span 2;
 /*
 grid-column-start: col2-start;
 grid-column-end: -1;
 grid-row-start: 2;
 grid-row-end: span 2; 
 */
}

grid-area(grid-row-start、grid-column-start、grid-row-end、grid-column-end 的缩写) #

grid-area 有两个用法。一是 grid-row-start / grid-column-start / grid-row-end / grid-column-end 的缩写(吐槽:这个顺序不好读,-start 后面应该跟 -end);二是搭配 grid-template-area,使用名称指定位置。

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.item-x {
 grid-area: 2 / col2-start / span 2 / -1;
 
 /*
 grid-column: col2-start / -1;
 grid-row: 2 / span 2;
 
 grid-column-start: col2-start;
 grid-column-end: -1;
 grid-row-start: 2;
 grid-row-end: span 2; 
 */
}

justify-self,inline 轴(横轴)对齐 #

justify-self 设置 1 个网格项的 inline 轴(横轴)对齐方式,默认值是 stretch

1
2
3
.item {
 justify-self: start | end | center | stretch;
}

在线示例

1
2
3
.item4 {
 justify-self: center;
}

align-self,block 轴(竖轴)对齐 #

align-self 设置 1 个网格项的 block 轴(竖轴)对齐方式,默认是 stretch

1
2
3
.item {
 align-self: start | end | center | stretch;
}

在线示例

1
2
3
.item4 {
 align-self: end;
}

place-self(align-self、justify-self 的缩写) #

参考资料:

place-selfalign-self justify-self 的缩写,只使用 1 个值时同时设置两者。

示例:

1
2
3
4
.item4 {
 place-self: center; /* 正中间 */
 place-self: end start; /* 左下角 */
}

subgrid 继承网格容器属性 #

参考资料:Subgrid - CSS: Cascading Style Sheets | MDN

虽然 subgrid 不是属性,但用于网格项,所以列举于此。

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="container">
 <div class="item item1">Item1</div>
 <div class="item item2">Item2</div>
 <div class="item item3">Item3</div>
 <div class="item item4">Item4</div>
 <div class="item item5">Item5</div>
 <div class="item item6">Item6, 4×4
 <div class="item6-1">Item6.1, 1×1</div>
 <div class="item6-2">Item6.2, 1×1</div>
 </div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template: 1fr 1fr 1fr / 1fr 1fr 1fr;
 gap: 10px;
}

.item {
 border: 1px solid green;
}

.item6 {
 border: 1px solid yellow;
 grid-area: 2 / 2 / span 2 / span 2;
 display: grid;
 /*
 用 subgrid 才能继承容器的 gap 属性,
 可以删掉下面两行代码看看差异 */
 grid-template-columns: subgrid;
 grid-template-rows: subgrid;
}

.item6-1 {
 border: 1px solid blue;
 /* 使用 .item6 的网格线编号,不要用 .container 的网格线编号 */
 grid-column-start: 2;
 grid-row-start: 2;
}

.item6-2 {
 border: 1px solid blue;
 grid-column-start: 2;
 grid-row-start: 1;
}

order 顺序 #

参考资料:order - CSS: Cascading Style Sheets | MDN

网格项默认按照源代码顺序出现。order 设置网格项的出现顺序,默认值为 0,可使用正数和负数。

示例(在线版):

1
2
3
4
5
6
.item1 {
 order: 1;
}
.item9 {
 order: -1;
}

待写内容 #

参考资料 #

图片出处 #

本文使用的图片出自 CSS Grid Layout Module Level 1#grid-concepts

练习 #

CSS BEM 命名规范入门教程

2024-09-08 00:00:00

BEM(Block, Element, Modifier)是 HTML/CSS 类的命名方法,它可以让 HTML 和 CSS 代码更有条理。

概念与用法 #

一开始看不懂没关系,后面有示例。

HTML 示例 #

<nav> 元素是 block,它包含的 element 有 <ul><li><a>--active 是修饰符。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<nav class="menu">
 <ul class="menu__list">
 <li class="menu__item">
 <a class="menu__link menu__link--active" href="/zh-cn/">主页</a>
 </li>
 <li class="menu__item">
 <a class="menu__link" href="/zh-cn/posts/">文章</a>
 </li>
 <li class="menu__item">
 <a class="menu__link" href="/zh-cn/about/">关于</a>
 </li>
 </ul>
</nav>

CSS 示例 #

在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* 横向列表 */
.menu__list {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: space-between;
 list-style-type: none;
 padding-inline-start: 0;
}

/* 加粗和当前页面匹配的链接 */
.menu__link--active {
 font-weight: bolder;
}

SCSS 示例 #

我更推荐用 SCSS,用父选择器 & 可以把 block 和 element 的样式都放在一起,这种结构可以清晰地展现它们的层级关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.menu {
 // 横向列表
 &__list {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: space-between;
 list-style-type: none;
 padding-inline-start: 0;
 }

 // 加粗和当前页面匹配的链接
 &__link {
 &--active {
 font-weight: bolder;
 }
 }
}

如果你还是很讨厌写类名,可以试试 atomic CSS 框架,比如:Tailwind CSSUnoCSS

补充资料 #

CSS Flexbox 布局教程

2024-09-06 00:00:00

Flexbox(弹性盒子)是一维的布局方法,也就是在一条横线(row)或者竖线(column)上排列元素。推荐大家看完本文后做文末提到的习题

flex 容器的属性 #

flex 容器(flex container)的属性有 displayflex-directionflex-wrapflex-flowjustify-contentalign-itemsalign-contentrow-gapcolumn-gapgap

flex 容器与 flex 项 #

以下的 section 元素包含了 3 个 article 元素。

1
2
3
4
5
<section>
 <article>Article 1</article>
 <article>Article 2</article>
 <article>Article 3</article>
</section>

要排列 article 元素,将它们的父元素设置为 flex 容器。

1
2
3
section {
 display: flex | inline-flex; /* inline-flex 表示容器是 inline 元素 */
}

在上述例子中,section 元素是 flex 容器(flex container),article 元素是 flex 项(flex item)。

flex-direction 方向、主轴、交叉轴 #

flex-direction 定义排列方向和主轴。

1
2
3
.container {
 flex-direction: row | row-reverse | column | column-reverse;
}

主轴(main axis)就是和 flex 项排列方向一致的轴,交叉轴(cross axis)则是与主轴垂直的轴。一定要分清楚方向和两条轴,在对齐 flex 项时会用到。

1
2
3
4
.container {
 display: flex;
 flex-direction: row;
}

上述代码表示从左到右排列 flex 项,主轴是从左到右,交叉轴是从上到下。

轴的图示

flex-wrap 换行 #

flex-wrap 控制是否换行,默认不换行。

1
2
3
.container {
 flex-wrap: nowrap | wrap | wrap-reverse;
}

示例代码(在线示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<section>
 <article>Article 1</article>
 <article>Article 2</article>
 <article>Article 3</article>
 <article>Article 4</article>
 <article>Article 5</article>
 <article>Article 6</article>
 <article>Article 7</article>
 <article>Article 8</article>
 <article>Article 9</article>
</section>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
section {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap-reverse;
}

article {
 width: 100%;
 outline: 1px solid green;
}

flex-flow(flex-direction 和 flex-wrap 的缩写) #

flex-flowflex-directionflex-wrap 的缩写,默认值为 row nowrap

下面两份代码效果一样:

1
2
3
4
section {
 flex-direction: row;
 flex-wrap: wrap;
}
1
2
3
section {
 flex-flow: row wrap;
}

justify-content 主轴对齐 #

justify-content 设置主轴的对齐方式。

1
2
3
.container {
 justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly | start | end | left | right ... + safe | unsafe;
}

下面只介绍常用的值。

align-items 交叉轴对齐 #

交叉轴垂直于主轴。align-items 设置交叉轴的对齐方式。

1
2
3
.container {
 align-items: stretch | flex-start | flex-end | center | baseline | first baseline | last baseline | start | end | self-start | self-end + ... safe | unsafe;
}

align-items 用法和 justify-content 类似,这里介绍一些不一样的值。

align-content 交叉轴对齐(仅用于多行 flex 项) #

align-content 设置多行 flex 项的交叉轴对齐方式,flex 项只有一行时(flex-wrap: nowrap;)无效,图示

1
2
3
.container {
 align-content: flex-start | flex-end | center | space-between | space-around | space-evenly | stretch | start | end | baseline | first baseline | last baseline + ... safe | unsafe;
}

align-content 用法和 align-items 类似。

row-gap、column-gap、gap 间隔 #

如果在 flex 项上画横线和竖线,row-gap 表示横线的间隔大小,column-gap 表示竖线的间隔大小,gap 是前面两者的缩写,在线示例

1
2
3
4
5
6
7
8
.container {
 display: flex;
 flex-wrap: wrap;
 row-gap: 20px;
 column-gap: 5px;
 gap: 20px 5px; /* row-gap, column-gap */
 gap: 20px; /* row-gap 和 column-gap 都是 20px */
}

gap 只是设置最小间隔,使用 space-between 可以加大间隔。

flex 项的属性 #

flex 项(flex item)的属性有 orderflex-growflex-shrinkflex-basisflexalign-self

flexbox 没有 justify-self,参看 Box alignment in flexbox - CSS: Cascading Style Sheets | MDN#there_is_no_justify-self_in_flexbox

order 顺序 #

默认情况下,flex 项按照源代码出现的顺序排列。使用 order 属性可以改变顺序,默认值为 0,数字越大位置越后,可以使用负值(比如:-1)。如果多个 flex 项的 order 值一样,它们还是按原始顺序排列。

1
2
3
4
5
6
7
8
9
#item1 {
 order: 2;
}
#item2 {
 order: 1;
}
#item3 {
 order: -1;
}

图示:

1
item3 | item2 | item1

flex-basis 大小 #

flex-basis 设置 flex 项的大小,默认为 auto

1
2
3
.item {
 flex-basis: 50px;
}

flex-grow 增长 #

flex 容器有多余空间时,flex-grow 设置 flex 项的增长系数,默认为 0(不增长),负值无效。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.item1 {
 flex-grow: 1;
}

.item2 {
 flex-grow: 2;
}

.item3 {
 flex-grow: 1;
}

上面代码表示将多余空间(假设为 100px)均分为 4 份,.item1 的宽度增加 1 份(25px),.item2 的宽度增加 2 份(50px),.item3 的宽度增加 1 份(25px),在线示例

flex-shrink 收缩 #

空间不足时,flex-shrink 设置 flex 项的收缩指数,默认为 1(可收缩),0 表示不可收缩,负值无效。flex-shrink 的效果和 flex-grow 类似。

假设有 1 个 200px 宽度的 flex 容器,它包含 3 个 flex 项(每个宽度 100px,共 300px),此时有 100px 溢出的宽度。

1
2
3
4
5
6
7
.container {
 display: flex;
 width: 200px;
}
.item {
 flex-basis: 100px;
}

如果 flex 项总宽度减去 100px,那么就不会溢出,在线示例

1
2
3
4
5
6
7
8
9
.item1 {
 flex-shrink: 1;
}
.item2 {
 flex-shrink: 2;
}
.item3 {
 flex-shrink: 1;
}

上面代码表示把溢出的 100px 均分为 4 份。.item1 宽度减去 1 份(25px),变成 75px;.item2 宽度减去 2 份(50px),变成 50px;.item3 宽度减去 1 份(25px),变成 75px。

flex(flex-grow、flex-shrink 和 flex-basis 的缩写) #

flexflex-growflex-shrinkflex-basis 的缩写,默认为 0 1 auto。推荐使用 flex 代替那 3 个属性,它会自动使用合理的默认值。比如使用 flex: 1 设置 flex-grow: 1 时,flex-basis 会变成 0%。

flex 可以使用 1~3 个值,具体用法请看 flex - CSS: Cascading Style Sheets | MDN

使用 flex 可以轻松设置 flex 项的大小比例,在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
.container {
 display: flex;
 flex-direction: row;
 width: 100px;
}

.item1 { /* 25px */
 flex: 1;
}
.item2 { /* 50px */
 flex: 2;
}
.item3 { /* 25px */
 flex: 1;
}

align-self 交叉轴对齐 #

align-self 设置单个 flex 项的交叉轴对齐方式,用法和 align-items 一样,两者可以同时用。

习题 #

参考资料 #

2024-08-03 本站改动:使用 Yue 主题和 giscus 评论,post 目录改为 posts

2024-08-03 00:00:00

本文列举了本站于2024年7月28日至2024年08月03日的改动。

改动很多,最好在 Git 仓库建一个分支搭配子域名(如 test.cyrusyip.org)测试,没问题再合并代码至主分支。修改网站要注意功能和链接(URL)变动,改了链接要修改文章内部链接、重定向、迁移评论。

清理无用文件 #

不再使用以下软件/服务:

删除相关文件,修订自述文件和 package.json

将主题换为 Yue #

先构建一次,保存起来

1
2
3
4
cd cyrusyip-blog
mkdir ~/Desktop/blog-builds
rm -r public; npx hugo # hugo 前面多了 npx 是因为我用 npm 安装 hugo
mv public ~/Desktop/blog-builds/jane

删除 Jane 主题和相关文件,重命名配置和中文内容文件夹。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 删除相关文件
rm -r assets layouts resources
git add --all
git commit --message 'chore: remove custom files related to jane theme'
# 删除主题
git submodule deinit themes/jane # 修改 .git/config
git rm themes/jane # 修改 .gitmodules
rm -rf .git/modules/themes/jane # 删除本地文件
git commit --message 'feat!: remove jane theme'
# 重命名
mv config.toml _config.toml
mv content/zh-cn content/zh-CN
git add --all
git commit --message 'refactor: rename to content/zh-CN and _config.toml'

安装主题。

1
2
git submodule add --depth=1 https://github.com/CyrusYip/hugo-theme-yue themes/hugo-theme-yue
git commit --message 'feat: add hugo-theme-yue'

写新配置,对照原来的配置(_config.toml)。删掉了 keywords、description、Google Analytics、Disqus、utterances,后面再加回来。

1
2
3
cp themes/hugo-theme-yue/exampleSite/hugo.yaml .
# 写好新配置后删除旧配置
rm _config.toml

构建,对比目录名和文件名变化,如果没变化表示 URL(链接)没变化。用 tree 命令列举两次构建的文件,复制到剪切板,使用 Diffchecker 对比。

1
2
3
4
5
rm -r public; npx hugo
# 旧主题
tree ~/Desktop/blog-builds/jane | wl-copy
# 新主题
tree ~/Desktop/blog-builds/yue | wl-copy

新主题编译的网站少了一些没用到的页码,这里无需处理。新网站还多了一篇 2021-01-29-python-calculate-utility-bills.Rmarkdown,这是 Rmarkdown 文件,不应该出现于此,在配置文件忽略它。

1
2
# hugo.yaml
ignoreFiles: ["\\.Rmd$", "\\.Rmarkdown$", "_cache$", "\\.knit\\.md$", "\\.utf8\\.md$"]

构建网站后再对比,没这个 .Rmarkdown 文件了。

对比:

用肉眼对比新旧网站,看看有没有问题。看样式、文章数量、日期等。

添加本站原有的功能 #

将 utterances 评论改为 giscus #

另建一个站点测试会比较稳妥,但这次为了节约时间就直接改了。

giscusGitHub Discussions 驱动,功能比 utterances 强,支持多语言和回复评论。

转换步骤:

  1. 删除网站的 utterances
  2. 给相关 issue 打标签「Comments」,issue 不多我就直接在网页操作了,一次可以改一页,多的话用 gh issue edit 命令
  3. 开启 Discussion,新建类别「Comments」
  4. 测试 giscus 是否能新建讨论,标题是否正确
  5. 转换 issue(Comments 标签)为 discussion( Comments 类别),参看 Managing discussions - GitHub Docs#converting-issues-based-on-labels。页面显示「Open issues with label ‘Comments’ are being converted to discussions」,但是过了一两个小时都没反应,最后手动转换了。
  6. 创建 giscus.json 配置文件,限制域名,参看 giscus/ADVANCED-USAGE.md#origins
  7. 在 GitHub 卸载 utterances
  8. 把仓库名 blog-comment 改为 blog-comments,去 giscus 官网重新获取脚本(非必须步骤,这里存了超过一则评论,所以用复数 comments)

post 文件夹改为 posts #

之前用的 Even 主题Jane 主题 要求内容放在 content/post 文件夹,但是标签(/tags/)和类别(/categories/)用的是复数,我觉得这非常不协调。这次换主题终于可以改成复数 posts 了。

改了之后网站链接(URL)会变化,需要重定向旧链接、修改文章内链接、迁移评论,参考我之前写的文章(真是帮了大忙):

重命名文件夹 #

构建未修改的网站并保存。

1
2
3
cd cyrusyip-blog
rm -r public; npx hugo
mv public ~/Desktop/blog-builds/before

重命名文件夹。

1
2
mv content/en/post content/en/posts
mv content/zh-CN/post content/zh-CN/posts

预览并修改配置。

1
rm -r public; npx hugo server --navigateToChanged --bind 0.0.0.0
1
2
3
4
5
6
7
8
--- a/hugo.yaml
+++ b/hugo.yaml

- post: /post/:year/:month/:day/:slug/
+ posts: /posts/:year/:month/:day/:slug/

- pageRef: /post
+ pageRef: /posts
1
2
3
4
5
6
7
8
--- a/frontmatter.json
+++ b/frontmatter.json

- "path": "[[workspace]]/content/en/post",
+ "path": "[[workspace]]/content/en/posts",

- "path": "[[workspace]]/content/zh-CN/post",
+ "path": "[[workspace]]/content/zh-CN/posts",
1
2
git add --all
git commit --message "refactor: content/*/post -> content/*/posts"

关掉 Hugo 服务器,构建修改后的网站并保存。

1
2
rm -r public; npx hugo
mv public ~/Desktop/blog-builds/after

复制两个网站目录的结构到剪切板,用 Diffchecker 对比。

1
2
3
4
# 旧网站,Original text
tree ~/Desktop/blog-builds/before | wl-copy
# 新网站,Changed text
tree ~/Desktop/blog-builds/after | wl-copy

就只是 post 目录变成了 posts

1
2
3
4
5
- /en/post
+ /en/posts

- /zh-cn/post
+ /zh-cn/posts

修改文章内部链接 #

要将 [本站的某文章](/zh-cn/post/2021/01/01/hi/) 这样的链接改成 [本站的某文章](/zh-cn/posts/2021/01/01/hi/)。 获取包含链接的文件,并删掉不需要改的链接。

1
2
3
mkdir todo
rg '\[.*\]\(/.*\)' content --only-matching >> todo/internal-links
rg 'cyrusyip\.org' content >> todo/internal-links

链接记录节选:

1
2
3
content/zh-CN/posts/2021-06-19-ac2100-openwrt.md:[红米 AC2100 刷 breed 后刷回官方固件](/zh-cn/post/2022/10/16/ac2100-stock-firmware/)
content/zh-CN/posts/2021-08-30-raspberry-pi-4b-openwrt.md:[在 OpenWrt 控制树莓派 Argon Mini Fan](/zh-cn/post/2021/09/15/openwrt-argon-mini-fan/)
content/zh-CN/posts/2021-08-30-raspberry-pi-4b-openwrt.md:[树莓派 4B 超频教程](/zh-cn/post/2021/09/20/raspberry-pi4-overclock/)

自己手动改链接,改一条就删去一条记录。使用 hugo server --navigateToChanged 命令预览网站可以很方便地查看变化。

我一开始让 ChatGPT 写 Node.js 脚本帮我改,多番交流后它生成了一些不存在的链接,看来这个工作还是太难为它。

重定向 #

现在本站使用 Cloudflare Pages 构建,参考 Redirects · Cloudflare Pages docs 设置重定向。我有点搞不懂我之前写的一些重定向规则,所以要写好注释,未来的我会感谢现在写注释的我。

1
2
3
4
5
6
7
# static/_redirect
######################################################################
# Rename "content/{en,zh-CN}/post" to "content/{en,zh-CN}/posts"
# URL changes: /en/post/ -> /en/posts/ , /zh-cn/post/ -> /zh-cn/posts/
######################################################################
/en/post/* /en/posts/:splat 308
/zh-cn/post/* /zh-cn/posts/:splat 308

测试链接:

关闭评论 #

关闭 Disqus 和 giscus 评论,以免迁移时有读者留言。

迁移 Disqus 评论 #

参考教程:How to download, edit, and upload a URL Map CSV | Disqus

打开迁移工具,点击「Start URL mapper」->「you can download a CSV here」,去邮箱拿到下载链接,下载并解压。这个 CSV 里面的链接非常乱,有无评论的、带查询参数的(/?utterances=xxx)、失效链接。建一个 Git 仓库,把 CSV 文件放进去并提交,后面方便看改动。

修改 CSV,留下以 https://cyrusyip.org/zh-cn/posthttps://cyrusyip.org/en/post 开头的。

文件节选:

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/

在 Vim 执行 :%s/.*/&,&,现在每行都有两个一样的链接,提交改动。

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/,https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/,https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/

执行 :%s/\v(.*)(post)/\1posts,现在把每行第二个链接的 post 改成 posts,提交改动。

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/,https://cyrusyip.org/zh-cn/posts/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/,https://cyrusyip.org/zh-cn/posts/2021/01/10/remove-odor/

把修改后的 CSV 文件上传到 Disqus 的 URL mapper。

迁移 giscus 评论 #

将标题的 post 改为 posts

示例:

1
2
- zh-cn/post/2024/04/05/i-hate-obscure-words/
+ zh-cn/posts/2024/04/05/i-hate-obscure-words/

GitHub CLI 不支持修改 Discussion,又得手动改了。

查看评论 #

这几篇文章有 Disqus 评论,打开看看是否有评论。

Discussions 里找几篇文章,打开看看是否有评论。

开启评论 #

可以看到评论,重新打开 Disqus 和 giscus。

Hugo:自定义标签标题

2024-07-09 00:00:00

背景 #

一开始我用 Hugo 做中文网站时打算在文章里用中文标签,但是链接里面的中文会被转换为人类读不懂的字符。比如:/zh-cn/tags/博客/ 会变成 /zh-cn/tags/%E5%8D%9A%E5%AE%A2/。我不喜欢这样,当时搜索一番也没找到办法,就用英文标签了。现在终于知道怎么改了,最终改为链接里面还是用英文,但是页面就显示中文。

下面是针对 hugo-theme-jane(ef8a126)主题的更改方法,方法是通用的,但是不同主题修改的部分可能不同。开始前先厘清两个概念:标签名(tag name)、标签标题(tag title)。在文章前页(front matter)写的是标签名,用于链接;在 _index.md 文件定义的是 title 是标签标题,它会显示于页面。

1
2
3
4
5
6
---
# /content/zh-cn/post/2021-03-21-dongman.md
title: 动漫这个词不能再用了
tags:
 - anime # 标签名
---
1
2
3
4
---
# /content/zh-cn/tags/anime/_index.md
title: 日本动画 # 标签标题
---

如果你启用了 GitInfo 而且不想更改 lastmod,请参考 Hugo:给文章添加 lastmod(上次修改时间)

修改标签单页标题(/zh-cn/tags/*/ #

以 anime(日本动画)为例。先自定义一个标签的标题。

1
2
mkdir --parents content/zh-cn/tags/anime/
touch content/zh-cn/tags/anime/_index.md

添加内容:

1
2
3
4
---
# /content/zh-cn/tags/anime/_index.md
title: 日本动画
---

文章前页(front matter)的 tags 还是保持用 anime。

1
2
3
4
5
6
---
# /content/zh-cn/post/2021-03-21-dongman.md
title: 动漫这个词不能再用了
tags:
 - anime
---

现在 /zh-cn/tags/anime/ 会显示「日本动画」。

修改文章底下的标签标题(/zh-cn/post/* #

在浏览器 inspect 找到 CSS 类,然后去 VS Code 搜索。

本地预览网站。随便打开一篇文章(http://localhost:1313/zh-cn/post/2022/12/03/ajin-oad/),滚动到底部的标签,右击 Inspect,可以看到对于的 HTML 代码。

1
2
3
<div class="post-tags">
 <a href="http://localhost:1313/zh-cn/tags/anime/">anime</a>
</div>

搜索主题目录。

1
rg '<div class="post-tags">' themes/jane

可以发现此代码就在 themes/jane/layouts/post/single.html,将其复制到 layouts/post/(我之前复制过,这次跳过此步骤)。

编辑 layouts/post/single.html

找到下面这段代码:

1
2
3
4
5
6
7
8
<div class="post-tags">
 {{ range . }}
 {{- $name := . -}}
 {{- with $.Site.GetPage "taxonomy" (printf "tags/%s" $name) | default ($.Site.GetPage "taxonomy" (printf "tags/%s" ($name | urlize))) -}}
 <a href="{{ .Permalink }}">{{ $name }}</a>
 {{ end -}}
 {{ end }}
</div>

<a href="{{ .Permalink }}">{{ $name }}</a> 里面的 $name 改为 .LinkTitle。改完是这样的:

1
<a href="{{ .Permalink }}">{{ .LinkTitle }}</a>

现在 http://localhost:1313/zh-cn/post/2021/03/21/dongman/ 底下会显示标签「日本动画」。

修改标签列表页面(/zh-cn/tags/ #

用前面的方法 Inspect 元素并搜索代码。

1
rg '<div class="tag-cloud-tags">' themes/jane

可以发现标签列表页面的模板为 themes/jane/layouts/_default/terms.html,复制一份到根目录再改。

1
2
3
4
mkdir --parents layouts/_default/
cp themes/jane/layouts/_default/terms.html layouts/_default/
git add layouts/_default/terms.html
git commit --message 'chore: copy layouts/_default/terms.html'

打开 layouts/_default/terms.html,搜索 tag-cloud-tags,找到这段代码。

1
2
3
4
5
6
<div class="tag-cloud-tags">
...
 <a href="{{ .Permalink }}"
 style="font-size:{{$currentFontSize}}{{$fontUnit}}">{{ $tagName }}</a>
...
</div>

将里面的 $tagName 改为 .LinkTitle

1
2
<a href="{{ .Permalink }}"
 style="font-size:{{$currentFontSize}}{{$fontUnit}}">{{ .LinkTitle }}</a>

提交改动。

1
2
git add layouts/_default/terms.html layouts/post/single.html
git commit --message 'feat: use tag title'

修改所有标签名,加上标签标题 #

标签名全部改成全小写,以连字符分割。改动比较多,新建一个分支,提交完再合并到主分支。

1
git switch --create customize-tag-titles

使用 VS Code 插件 Front Matter CMS 修改标签名。

加标签标题就比较繁琐,要自己创建文件。我让 ChatGPT 给我写了个脚本,好使!

我说的话:

I am customizing tag titles in a Hugo website. I want you to create a CLI program.

1
create-tag-title directory tag-name tag-title

If I run

1
create-tag-title content/en arch-linux 'Arch Linux'

the program should create content/en/tags/arch-linux/_index.md, the content of _index.md should be:

1
2
3
---
title: Arch Linux
---

ChatGPT 给的 Python 脚本(Shebang 是我加的1):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python 
import os
import sys

def create_tag_title(directory, tag_name, tag_title):
 # Construct the path to the tag directory
 tag_dir = os.path.join(directory, 'tags', tag_name)
 
 # Make the directories if they don't exist
 os.makedirs(tag_dir, exist_ok=True)
 
 # Define the path for the _index.md file
 index_file_path = os.path.join(tag_dir, '_index.md')
 
 # Define the content of the _index.md file
 index_content = f"""---
title: {tag_title}
---
"""
 # Write the content to the _index.md file
 with open(index_file_path, 'w') as index_file:
 index_file.write(index_content)

 print(f"Created {index_file_path} with title: {tag_title}")

if __name__ == '__main__':
 # Ensure the correct number of arguments are provided
 if len(sys.argv) != 4:
 print("Usage: create-tag-title directory tag-name tag-title")
 sys.exit(1)

 # Read the arguments from the command line
 directory = sys.argv[1]
 tag_name = sys.argv[2]
 tag_title = sys.argv[3]

 # Call the function to create the tag title file
 create_tag_title(directory, tag_name, tag_title)

自己写提交信息还是太繁琐,让 ChatGPT 把这部分也加上。

我说的话:

Besides creating the _index.md, I want to stage content/ directory and commit. Example:

1
git add content/; git commit -m 'tag-title: arch-linux -> Arch Linux'

ChatGPT 返回的代码(Shellbang 是我加的):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/env python
import os
import sys
import subprocess

def create_tag_title(directory, tag_name, tag_title):
 # Construct the path to the tag directory
 tag_dir = os.path.join(directory, 'tags', tag_name)
 
 # Make the directories if they don't exist
 os.makedirs(tag_dir, exist_ok=True)
 
 # Define the path for the _index.md file
 index_file_path = os.path.join(tag_dir, '_index.md')
 
 # Define the content of the _index.md file
 index_content = f"""---
title: {tag_title}
---
"""
 # Write the content to the _index.md file
 with open(index_file_path, 'w') as index_file:
 index_file.write(index_content)

 print(f"Created {index_file_path} with title: {tag_title}")

 # Stage the content directory
 try:
 subprocess.run(['git', 'add', directory], check=True)
 print(f"Staged {directory}")

 # Commit the changes
 commit_message = f'tag-title: {tag_name} -> {tag_title}'
 subprocess.run(['git', 'commit', '-m', commit_message], check=True)
 print(f"Committed with message: '{commit_message}'")
 except subprocess.CalledProcessError as e:
 print(f"Error during git operation: {e}")

if __name__ == '__main__':
 # Ensure the correct number of arguments are provided
 if len(sys.argv) != 4:
 print("Usage: create-tag-title directory tag-name tag-title")
 sys.exit(1)

 # Read the arguments from the command line
 directory = sys.argv[1]
 tag_name = sys.argv[2]
 tag_title = sys.argv[3]

 # Call the function to create the tag title file
 create_tag_title(directory, tag_name, tag_title)

我使用的 Python 版本是 3.12.4。

检查链接变化 #

可以在修改前后构建网站,用于对比。如果链接有变化,需要重定向旧链接。改了一堆东西,我已经没心思检查了,忽略此步骤。

参考资料 #


  1. 写完文章我才想到可以让 ChatGPT 加 Shebang ↩︎

菜谱:煎荷包蛋

2024-07-05 00:00:00

材料与工具 #

第一次煎蛋的流程 #

  1. 打蛋到碗里
  2. 打开计时器记录热锅时间(后面会用到),电磁炉开到最高档位(2200w)。用手沾一点点水甩入锅,如果直接蒸发就是温度不够,如果水珠弹跳说明温度够了,记下热锅时间,重置计时器,等水珠蒸发。
  3. 暂停电磁炉,倒油,摇晃锅使锅底都被油覆盖
  4. 倒蛋,马上调到 500W1,按下计时器
  5. 等大概 1.5 分钟。等待期间可用锅铲轻推蛋的底部,若不能推动则等待15秒再推,重复此过程直至蛋可以推开。这段时间可以加白胡椒粉2调味。
  6. 铲蛋,用筷子夹住边缘并慢慢地翻面
  7. 大概煎40秒,试着用锅铲轻推,如果不行就等待后再推,能推开就可以铲起来吃了。

继续煎蛋的流程 #

  1. 如果粘锅了,洗锅、烧干水,从头开始
  2. 如果没粘锅,铲走残渣后按照第一次的流程煎蛋
  3. 注意现在锅里有油,不要洒水测试温度,水和油混合会炸开,请参考第一次记录的热锅时间进行热锅

窍门 #


  1. 我这个电磁炉 500W 时是间隔加热,持续加热的电磁炉应该也是可以的 ↩︎

  2. 胡椒研磨成粉后香味会散发,不推荐用成品胡椒粉,推荐用胡椒加研磨器 ↩︎

Hugo:补全文章的 ISO 8601 日期

2024-05-25 00:00:00

背景 #

本网站使用 Hugo 构建,文章日期(date)是这样的:

1
date: '2020-10-02'

这里缺少了时间和时区。于是我给全部文章都设置了同一个时区(时间没填就是 00:00:00),但这个办法有隐患。如果我在另一个时区写文章,这时新的文章还是使用了旧时区。所以最好在每篇文章写上日期、时间和时区。

Hugo 日期的格式为 ISO 8601,示例:

1
date: 2020-10-02T00:00:00+08:00

补全日期的方法就是去掉引号1、补上 T00:00:00+08:00T00:00:00 是时间,+08:00是时区。

修改日期前先给全部文章加上 lastmod(上次修改时间),不然修改日期后 lastmod 全部变成今天了。

操作流程 #

在根目录新建 use-iso8601-date.sh,添加执行权限。

1
2
touch use-iso8601-date.sh
chmod +x use-iso8601-date.sh

use-iso8601-date.sh 填入脚本内容。

1
2
3
4
5
6
7
8
#!/usr/bin/env bash
# usage: ./use-iso8601-date.sh directory-name
directory="$1"
files=$(find "$directory" -type f)
for file in $files; do
 sed -i "s|date: '\(.*\)'|date: \1T00:00:00+08:00|g" "$file"
 sed -i 's|date: "\(.*\)"|date: \1T00:00:00+08:00|g' "$file"
done

修改前先构建网站,后面用于对比。

1
2
3
rm --recursive public/
hugo
mv public public-original

补全日期。

1
./use-iso8601-date.sh content

删除时区配置。

1
2
3
4
# config.toml

# 删掉下面这行
timeZone = "Etc/GMT-8"

再次构建网站。

1
2
hugo
mv public public-changed

使用文件对比工具(我用的是 Kompare)对比 public-originalpublic-changed,有一处差异:/en/post/2023/11/06/hugo-top-level-404/ 文内代码块的日期也被修改了。这样改也没错。

预览,稍微和 https://cyrusyip.org/ 对比一下,看起来一样。

1
hugo server

删除构建的网站。

1
rm --recursive public public-original public-changed

查看差异后提交改动。

1
2
3
git diff
git add --update
git commit --message 'feat: use ISO 8601 date'

删除脚本。

1
rm use-iso8601-date.sh

  1. 日期的引号去不去都行,我觉得不用更简洁就选择去掉。 ↩︎

Hugo:给文章添加 lastmod(上次修改时间)

2024-05-25 00:00:00

背景:本站文章的 lastmod(上次修改时间)就是 Git 提交的 author date(作者日期)。我需要批量修改文章(Hugo:补全文章的 ISO 8601 日期),这会导致所有文章的 lastmod 都变成今天。文章内容没变就不应该改 lastmod,所以我打算给每篇文章都加上 lastmod,后面改动文章就 lastmod 就不会变。


先把 lastmod 的优先级调至最高,不然 Hugo 会继续使用 Git 提交日期。

1
2
3
# config.toml
[frontmatter]
 lastmod = ['lastmod', ':default']

在根目录新建 add-lastmod.sh,添加执行权限

1
2
touch add-lastmod.sh
chmod +x add-lastmod.sh

add-lastmod.sh 填入脚本内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env bash
# usage: ./add-lastmod.sh directory-name
directory="$1"
files=$(find "$directory" -type f)
for file in $files; do
 echo "${file}"
 lastmod_date=$(git log --no-show-signature -1 --format=%aI "$file") # example: 2024-05-16T14:23:53+08:00
 echo "$lastmod_date"
 # Use awk to insert the lastmod line above the second ---
 awk -v lastmod="lastmod: $lastmod_date # remove this line if the content is actually changed" '
 BEGIN { frontmatter = 0 }
 /^---$/ { frontmatter++ }
 frontmatter == 2 && !printed { print lastmod; printed = 1 }
 { print }
 ' "$file" >tmpfile && mv tmpfile "$file"
done

修改前先构建网站,后面用于对比。

1
2
3
rm --recursive public/
hugo
mv public public-original

添加 lastmod。

1
./add-lastmod.sh content

再次构建网站。

1
2
hugo
mv public public-changed

使用文件对比工具(我用的是 Kompare)对比 public-originalpublic-changed,结果完全相同。

预览,稍微和 https://cyrusyip.org/ 对比一下,看起来一样。

1
hugo server

删除构建的网站。

1
rm --recursive public public-original public-changed

查看差异后提交改动。

1
2
3
git diff
git add --update
git commit --message 'chore: add lastmod before changing date'

删除脚本。

1
rm add-lastmod.sh

拒绝烂词

2024-04-05 23:37:10

迷因图:简单点,说话的方式简单点

最近发现自己越来越讨厌网络词语了,但是一直没细想原因,今天决定仔细思考一下。

我讨厌的词语有(次级列表表示讨厌原因):

迷因图:你要不要聽聽看 你現在到底在講什麼

这些词语的问题就是:晦涩难懂、不必要地创造新词、重新定义词语导致歧义、不符合直觉、意义模糊。语言的目的就是传递信息,而费解的词语会阻碍这个过程。我写博客时越来越追求文章通俗简洁,所以就很讨厌这些词语。看来我不是讨厌网络词语,是讨厌烂词。

世界在变化,我们需要创造新词来描述新事物。近年也有一些我觉得不错的网络词语:

前面批评了烂词,我也顺便从中总结我对语言的态度:

最后,推荐我自己的中文训练方法:


本文图片出处:

跳过 Android 应用广告

2024-03-19 00:00:00

GKD 是开源的屏幕点击应用,可以自动跳过应用的开屏广告和应用内广告。实际测试 GKD 可以跳过铁路 12306 和京东的开屏广告。

使用方法:

  1. 根据官方文档安装并授予权限。
  2. 点击「订阅->右下角加号」,添加规则订阅。

默认规则订阅链接:https://fastly.jsdelivr.net/npm/@gkd-kit/subscription。默认规则于 2024 年 2 月 1日 停止更新,如果失效了可以试试第三方规则或者自己编写规则。GKD 不能跳过所有应用广告,得有对应的规则才行。

Mastodon 不是长毛象

2024-03-12 00:00:00

Mastodon乳齿象,不是长毛象

KDE Plasma X11 黑屏后重建账号

2023-12-05 00:00:00

最近 Arch Linux 升级重启后进 KDE Plasma X11 就黑屏剩下光标,某次升级后 Wayland 也用不上了。在 Arch 论坛看到一个相似的情况,但是里面的方法在我这边没用。提问和等待也要花时间,索性就新建账号吧,至少新账户用 KDE Plasma 没问题。

新建用户 #

按下 Ctrl Alt F4 进入 tty 新建用户。

1
2
useradd --create-home --groups wheel --shell /bin/bash cyrus2
passwd cyrus2

Ctrl Alt F1(也可能是 F2 或者 F3) 返回 sddm 登录新账户。

配置 #

  1. 软件基本都是装在系统,无需重装。
  2. 连接 WIFI
  3. 安装配置文件
  4. 安装 zinit: bash -c "$(curl --fail --show-error --silent --location https://raw.githubusercontent.com/zdharma-continuum/zinit/HEAD/scripts/install.sh)"
  5. 设置默认 Shell:chsh --shell /usr/bin/zsh
  6. 安装 kio-admin,这样 dolphin 才能用管理员权限访问旧账户的家目录
  7. 从旧账号复制配置文件
    • ~/.ssh
    • ~/.gnupg
    • ~/.local/share/keyrings/ (seahorse)
  8. 移动家目录的文件(用 dolphin 复制容易卡住,还时不时询问密码,不要一次弄太多。sudo lf 里面复制的文件权限会变 root。复制 git 仓库超慢,最好忽略。)
    1. Desktop
    2. Documents
    3. Downloads
    4. Music
    5. Pictures
    6. Videos
    7. VirtualBox VMs
  9. 登录软件
    1. 登录 Firefox 和 Brave(Brave 浏览器同步码的最后一个单词每天都不一样
      1. 登录 Bitwarden
    2. 登录 MegaSync 同步文件,同步完后删除旧账户的网盘文件
    3. 登录 Joplin
    4. 登录 VS Code,同步设置
    5. 登录 GitHub CLI
  10. 克隆常用的 Git 仓库
  11. 配置 KDE
    1. 添加 Netspeed Widget
    2. 新建两个 Virtual Desktop
    3. 开机启动(autostart)
      1. MegaSync
      2. Fcitx5
      3. Joplin
    4. bismuth 快捷键设置,Settings -> Shortcuts -> Shortcuts -> Window Tiling,打开需要使用的快捷键
    5. 其他快捷键
      1. Switch to Next Screen = Meta +,
      2. 点击 Window Behavior,关闭 Active screen follows mouse,打开 Separate screen focus
      3. Lock Session = Meta+Ctrl+Space
    6. 特效
      1. Desktop Effects -> Virtual Desktop Switching Animation -> Fade Desktop
    7. 设置 plasma-hud 快捷键
      1
      2
      3
      
      cp ~/.config/kwinrc{,.bak} # 备份
      kwriteconfig5 --file ~/.config/kwinrc --group ModifierOnlyShortcuts --key Alt "com.github.zren.PlasmaHUD,/PlasmaHUD,com.github.zren.PlasmaHUD,toggleHUD"
      qdbus org.kde.KWin /KWin reconfigure
      
  12. GoldenDict 导入词典
  13. 清理旧家目录里不需要的文件,比如 ~/.cache

使用默认配置的感想 #

KDE Plasma 默认单击打开文件,这还挺好用的。

如何召唤人工客服

2023-11-27 00:00:00

目前中国的购物网站有很多机器人客服。它们假装真人,却常常答非所问。和客服聊天先发送咒语「人工客服」就可以驱散机器人,召唤真人。如果说一次不行,就说多几次。次数越多,威力越大。

小米 / 红米 / MIUI 自动通话录音教程

2023-11-27 00:00:00

通话录音:打开设置 -> 应用设置 -> 系统应用设置 -> 电话 -> 通话录音 -> 打开「录音通知」和「自动录音」 -> 选择「自动录音对象」

应用录音:打开设置 -> 应用设置 -> 系统应用设置 -> 录音机 -> 应用通话录音 -> 打开「应用通话录音」和「自动开启录音」-> 选择需要录音的应用(支持微信和 QQ)

非中国版 MIUI 可能不支持自动通话录音。经测试,本文适用于 Redmi K40S(MIUI 14.0.7.0.TLMCNXM)和 Redmi Note 12 Turbo。

把 Hugo 网站从 Vercel 搬到 Cloudflare Pages

2023-11-05 00:00:00

起因 #

本 Hugo 网站原本使用 Vercel 构建,但是它只获取 Git 源代码仓库的前十次提交1,多数文章的更新时间会出错,变成最新提交的时间。于是我就改用支持克隆整个仓库的 Cloudflare Pages。本站的 RSS 订阅文件消耗流量大,搬到无限流量的 Cloudflare Pages 也比较合适。本文发布时本站已搬到 Cloudflare Pages 超过一个月,一切正常。

搬迁的主要步骤包括:写重定向配置文件、解决 Cloudflare Pages 导致的问题、创建 Cloudflare Pages 应用、删除 Vercel 项目。

重定向配置 #

Cloudflare Pages 的重定向配置写法和 Vercel 不一样。参考 Redirects · Cloudflare Pages docs 写一份。

Vercel 的重定向配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
 "redirects": [
 {
 "source": "/(index.xml)",
 "destination": "/zh-cn/$1"
 },
 {
 "source": "/(post/index.xml)",
 "destination": "/zh-cn/$1"
 },
 {
 "source": "/(about|subscribe|donate)(/?)",
 "destination": "/zh-cn/$1$2"
 },
 {
 "source": "(/post/[0-9].*)",
 "destination": "/zh-cn$1"
 },
 {
 "source": "/(tags|categories)(/?.*)",
 "destination": "/zh-cn/$1$2"
 }
 ]
}

Cloudflare Pages 的重定向配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# This file is used by Cloudflare Pages
# To improve performance, put splats and placeholders to the bottom

###########################
### Old Chinese Website ###
###########################
## RSS
/index.xml /zh-cn/index.xml 308
/index.xml/ /zh-cn/index.xml 308
/post/index.xml /zh-cn/post/index.xml 308
/post/index.xml/ /zh-cn/post/index.xml 308
## Menu
/about /zh-cn/about/ 308
/about/ /zh-cn/about/ 308
/subscribe /zh-cn/subscribe/ 308
/subscribe/ /zh-cn/subscribe/ 308
/donate /zh-cn/donate/ 308
/donate/ /zh-cn/donate/ 308
/tags /zh-cn/tags/ 308
/categories /zh-cn/categories/ 308
/tags/* /zh-cn/tags/:splat 308
/categories/* /zh-cn/categories/:splat 308
## Post
/post/:year/:month/:day/:slug /zh-cn/post/:year/:month/:day/:slug/ 308
/post/:year/:month/:day/:slug/ /zh-cn/post/:year/:month/:day/:slug/ 308
# Examples:
# /post/2020/10/02/ubuntu-compile-goldendict
# /post/2020/10/02/ubuntu-compile-goldendict/


#######################
### Current Website ###
#######################
## RSS links shouldn't contain a trailing slash
/en/index.xml/ /en/index.xml 308
/zh-cn/index.xml/ /zh-cn/index.xml 308

在根目录创建 404 页面 #

本 Hugo 多语言网站的 defaultContentLanguageInSubdir = "true" 配置导致构建网站后根目录没有 404.html 文件,/en//zh-cn/ 才有。此时 Cloudflare 认为网站是单页应用,用户访问 /non-existent-page/ 这样的路径网页就会跳转到 / 并且返回 200 状态码。这是错误的行为,所以要让 Hugo 构建后根目录有 404.html。解决方法请看 Create Top-Level 404 Page for a Multilingual Hugo Site,我目前使用了自己写的 404 页面

去掉缓存规则 #

之前在 Cloudflare 设置了缓存 RSS 文件,现在不需要这些规则了,删除之。

自定义样式使用 SCSS 格式 #

Cloudflare 会缓存 custom.css,改用 SCSS 格式就不会出问题。

创建应用 #

使用以下配置创建 Cloudflare Pages 应用,然后配置域名。

Build settings
Framework preset None
Build command git fetch --unshallow && hugo
Build output directory public
Environment variables
HUGO_VERSION 0.99.1

具体操作请看教程:

Cloudflare Pages 的缺点 #

重定向例子 #


  1. Configuring a Build | Vercel Docs: When you make a deployment, Vercel builds your project. During this time, Vercel performs a “shallow clone” on your Git repository using the command git clone --depth=10 (...) and fetches ten levels of git commit history. This means that only the latest ten commits are pulled and not the entire repository history. ↩︎

无需 root 修复 LineageOS 的银行应用 / SafetyNet / Play Integrity

2023-10-12 00:00:00

有些银行应用在没有 root 的 LineageOS 打开会提示「不支持在 root 设备使用」,然后退出。用 Lineage Recovery 安装由 althafvly 或者你的维护者提供的 ih8sn,这样银行应用就正常了。

详细解释请看 How to Fix Banking Apps / SafetyNet / Play Integrity on LineageOS without Root

在 Proxmox VE 安装最新 microcode

2023-09-20 00:00:00

本教程介绍用 apt 命令安装最新版微码(microcode)的方法,以改善 N5105 虚拟机死机问题。为了避免弄坏系统,请勿从 unstable 软件仓库安装微码以外的软件包。本教程测试于 Proxmox VE 7.4 / 8.0.2。

微码是修复中央处理器(CPU)的固件。它在开机时被使用,应该装在宿主机(host),不要装在虚拟机(virtual machine)。Proxmox (Debian stable)仓库的微码可能是过时的,所以要从 Debian unstable 仓库安装。

安装方法 #

添加 unstable 仓库,只使用 non-free-firmware 组件。

1
echo "deb http://deb.debian.org/debian/ unstable non-free-firmware" > /etc/apt/sources.list.d/debian-unstable.list

创建 /etc/apt/preferences.d/unstable-repo 配置文件,添加以下内容,防止从 unstable 仓库安装其他软件包。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 降低所有 unstable 仓库软件包的优先级
Package: *
Pin: release o=Debian,a=unstable
Pin-Priority: 10

# 允许从 unstable 仓库升级微码
Package: intel-microcode
Pin: release o=Debian,a=unstable
Pin-Priority: 500
# 此行应该是空行或者注释
Package: amd64-microcode
Pin: release o=Debian,a=unstable
Pin-Priority: 500

更新仓库,确保没有软件包从 unstable 仓库升级。

1
apt update && apt list --upgradable

根据处理器厂商安装微码软件包。

1
2
3
4
# Intel 处理器
apt install intel-microcode
# AMD 处理器
apt install amd64-microcode

重启 Proxmox。

1
reboot

核实微码加载情况。

1
journalctl -k --grep="microcode updated early to"

看到和下面类似的输出信息就对了。

1
Sep 10 11:38:55 pve kernel: microcode: microcode updated early to revision 0x24000024, date = 2022-09-02

此处的 2022-09-02 和微码软件包版本不对应,它表示 Intel 上次更新本机处理器(N5105)的日期。

其他安装方法 #

如果你不想添加 unstable 仓库,可以直接从 Debian 网站下载微码软件包。根据处理器厂商,从 amd64-microcode 或者 intel-microcode 获取最新的下载链接,使用 wget '链接' 命令下载,apt install ./文件名.deb 命令安装。

示例:

1
2
3
4
5
6
7
# AMD 处理器
wget 'http://ftp.us.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/amd64-microcode_3.20230808.1.1_amd64.deb/DontCopyThisLinkVerbatim'
apt install ./amd64-microcode_3.20230808.1.1_amd64.deb

# Intel 处理器
wget 'http://ftp.us.debian.org/debian/pool/non-free-firmware/i/intel-microcode/intel-microcode_3.20230808.1_amd64.deb/DontCopyThisLinkVerbatim'
apt install ./intel-microcode_3.20230808.1_amd64.deb

卸载方法 #

移除微码软件包和 unstable 仓库:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 移除微码软件包
apt purge amd64-microcode intel-microcode
apt autoremove
# 确保没有从 unstable 仓库安装软件
apt list --installed | grep '/unstable'
# 移除 unstable 仓库与配置文件
rm /etc/apt/sources.list.d/debian-unstable.list /etc/apt/preferences.d/unstable-repo
# 重启
reboot
# 检查微码,此时应该看到 No entries
journalctl -k --grep="microcode updated early to"

缘起 #

我在零刻 EQ591 N5105 迷你主机安装了 Proxmox VE。一开始用得挺开心的,但部分虚拟机时不时死机,搞的我很烦。我发现很多 N5105 处理器的用户也有一样的问题,其中一个解决方法是安装微码,于是我就装上最新的微码。虽然微码没有完全解决问题,但确实大大减少了死机的情况。除此之外,我还安装了 Proxmox VE No-Subscription 仓库里最新的内核。

延伸阅读 #


  1. 零刻 EQ59 N5105 对应的海外型号是 Beelink U59 N5105. ↩︎

小米 / 红米 / MIUI / HyperOS 的 Google Play 安装教程

2023-05-05 00:00:00

指南 #

Google Play 支持的设备列表1内的小米/红米手机已预装谷歌服务,我们只需要安装 Play 商店。

  1. 开启谷歌服务:
    • MIUI:设置 -> 帐号与同步 -> 谷歌基础服务
    • HyperOS:设置 -> 更多设置 -> 帐号与同步 -> 谷歌基础服务
  2. 安装 Play 商店:在应用商店搜索「google play」,安装或者升级「Google Play 商店」。如果找不到就从 Aptoide 安装。

安装完桌面就有 Play 商店啦。

经测试,本文适用于 Redmi K40S、Redmi Note 12 Turbo、Redmi Turbo 3、Xiaomi 11 青春活力版、Xiaomi 14。


感谢吴诗涛测试小米 14(小米澎湃 OS)。

注意 #

本指南不适用于所有手机,我也不知道哪些手机不行,只能你自己试试看了。

如果你还没买你想要的手机,你可以

如果你已经买了手机但本指南没用,你可以


  1. 这不是硬性要求。有些不在列表的手机也可以安装 Play 商店。 ↩︎

Epic 游戏商城的免费游戏

2022-12-26 00:00:00

Epic 游戏商城从 2018 年 12 月 14 日开始每两周送一款游戏,2019 年 5 月 23 日开始每周至少送一款游戏。Epic 商城刚解锁中国区的时候我也跟风去每周领游戏,当时觉得只是暂时的活动,领几次停止了。几个月前我发现这个活动居然还在继续,于是我又开始每周领游戏了。虽然现在不玩游戏了,但还是觉得领游戏很快乐。

这几个月一边领游戏一边想为啥领了不玩还觉得快乐呢。大概是因为:

现在圣诞节期间 Epic 每天都送一款游戏,真棒!

亚人 OAD 动画

2022-12-03 00:00:00

最近看了两季亚人动画,看完回味无穷,可惜还没有第三季。不过亚人有三集 OAD(original animation DVD)动画,还能过把眼瘾。第一集讲的是中村慎也事件。第二集讲的是四个搞笑故事。第三集讲了佐藤在军队的经历。喜欢看亚人的朋友一定不要错过这三集。

这三集 OAD 的资源大多都是高糊画质的,实在是看不下去,最后在 https://nyaa.si 里找到了唯一的 1080p 版本(英文字幕)。中文字幕也找不到,就直接看这个版本了。

用 Cloudflare 缓存本站的订阅文件

2022-11-22 00:00:00

本站目前使用 Vercel 部署。最近看了下 Vercel 的使用量,10 月份中文订阅文件(/zh-cn/index.xml)用了 17 GB 流量(平均每日 561 MB),占据了大部分流量,消耗流量第二多的文件只占 79 MB。本站 10 月就 959 次访问,没想到有那么多人订阅。Vercel 免费账户每月 100 GB 流量,真担心有一天流量超标了。

本站的订阅文件包含所有文章全文,大小是 1 MB。减少文章或者文章内容改为摘要都可以减少文件大小。只要流量不超标,我还是希望文件保持完整,给读者选择看网页或者使用 RSS 阅读器的自由。

之前听闻 Cloudflare 提供免费无限流量的 CDN 服务,那让本站使用 Cloudflare 提供的 CDN 不就能减少 Vercel 的流量吗?Vercel 官方就有相关教程:How do I use a Cloudflare domain with Vercel? – Vercel Docs。使用 CDN 之后 Vercel 流量并没有明显变化。11 月 10 号使用 Cache Rules 缓存中文和英文订阅文件,之后中文订阅文件流量降低到 100 MB 左右,效果挺好。11 月 14 日将缓存时间(Edge TTL)改为 2 小时,之后中文订阅文件流量从 55 MB 逐步变成 2.2 MB,效果极佳。11 月 22 日晚上 10 点 55 分 Cloudflare 后台显示 24 小时内缓存了 97.81% 流量。

2 小时的缓存时间虽然很省流量,但是可能导致发新文章后过 2 小时订阅文件才更新。11 月 22 日晚上 11 点 07 分我把缓存时间改为了保持原样(Respect origin),接下来三天订阅文件的流量都低于 6 MB。

以下是缓存规则的设置方法(你也可以按照自己需求来,不用和我一样):

  1. 点击域名 -> Caching -> Cache Rules
  2. 点击 Create cache rule
  3. Rule name 填写规则名称
  4. 点击 Edit expression 并填入 (http.request.uri.path eq "/zh-cn/index.xml") or (http.request.uri.path eq "/en/index.xml")
  5. 上一步对应的手动操作:
    1. Field 选择 URI Path
    2. Operator 选择 equals
    3. Value 填 /zh-cn/index.xml
    4. 点击 Or
    5. Field 选择 URI Path
    6. Operator 选择 equals
    7. Value 填 /en/index.xml
  6. Cache status 选择 Eligible for cache
  7. Edge TTL 表示多久缓存一次文件,可以不设置。如果要设置就点 Add -> Override origin,选择一个合适的时间。
  8. 点击最后的 Deploy

设置完后可以用 curl --head 检查文件缓存状态(cf-cache-status),状态含义请参看:Default Cache Behavior · Cloudflare Cache docs

1
2
❯ curl --head https://cyrusyip.org/zh-cn/index.xml | grep cf-cache-status
cf-cache-status: REVALIDATED

折腾完之后我又在想:要是订阅文件以外的路径也很多流量怎么办(焦虑发作……)?我还是发现了一劳永逸的方法:改用无限流量的 Cloudflare Pages。Cloudflare Pages 没有超越 Vercel,两者的免费帐号各有优缺点。

Vercel Cloudflare Pages
使用量 6000 分钟 500 次
流量 100 GB 无限
配置文件 支持 不支持

Cloudflare Pages 除了流量无限,其他都比 Vercel 差。CDN 套壳算是集两家所长,但是 Vercel 文档并不推荐这样做,参看:Can I use a proxy on top of my Vercel Deployment? – Vercel Docs。目前就这样吧,慢慢观察这个方案有没有问题。

红米 AC2100 刷 breed 后刷回官方固件

2022-10-16 00:00:00

相关文章:红米 AC2100 刷入 OpenWrt 固件

红米 AC2100 刷回官方固件的主要步骤:准备工具、刷入官方 bootloader、刷入官方固件。

准备工具 #

  1. 官方 bootloader(找不到官方链接,我在这里下载的)
  2. Redmi路由器AC2100 稳定版固件(我下载的版本是 miwifi_rm2100_all_fb720_2.0.23.bin
  3. 小米路由器修复工具
  4. 牙签
  5. 网线
  6. Windows 电脑1

刷入 bootloader #

  1. LAN 插到电脑
  2. 拔下 AC2100 的电源,用牙签顶住 Reset 键,插电,蓝色灯闪烁时松开
  3. 访问 192.168.1.1
  4. 固件更新->Bootloader->选择文件->选刚刚下载的 bootloader-redmi-ac2100.bin->勾选自动重启->上传->更新

刷入官方固件 #

  1. 拔下电源,用牙签按住 Reset 键,插电,橙色灯闪烁时松开
  2. 解压修复工具,打开 MIWIFIRepairTool.x86.exe
  3. 本地上传->浏览->选择 miwifi_rm2100_all_fb720_2.0.23.bin->选择对应的网卡->下一步
  4. 蓝色灯闪烁表示成功,拔电源再接上,电脑打开 http://192.168.31.1/ 有页面就成功啦

文件哈希值 #

bootloader-redmi-ac2100.bin miwifi_rm2100_all_fb720_2.0.23.bin
md5 f4ba3f7b01d2dd47c664528688166cd1 ca32a6cb7e60df65a391fe5f235fb720
sha256 e09c1a8ddb794ed237960b021ac34c0602374211cc5e589f6b2084b9fde3f96c f1e374fcecab26e6d968f24e4753f0c78543ff8819c7fd4a3e6aa6316c943994
1
2
3
4
5
6
7
8
 md5sum bootloader-redmi-ac2100.bin
f4ba3f7b01d2dd47c664528688166cd1 bootloader-redmi-ac2100.bin
 md5sum miwifi_rm2100_all_fb720_2.0.23.bin
ca32a6cb7e60df65a391fe5f235fb720 miwifi_rm2100_all_fb720_2.0.23.bin
 sha256sum bootloader-redmi-ac2100.bin
e09c1a8ddb794ed237960b021ac34c0602374211cc5e589f6b2084b9fde3f96c bootloader-redmi-ac2100.bin
 sha256sum miwifi_rm2100_all_fb720_2.0.23.bin
f1e374fcecab26e6d968f24e4753f0c78543ff8819c7fd4a3e6aa6316c943994 miwifi_rm2100_all_fb720_2.0.23.bin

参考资料:小米/红米AC2100 刷breed后 刷回官方固件 - YouTube


  1. 小米路由器修复工具只有 Windows 版,我的系统是 Arch Linux。我用了 Virtualbox 启动 Win10 来刷机的,把 USB 网卡分配给虚拟机就行了。 ↩︎

修复奇怪的电脑问题

2022-08-31 00:00:00

友情提示:你可以边看本文边猜问题的原因,这样可能比较有趣。

今天晚上电脑在 Chrome 用 Fcitx5 输入法的时候选字总是出错(奇怪现象 1),重启 Chrome 后又没事了。电脑锁屏后我就去睡觉了,半夜去上厕所的时候发现显示器和笔记本电脑屏幕都亮着,没有自动关闭(奇怪现象 2)。第二天打开 KDE Plasma 的设置看看有没有「接了显示器就不熄屏」的选项。打开「Energy Saving」,里面似乎没有相关选项。此时电脑有点不受控制,乱点里面的东西,还点击里面的选项卡了(奇怪现象 3)。俗话说,重启能解决 90% 的电脑问题。于是我就升级了 Arch Linux 并重启,但是没用。

用电脑那么多年没见过那么奇怪的事情,该不会被黑了吧。想想又不太可能,我用 Linux,黑了我电脑干坏事也得悄悄地用命令行吧,用远程桌面实在是吃力不讨好。好吧,打开 Chrome 搜索一下,不过我也不知道搜啥。打开 Chrome 之后,无论我点击哪个标签页,Chrome 都会选择它右边的标签页,再选择右边的,直至到最右边(奇怪现象 4)。同样是 Chromium 内核的 Brave 浏览器也有这个问题,但是 Firefox 就没事。

电脑的输入设备无非是鼠标和键盘,就逐个排查吧。先打开 screenkey 检测键盘输入,再点击 Chrome 标签页,问题依旧,但没有键盘输入,说明键盘没问题。接下来就打开鼠标检测软件,但我没安装。算了,直接关掉无线鼠标,用笔记本触控板点 Chrome 标签页,问题依旧。我突然想到自己写过一个检测鼠标的网页,打开一看发现输入设备一直触发向后滚动。终于确定问题了,上谷歌搜「linux mouse keep scroll」,找到相关问题:10.10 - Mouse wheel jumpy on scrolling - Ask Ubuntu。提问者也是点标签页就跳到最右边。

But I open many browser tabs, and if i just move my mouse of one of the tabs, the the right-most tab gets selected.

提问者自己回答说问题原因是低电量的无线键盘干扰了无线鼠标。

Unbelievable, but the problem was that my wireless keyboard and my wireless mouse’s receivers were both on adjacent usb ports. when i moved my mouse’s reciever to a further away port, I found that my mouse was not being weird any more, but my keyboard was acting funny. Turns out my keyboard’s battery was running low and it was interfering with my mouse.

我电脑用的无线输入设备就只有鼠标,应该没干扰吧。拔掉插在 USB 分线器的接收器再插入,问题就消失了。我还是觉得很奇怪,为什么一开始插着接收器关了鼠标还是出问题?鼠标都关了应该就不会向接收器发指令吧。

Windows 10 简易安装教程

2022-07-14 00:00:00

安装系统有三个基本步骤:刻录系统映像、分区、安装。如果你要安装其他系统,也可以参考本文的步骤。

下载系统映像 #

在谷歌或必应搜「windows 10 下载」,可以找到微软官方的下载地址,选择合适的版本。如果你不知道选啥,那就下载「Windows 10 (multi-edition ISO)」。

你也可以在 https://msdn.itellyou.cn/ 下载系统,但这不是官方网站。

刻录映像 #

将下载好的系统映像刻录到 U 盘就可以用来装系统了。你可以用 balenaEtcher 这种传统的刻录工具。我更推荐用 Ventoy,将它装到 U 盘之后,它就能启动 U 盘里的任意系统映像。这就免除了刻录的过程,升级 Ventoy 也无需格式化 U 盘。

BIOS/UEFI 设置 #

开机时按特定按键1(如 F2)进入 BIOS/UEFI,将启动方式改为 UEFI,现在的系统似乎都用这个启动方式,我也不知道用 BIOS 启动行不行。然后再启用 USB 启动。

硬盘分区 #

分区工具可以用 DiskGenius 或者 GParted。第三方 Windows PE 一般都有 DiskGenius,例如:优启通微PE工具箱。有些无良 Win PE 有病毒和广告软件,所以要选择口碑良好,没出过问题的 Win PE,比如我刚刚提到那两个。选 Win PE 那么麻烦,大家赶紧用 Linux 发行版映像吧!一般的桌面 Linux 发行版映像都有分区工具,例如:Ubuntu。手动分区不是必要步骤,Windows 10 安装映像里有简单的分区工具。

安装系统 #

开机时按特定按键(例如 F12)进入启动菜单,选择 U 盘启动。接下来按照指引点点鼠标就可以安装了。如果你不知道选那个版本,就用家庭版,一般电脑都预装这个版本。以后换版本不需要重装,用对应的激活码就行了。

激活系统 #

以上两种激活方法都是安全的。千万不要用激活软件,小心病毒。


  1. 不同品牌的电脑按键不一样,你得上网搜索你的电脑怎么进 BIOS/UEFI。 ↩︎

Hugo 多语言博客折腾记

2022-05-30 00:00:00

最近在统计之都看大家讨论博客,又激起了我的折腾之心,打算把一个大坑给填了:把中文博客改为双语博客。我先建立了一个新的 Git 分支来折腾,成功后才合并到主分支。以下是详细的折腾过程。

改用 jane 主题 #

我原来用的 even 主题 年久失修,作者超过一年没出来处理 Issue 和 Pull Request 了,这个主题的多语言配置也没有详细的说明和配置,于是我就改用 jane 主题,这个主题更新及时,要是出 bug 应该也容易解决。jane 主题有现成的多语言配置可以抄,而且手机页面也有目录。

这次改用 git submodule 添加主题,这样就不用把主题里的一堆文件加进 git 仓库了。

1
git submodule add https://github.com/xianmin/hugo-theme-jane.git themes/jane

配置就抄这几个文件:

1
2
3
./exampleSite/config.toml
./exampleSite/full-config.toml
./dev-config.toml

双语配置是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
defaultContentLanguageInSubdir = "true" # Render English site under /en
[permalinks]
 post = "/post/:year/:month/:day/:slug/"
# language support # en / zh-cn / other... translations present in i18n/
defaultContentLanguage = "en" # Default language to use
[langusges]
[languages.en]
 title = "Cyrus Yip's Blog"
 languageName = "English"
 contentDir = 'content/en'
 # languageCode = "en"
 # weight = 2
[languages.zh-cn]
 title = "叶寻的博客"
 languageName = "简体中文"
 contentDir = 'content/zh-cn'
 # languageCode = "zh-cn"
 # weight = 1
[author] # essential # 必需
 name = "Cyrus Yip 叶寻"

[[languages.en.menu.main]]
 name = "Home"
 weight = 10
 identifier = "home"
 url = "/"

[[languages.en.menu.main]]
 name = "Archives"
 weight = 20
 identifier = "archives"
 url = "/post/"
[[languages.en.menu.main]]
 name = "Tags"
 weight = 30
 identifier = "tags"
 url = "/tags/"
[[languages.en.menu.main]]
 name = "Categories"
 weight = 40
 identifier = "categories"
 url = "/categories/"

[[languages.zh-cn.menu.main]]
 name = "主页"
 weight = 10
 identifier = "home"
 url = "zh-cn/"
[[languages.zh-cn.menu.main]]
 name = "归档"
 weight = 20
 identifier = "archives"
 url = "zh-cn/post/"
[[languages.zh-cn.menu.main]]
 name = "标签"
 weight = 30
 identifier = "tags"
 url = "zh-cn/tags/"
[[languages.zh-cn.menu.main]]
 name = "分类"
 weight = 40
 identifier = "categories"
 url = "zh-cn/categories/"

内容目录改成这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ tree content
content
├── en
│   ├── abount.md
│   └── post
│   └── 2022-05-24-hello-world.md
└── zh-cn
 ├── about.md
 └── post
    └── 2020-10-02-ubuntu-compile-goldendict.md

URL 重定向 #

网站改为双语之后,网站链接和原来的不一样了,需要把旧链接重定向到新链接。中文博客链接现在多了 zh-cn,以前是这样的:https://cyrusyip.org/post/2020/10/02/ubuntu-compile-goldendict/,现在要改成 https://cyrusyip.org/zh-cn/post/2020/10/02/ubuntu-compile-goldendict/。RSS 链接也要改。我用 Vercel 部署网站,所以修改 vercel.json,使用正则表达式来重定向链接。改动链接和迁移评论前最好关闭网站,以免造成同一篇文章出现两份评论。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 "redirects": [
 {
 "source": "/post/(index.xml)",
 "destination": "/zh-cn/$1"
 },
 {
 "source": "/(about|subscribe|donate)(/?)",
 "destination": "/zh-cn/$1$2"
 },
 {
 "source": "(/post/[0-9].*)",
 "destination": "/zh-cn$1"
 }
 ]

Vercel 重定向的官方文档 也没介绍它支持哪种正则表达式,我就参照益辉的配置自己尝试了。如果你像我一样对正则表达式读写困难,可以用 regex visualizer 帮助理解。修改后的完整 vercel.json 在这里。弄好之后可以用 Google Search Console 再看看有没有错误链接。

改动内部链接 #

网站源文件里面的链接也要更改,例如:[本站的某文章](/post/2021/01/01/hi/) 要改为 [本站的某文章](/zh-cn/post/2021/01/01/hi/)https://cyrusyip.org/post/xxx 改为 https://cyrusyip.org/zh-cn/post/xxx

用 ripgrep 查找源文件里需要改的链接。

1
2
rg '\[.*\]\(/.*\)' content --only-matching >> todo/internal-links
rg 'cyrusyip\.org' content >> todo/internal-links

修正链接。

1
sed --regexp-extended -i "s|(\[.*\]\()(/.*\))|\1/zh-cn\2|g" content/zh-cn/post/*

剩下的漏网之鱼就自己手动改了。

更新博客列表的 RSS 链接 #

我的博客收录于下面这两个博客列表。

于是过去提交了 pull request 改 RSS 链接。其实不改也没事,旧链接会重定向到新链接。

迁移评论 #

本站的评论是和文章链接绑定的,因为文章链接改变了,所以要将评论指向新的链接,不然就看不到已有的评论了。本站的评论系统是 Disqus 和 utterances。

Disqus #

Disqus 评论使用 URL Mapper 修改。URL Mapper 使用 .csv 文件迁移评论,我先从 Disqus 那里拿到一个 .csv 文件。节选内容如下。

1
2
3
4
https://cyrusyip.org/about/
https://cyrusyip.org/donate/
https://cyrusyip.org/subscribe/
https://cyrusyip.org/post/2020/11/15/mute-volume-plasma/

将它加上逗号和新链接。

1
2
3
4
https://cyrusyip.org/about/,https://cyrusyip.org/zh-cn/about/
https://cyrusyip.org/donate/,https://cyrusyip.org/zh-cn/donate/
https://cyrusyip.org/subscribe/,https://cyrusyip.org/zh-cn/subscribe/
https://cyrusyip.org/post/2020/11/15/mute-volume-plasma/,https://cyrusyip.org/zh-cn/post/2020/11/15/mute-volume-plasma/

然后再上传到 Disqus 就可以迁移评论了,Disqus 会合并逗号两边的文章评论,并将评论移动到右边的链接。官方文档说可能需要 24 小时才完成迁移,实际上我迁移了 82 份评论,一瞬间就好了。修改 .csv 的每一行都是一样的操作:复制整行内容、加逗号、粘贴、加上 zh-cn,所以用 Vim 的 q 命令录制一次操作,然后就可以批量更改了。以下是操作视频,速度比较慢,可以调到 2 倍速观看。

视频 32 秒执行录制命令时特别慢,主要原因是 coc.nvim 和 fcitx.vim。用 Vim 默认配置(vim --clean)瞬间就完成了,看来录制命令最好在无插件下使用。我专门定义了命令来用 Vim 默认配置打开文件

1
2
" Cv: clean vim, used for fast macro replaying
command Cv silent! w | wviminfo | !vim -c 'set rnu nuw=1' -c 'file' --clean -i ~/.viminfo %

Disqus 还有其他迁移方式,不过我觉得还是 URL Mapper 最稳妥。

utterances #

utterances 使用 GitHub Issue 保存评论。标题为「post/2021/10/11/build-aur-wps/」的 Issue 对应的文章链接为「https://cyrusyip.org/post/2021/10/11/build-aur-wps/」。使用 GitHub CLI 修改 Issue 标题就可以迁移评论。

保存 Issue 标题与号码,并用 Vim 打开。

1
2
3
mkdir todo
gh issue --repo CyrusYip/blog-comment list > todo/utterances
vim todo/utterances

Issue 信息是这样的:

1
2
3
3	OPEN	about/		2021-10-05 07:07:15 +0000 UTC
2	OPEN	post/2021/03/15/liangfen-shaoxiancao-guilinggao/		2021-04-30 06:53:25 +0000 UTC
1	OPEN	post/2021/03/08/girls-day-womens-day-and-goddesses-day/		2021-03-17 14:46:31 +0000 UTC

这里用 Vim 的 q 命令批量处理上面的内容,使其变成:

1
2
3
gh issue --repo CyrusYip/blog-comment edit 3 --title 'zh-cn/about/'
gh issue --repo CyrusYip/blog-comment edit 2 --title 'zh-cn/post/2021/03/15/liangfen-shaoxiancao-guilinggao/'
gh issue --repo CyrusYip/blog-comment edit 1 --title 'zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/'

然后在 Vim 执行 :%!bash,用 BASH 执行上面的命令。utterances 只能移动评论,不能合并评论。移动前最好关闭网站,以免造成同一篇文章出现两份评论。

增加标题级别 #

之前我写文章都是先用 Markdown 的一级标题,但根据 MDN 和 Bing Webmaster Tools,使用多个 <h1> 标题是错误的。

Use only one <h1> per page or view. It should concisely describe the overall purpose of the content.

Using more than one <h1> is allowed by the HTML specification, but is not considered a best practice. Using only one <h1> is beneficial for screenreader users.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#usage_notes

These pages have more than one <h1> tag. Multiple <h1> header tags might confuse search engine bots and website’s users. It is recommended to use only one <h1> tag per page.

—SEO Reports from Bing Webmaster Tools

这次难得折腾,就彻底整改吧。依然是用正则表达式解决:

1
find content/ -type f -exec perl -0777 -i -pe 's/(?<=\n\n)(#.*)(?=\n\n)/#$1/g' {} +

上面的正则表达式查找的是「以 # 开头的行,并且它上下都有 1 个空行」,然后在查找到的内容前面增加 #,也就是增加了 1 个标题级别。处理完之后用 VS Code 手动检查,没问题就提交 commit。可以用 VS Code 搜索 ^# ,看看有没有遗漏的。

preservim/vim-markdown:HeaderIncrease 和 pandoc 的 --shift-heading-level-by=1 选项也可以用来增加标题级别。但是这两个工具都有问题,vim-markdown 会将 Markdown 文件 YAML 内容后两行当成标题,pandoc 会修改 YAML 内容。还是自己老老实实写的正则表达式好。

添加 favicon #

https://realfavicongenerator.net/ 将我的头像做成 favicon,放到 /static 文件夹。

Jane 主题的问题 #

从 Even 主题切换到 Jane,还是遇到了一些小问题。

修改网站是永无止境的,这些小问题就忍忍吧,太纠结反而没时间写文章了。博客内容最重要,功能够用外观不丑就行。

代办事项 #

这是我想做的一些事。先挖坑,看看有没有人帮我埋

资料和工具 #

人不应适应衣服

2022-05-29 00:00:00

前些天陪女友买衣服。女友试穿了一条阔腿牛仔裤,结果裤子长到拖地了。女友说裤子太长了,能不能改短些。结果店员说拖地才好看,改短了不好看,而且这种裤子很难改。最后还是没买,女友可不想要拖地之后脏脏的裤子。

我觉得店员说话实在是不尊重人,衣服是给人用来穿,满足需求和喜好的衣服才是合适的。什么时候到人适应衣服了,真是离谱!不想改裤子就直说啊,为什么硬说拖地的裤子好看呢?!

后来我又和女友吐槽起这件事,女友说其实那条阔腿牛仔裤也好看,就是自己太矮了,穿了才拖地。我说明明是裤子太长了。

旧电脑升级未遂记

2022-05-02 00:00:00

相关文章:Windows 10 简易安装教程

最近亲戚抱怨家里的台式电脑很慢,我就打算帮忙升级硬件,让电脑重获新生(苟延残喘)。电脑配置如下。

型号 Dell Optiplex 3020 备注
处理器 Intel Celeron G1840 2.8 GHz 双核双线程
内存 2*2 GB DDR3L 1600MHz 有两个内存插槽
硬盘 Seagate ST500DM002-1BD142 500 GB 用 HD Tune Pro 5.75 测得读取速度为 120MB/s
主板 40DDP
系统 Windows 7

旧电脑升级主要就是把机械硬盘换成固态硬盘,固态硬盘真的比机械硬盘快多了,用一次之后真的是再也无法忍受机械硬盘的速度了。内存 4GB,也就够用的水准,这也一并升级了吧。其他硬盘就不用管了。硬盘接口似乎只有 SATA,就买 2.5 寸 SATA 固态硬盘。内存打算买条 8GB 替换一条 2GB 的。

拆机小技巧:断电后按住电源键,主板的灯熄灭就断电了、带硅胶手套防静电。以下是升级步骤。

升级内存 #

内存要买主板兼容的才行,最简单的方法就是买同类的。比如这台机原本的内存是 DDR3L1 1600MHz,那就买这样的。但我没注意,买了 DDR3 1600MHz 的内存,还好这个主版兼容这两种电压的内存。最后用一条联想 8GB DDR3 1600MHz 内存(229 元)替换一根 2GB 内存,现在内存总共 10GB。

升级硬盘 #

这台戴尔台式机加硬盘不是扭螺丝就行的,得用它专用的支架,螺丝在支架里。这台机里面没有多余的硬盘支架,得上淘宝买这台机用的 2.5 寸硬盘支架。支架的螺丝也很坑,装硬盘的时候很容易磨损表面。第一次买的联想固态就被刮花了一点。

固态硬盘买的是联想 SL700 SATA3 240GB(185 元)。一开始把硬盘插到 SATA2 接口了,读取速度才 240MB/s,接到 SATA3 接口后速度是 440MB/s。测速结果看着还行,但用电脑的时候会突然掉速,比机械硬盘还慢。这实在是太垃圾了,退货!后来买了铠侠 TC10 480GB

拿到铠侠 TC10 之后就装 Windows 10 了,结果装完之后年迈的 TCL 显示器就一直白屏。重启主机和显示器都没辙。别说测试硬盘了,正常使用都不行。亲戚没有修或者买显示器的意愿,本次的硬件升级计划就此结束,退了铠侠硬盘。

优化系统 #

电脑满除了硬盘问题以外,软件也影响很大。亲戚家的电脑开机之后右下角就一堆软件图标,还有锁屏软件、弹窗广告、两家公司的杀毒软件(它们不会相互陷害吗?)、好多流氓软件。我爸妈用上智能手机之后我就不用每次回家都清理电脑了,我真的很多年都没看过这副景象了,真的不知道亲戚是怎么忍得了这样的电脑,我都想重装系统了。最后用杀毒软件杀毒,手动删除了一堆流氓软件,电脑竟然快了不少,达到了能用的速度。

2022 年了,Windows 还是流氓软件很多,装个软件都得小心翼翼的。Windows 是个需要学习如何使用的系统,真的不太适合不愿意花时间用电脑的人。我觉得这类人挺适合用 deepin,前提是有人帮忙安装和维护。deepin 可以安装打包好的 Windows 和安卓软件,常见的软件应该都有,简单的娱乐办公应该不成问题。


  1. DDR3L 的 L 表示 Low Voltage(低电压)。 ↩︎

小米 9 SE 刷欧版 MIUI 记录

2022-04-22 00:00:00

2023-05-19 更新:改用 LineageOS #

虽然欧版 MIUI 比大陆版流畅,但仍然经不住国产软件毒打,而且系统也没更新过,没有新功能和安全更新。于是改用开源的 LineageOS 20,没想到这台机器还能用上最新的安卓 13,和之前比升级了两个版本号呢,而且更流畅了。用了两个月还是感觉不够流畅了,这配置真的跟不上软件发展了(软件厂商你们怎么不多做优化,非要逼人换手机吗!)。在 https://lineageosdevices.com/LineageOS 设备列表 物色手机,选中小米 11 青春活力版,在京东只用 1199 块就买到了(首发价格 1999),继续用 LineageOS,体验良好。顺便一提,所有 LineageOS ROM 都需要满足严格的设备支持要求,所以它软件硬件功能正常,完全可以代替原厂系统。

缘起 #

之前看了 Pockies 写的《老妈钦点,我买了一部Redmi K30 5G版》,感觉欧版 MIUI 真是太棒了,既没有广告和无用的内置软件,还有 Google Play。我手头这台小米 9 SE 也用了两三年了,越来越卡了,用了冰箱(Ice Box)冻结应用之后还是卡。骁龙 712 + 6G 内存,好歹也是中高端配置吧,怎么就卡了呢!我需要安装一些外国软件,但这台机器没预装 Google Play,自己装上的用几次就不行了。该死,只有骁龙 800 系列的小米手机才配有 Google Play 吗?

我很早就知道国外版本的 MIUI 有 Google Play,但是要解锁 bootloader 才能装。解锁 bootloader 之后呢就可以随便刷 recovery,我担心手机丢了后别人可以通过 recovery 随便查看我的文件,所以一直没换系统。经过 Pockies 的一番指点,我才知道我的认知已经过时了,但还是不太懂。于是我在联想 ZUK Z2 Pro 刷机测试,发现只要开启了系统的加密功能,别人进 recovery 就要输入解锁密码才能读取文件(\data 分区)。MIUI 默认开启加密,所以不用担心这个问题。

备份 #

下载系统与工具 #

解锁 bootloader #

使用官方软件解锁即可。进入 fastboot 之后要安装驱动,不然识别不了。我直接解锁成功了,不需要等待。解锁过程超折腾,用了三台电脑两条数据线尝试了超多次才检测到手机。

刷入 TWRP #

先进入 fastboot。

1
adb reboot bootloader

在 Arch Linux 刷入失败。

1
2
3
❯ fastboot flash recovery twrp-3.6.1_9-0-grus.img
Sending 'recovery' (65536 KB) FAILED (Write to device failed (No such device))
fastboot: error: Command failed

在 Windows 用解锁工具里的 fastboot 就行,玄学啊。

1
2
3
4
5
.\fastboot.exe flash recovery .\twrp-3.6.1_9-0-grus.img
Sending 'recovery' (65536 KB) OKAY [ 1.413s]
Writing 'recovery' OKAY [ 0.316s]
Finished. Total time: 1.746s
.\fastboot boot twrp.img

重启时要按住「关机键和音量加键」,不然进 MIUI 后 recovery 会变成原版。

刷入系统 #

我安装固件的时候出现 the package is for grus 错误,在 TWRP 执行以下命令就可以解决。

1
2
setprop ro.product.device grus
setprop ro.build.product grus

我刷完固件一开始进了系统,后来重启就总是自动进 TWRP,不知道为啥。重新 Format Data 之后又正常了。

配置系统 #

治理流氓软件 #

平时用的国产软件实在太流氓,需要用以下软件治理。下列软件只需要 adb 就能用,不需要 root,但 adb 激活重启会失效。

以下命令用于激活黑阈和 shizuku。

1
adb -d shell sh /data/data/me.piebridge.brevent/brevent.sh; adb shell sh /sdcard/Android/data/moe.shizuku.privileged.api/start.sh

除了用电脑激活,也可以在手机通过无线调试激活(开发者怎么想到的,好厉害!出门在外不需要用电脑激活了)。

使用体验 #

用了一周,系统比原来的流畅多了,Google Play 也正常能使用。欧版 MIUI 是基于中国版 MIUI 修改的,所以功能都差不多,但有些小毛病。

欧版 MIUI 12 的特性 #

本节内容出自欧版 MIUI 官网,这些特性看起来还是很棒的。

  • Based on China Stable / Weekly builds (except POCOF1, HMNote6Pro, HMNote8T, HMNote9ProMax, HMNote9Pro_HMNote9S, HMNote9ProEU)
  • Enabled search gesture (swipe up) on the desktop
  • 3way reboot menu (in dev settings)
  • MIUI Contacts and Dialer
  • Dark Mode (Night Mode) for all devices (In the dev settings except 9.x/10.x)
  • Removed ads in MIUI system apps
  • Face Unlock for all devices
  • Vertical clock widget on lockscreen
  • Steps counter in Assistant screen
  • More shortcuts on left lockscreen
  • Wake up gestures
  • AI preloading option in MIUI Lab
  • Fullscreen gestures
  • Option to auto-expand first notification
  • Notifications priority settings
  • More edit options in Gallery app
  • Enabled MiDrive in File Explorer
  • Added landscape mode for SMS app
  • Sunrise/Sunset in the weather app
  • Google apps support integrated
  • Removed 5GHz region restrictions
  • Confirm dials from call logs
  • Morning report on alarm at morning
  • More apps supported for AOD notifications
  • More styles for AOD screen
  • Raise to wake gesture
  • AOD notification received animation
  • Default volume control stream toggle
  • Tap fingerprint sensor to wake toggle
  • More Camera features
  • Double tap to lock screen
  • Battery AI scenes
  • Allowed disabling specific system apps
  • Importing theme .mtz from zhuti.xiaomi.com via ThemeManager
  • No useless Chinese bloatware apps
  • More free RAM due to less background processes
  • Unified flat style app icons for both system and 3rd party apps
  • Advanced menu with color icons
  • No any Chinese character under the full system
  • Mi Video, Mi Music, Mi Browser: No any useless Chinese content
  • No possibility to re-lock bootloader accidentally with flash any xiaomi.eu release
  • Added real 27 languages translation made by Official MIUI Fansites and MIUI Fans
  • Added EU T9 dialer support
  • Added charging sound switch
  • Added Screen-OFF animation
  • Optimized GPS settings for EU
  • Optimized Battery consumption
  • Optimized RAM consumption
  • Added 3D Touch to MI5S 3GB RAM version
  • Added GSMalphabet into SMS app options
  • Added more icons grid layout 4x5, 4x6, 4x7, 5x5, 5x6, 5x7 (depends on device)
  • SafetyNet passed (Google Pay)
  • Play Store Certified
  • Deodexed
  • Added automated tasks in Security app
  • Added game speed booster
  • Added erase module (magic elimination) to Gallery photo edit options
  • Fixed low volume sound via headphone
  • Charging screen
  • And more, and more made by our 9 years MIUI mods experience.

参考资料 #


  1. Goople Play 的版本似乎停更了,安装不了软件。得去 F-droid 下载。 ↩︎

为何红茶叫 black tea

2022-03-30 00:00:00

小学刚学英语的时候有个困惑我很久的问题:为什么绿茶是 green tea,红茶却不是 red tea。后来上大学的英汉对比课听到了答案:中文的颜色指茶水的颜色,英语指茶叶的颜色。红茶茶叶是黑色的,所以英语是 black tea。

红茶茶水
红茶茶叶

中文的「茶」和英文的「tea」都有两个意思:茶叶、用茶叶沏成的饮料(茶水)。茶叶和茶水的颜色不一定相同,中文以茶水颜色区分不同的茶叶与茶水,英文则以茶叶颜色区分。


图片出处:

在 Linux 上使用京东京造(Keychron)K1 SE 的功能键(F1~F12)

2022-01-18 00:00:00

K1 SE 的功能键在 Linux 上就永远是媒体键,按 F12fn + F12 都是增加音量。解决办法:

1
echo 2 | sudo tee /sys/module/hid_apple/parameters/fnmode

现在按功能键就是功能键,按 fn + 功能键 就是媒体键,不过重启就失效了。下面是永久生效的方法:

1
2
echo options hid_apple fnmode=2 | sudo tee -a /etc/modprobe.d/hid_apple.conf
sudo mkinitcpio --allpresets

附言:京东京造 K 系列键盘就是贴了京东牌的 Keychron 键盘。


参考资料:

修复键盘映射在 VS Code 失效的问题

2021-12-27 00:00:00

我将 Caps 键改为 Esc 键之后,在 VS Code 设置就失效了。修复方法:按下 Ctrl + , 打开设置,找到 Keyboard: Dispatch,将其改为 keyCode


参考资料:Howto: Fix Caps Lock Escape Swap Not Working in VS Code

在 KDE Plasma 将 Caps 键换成 Esc 键

2021-12-27 00:00:00

最近用 Vim 键位之后经常按 Esc 键,于是就打算将 Caps 键替换为 Esc 键,这样挪一下左小指就能按到。设置方法:打开设置,点击「Input Devices -> Keyboard -> Advanced -> Configure keyboard options -> Caps Lock behavior -> Swap Esc and Caps Lock -> Apply」。

VSCodeVim 自动切换 Fcitx5

2021-12-24 00:00:00

$HOME/.config/Code/User/settings.json 加入以下内容:

1
2
3
4
5
6
{
 "vim.autoSwitchInputMethod.obtainIMCmd": "/usr/bin/fcitx5-remote",
 "vim.autoSwitchInputMethod.switchIMCmd": "/usr/bin/fcitx5-remote -t {im}",
 "vim.autoSwitchInputMethod.enable": true,
 "vim.autoSwitchInputMethod.defaultIM": "1",
}

或者按下 Ctrl + , 用图形界面设置对应选项。这样退出插入模式时会将 Fcitx5 切换为英文。


参考文章:在 VSCode 的 Vim keybinding 下自動切換 fcitx 模式 | by DanSnow | Medium

修复变慢的 Arch Linux(起因:auto-cpufreq)

2021-11-29 00:00:00

最近 Arch Linux 用起来有点慢,有时候桌面会卡住一两秒。我用 auto-cpufreq(1.7.2) 调节 CPU 频率,该不会是它出问题了吧。先看看数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
❯ sudo auto-cpufreq --stats

Note: You can quit stats mode by pressing "ctrl+c"

 Executed on: December 06 (Monday) - 01:05:35

-------------------------------------------------------------------------------

Linux distro: Arch Linux rolling n/a
Linux kernel: 5.15.6-zen2-1-zen
Processor: AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx
Cores: 8
Architecture: x86_64
Driver: acpi-cpufreq

------------------------------ Current CPU stats ------------------------------

CPU max frequency: 2100 MHz
CPU min frequency: 1400 MHz

Core Usage Temperature Frequency
CPU0: 11.0% 43 °C 1397 MHz
CPU1: 8.2% 43 °C 1397 MHz
CPU2: 10.0% 43 °C 1400 MHz
CPU3: 13.9% 43 °C 1400 MHz
CPU4: 16.2% 43 °C 1400 MHz
CPU5: 7.0% 43 °C 1400 MHz
CPU6: 13.0% 43 °C 1400 MHz
CPU7: 8.9% 43 °C 1400 MHz

---------------------------- CPU frequency scaling ----------------------------

Battery is: discharging

Setting to use: "powersave" governor

Total CPU usage: 1.2 %
Total system load: 0.44
Average temp. of all cores: 42.75 °C

Load optimal
setting turbo boost: off

-------------------------------------------------------------------------------

果然出问题了,我的笔记本在充电,auto-cpufreq 错误地认为电脑没充电,将调频器设置为 powersave,而且频率只有 1400 MHz。Ryzen 5 3500U 的最高频率可是 3700 MHz 啊。解决办法就是禁用或卸载 auto-cpufreq。

1
2
3
4
# 禁用
sudo systemctl disable --now auto-cpufreq.service
# 卸载
sudo pacman -Rsn auto-cpufreq

1.8.0 版本修复了充电检测的问题,但我使用设置充电阈值(charge threshold)之后 auto-cpufreq 会认为笔记本未充电。用了 auto-cpufreq 之后用 Geekbench 跑分比不用还低,TLP 就没这个问题。auto-cpufreq 实在太多问题了,已弃用。


排查问题的时候发现几个好用的工具:

修复 BitLocker 错误:The system cannot find the file specified

2021-11-09 00:00:00

用 BitLocker 给 Windows 10 加密时出现了错误信息:The system cannot find the file specified。这个提示信息简直是废话嘛,为什么不告诉我哪个文件有问题……提示信息还附上了 Win10 安装要求,一样是没用……

谷歌之后找到了解决办法:把 C:\Windows\System32\Recovery\ReAgent.xml 重命名为 ReAgent.xml.old

Arch Linux 备份、加密、还原教程

2021-10-24 00:00:00

最近想加密笔记本电脑的硬盘,一般硬盘加密都是在安装系统前做的,但是我的笔记本已经装上 Arch Linux 了。我刚好有个闲置的移动硬盘,于是我就想到了办法:

  1. 备份系统到移动硬盘
  2. 加密笔记本硬盘
  3. 还原系统
  4. 重装引导程序

准备 #

  1. 待加密的电脑
  2. 移动硬盘
  3. 刷入 Arch Linux 安装镜像的 U 盘(推荐用 Ventoy 刷入镜像)
  4. 网络

备份、还原和写入空数据会花费比较多时间,建议准备 1 天的时间慢慢弄,等待的时候就去干点别的事。请谨慎操作,要是你打错命令(比如:格式化分区时搞错了),可能会丢失数据。可以先在虚拟机练习,弄坏也没关系。用于备份的移动硬盘也加密才是真的安全,为了简化文章,我就忽略这部分了。后面写的加密方法一样适用于移动硬盘。本文适用于 UEFI + GPT。

扩大 EFI 分区(可略过) #

如果你把 EFI 分区挂载到 /efi,那么只需要把引导程序安装到 EFI 分区,Linux 内核不在 /efi,所以不需要占用很多 EFI 分区的空间。但是加密硬盘需要把 EFI 分区挂载到 /boot,Linux 内核也要安装到 EFI 分区,这需要占用更多空间。ArchWiki 建议 EFI 分区至少 260 MiB。我安装了 linux 内核、linux-lts 内核、linux-zen 内核和 GRUB,EFI 分区占用 249.4 MiB。260 MiB 就只能安装 3 个 Linux 内核了。如果有安装多个内核的需求,可以把 EFI 分区调得更大,我自己把 EFI 分区扩大到 3 GiB 了。分区工具可以选择 cfdisk 或 GParted。先缩小 EFI 分区隔壁的分区,再把多出的空间加到 EFI 分区就可以了。

备份 #

启动待备份的 Arch Linux,先查看分区信息,nvme0n1p4 就是需要加密的 / 分区。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
> lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 931.5G 0 disk 
├─sda1 8:1 0 350M 0 part /run/media/hunter/B8CA-E0A9
├─sda2 8:2 0 251.4G 0 part /run/media/hunter/0CB8CBCCB8CBB30E
└─sda3 8:3 0 377.9G 0 part /run/media/hunter/6676d81d-215e-4fcc-a1ff-4e87df4ffe34
mmcblk0 179:0 0 116.5G 0 disk 
├─mmcblk0p1 179:1 0 116.5G 0 part /run/media/hunter/Ventoy
└─mmcblk0p2 179:2 0 32M 0 part 
nvme0n1 259:0 0 238.5G 0 disk 
├─nvme0n1p1 259:1 0 260M 0 part /efi
├─nvme0n1p2 259:2 0 16M 0 part 
├─nvme0n1p3 259:3 0 150G 0 part 
├─nvme0n1p4 259:4 0 79.4G 0 part /
├─nvme0n1p5 259:5 0 1000M 0 part 
└─nvme0n1p7 259:6 0 7.8G 0 part [SWAP]

挂载移动硬盘,KDE Plasma 已经帮我自动挂载好了,现在把 nvme0n1p4 备份至 sda3/run/media/hunter/6676d81d-215e-4fcc-a1ff-4e87df4ffe34)。备份 45G 花了 10 分钟。

1
2
3
sudo rsync --archive --acls --xattrs --hard-links --sparse --one-file-system --delete --delete-excluded --numeric-ids --progress --info=progress2 \
--exclude={"/var/lib/dhcpcd/*","/swapfile","/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} \
/ /run/media/hunter/6676d81d-215e-4fcc-a1ff-4e87df4ffe34

参数含义:

加密 #

备份好就关机,启动 U 盘的 Arch Linux 安装镜像,选择「Copy to RAM」。启动后拔下 U 盘,插入移动硬盘。以下命令可以调大字体、调高亮度:

1
2
3
4
# 调大字体
setfont ter-132n
# 调高亮度(把 * 改为实际的目录)
echo 200 > /sys/class/backlight/*/brightness

现在开始加密。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 写入随机数据,防止原有数据被恢复
# 79.4G 的固态硬盘,时间 5 分 08 秒
cryptsetup open --type plain --key-file /dev/urandom /dev/nvme0n1p4 to_be_wiped
dd if=/dev/zero of=/dev/mapper/to_be_wiped bs=1M status=progress

# 检查写入数据大小,单位是 Byte
blockdev --getsize64 /dev/mapper/to_be_wiped
85298511872 # 85298511872/1024/1024/1024 = 79.44G

# 关闭临时容器
cryptsetup close to_be_wiped

# 加密分区
cryptsetup --verify-passphrase --verbose luksFormat /dev/nvme0n1p4
cryptsetup open /dev/nvme0n1p4 cryptroot
mkfs.ext4 /dev/mapper/cryptroot

还原 #

还原还是用一样的 rsync 命令,就是把路径反过来写就行了,先写 sdb3 的挂载点,再写加密分区的挂载点。因为备份的时候排除了不需要的文件,所以不需要 --delete-excluded--exclude={...} 参数。用安装镜像不能方便地复制粘贴命令,我用了简写的命令。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 挂载加密分区
mkdir /mnt/cryptroot
mount /dev/mapper/cryptroot /mnt/cryptroot

# 挂载移动硬盘
mkdir /mnt/disk
mount /dev/sdb3 /mnt/disk

# 还原系统(注意路径最后的斜杠)
# 耗时 15 分钟
rsync -aAXHSx --delete --numeric-ids --progress --info=progress2 \
/mnt/disk/ /mnt/cryptroot/

# 重新挂载
# EFI 分区必须挂载到 /boot,不然开机时无法使用内核
umount /mnt/cryptroot
umount /mnt/disk
rm -r /mnt/cryptroot
rm -r /mnt/disk
mount /dev/mapper/cryptroot /mnt
mount /dev/nvme0n1p1 /mnt/boot

# 创建 swap 文件,大小为 1M*8192(8G)
dd if=/dev/zero of=/mnt/swapfile bs=1M count=8192 status=progress
chmod 600 /mnt/swapfile
mkswap /mnt/swapfile
swapon /mnt/swapfile

# 重新生成 fstab(file systems table)
genfstab -U /mnt > /mnt/etc/fstab
# 将根目录 / 改为 /mnt
arch-chroot /mnt

编辑 /etc/mkinitcpio.conf,在 HOOKS 这一行加入

完整的内容:

1
HOOKS=(base udev keyboard autodetect keymap modconf block encrypt filesystems resume fsck)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 连接无线网络
# 用网线插上就能用了,可以忽略这部分
rfkill list # 检查无线设备状态
rfkill unblock wifi # 如果 WIFI 被禁用就启用 WIFI
iwctl # 进入交互式命令行
device list # 列出设备名
station wlan0 scan # 扫描网络
station wlan0 get-networks # 列出网络
station wlan0 connect abcd-wifi # 连接 abcd-wifi
exit # 推出 iwd

# 重装内核与 microcode
# 用 Intel 处理器就安装 intel-ucode
# 用 AMD 处理器就安装 amd-ucode
# 可能不需要 linux-firmware
pacman -Syu linux linux-firmware linux-zen amd-ucode

# 重新生成 initramfs
mkinitcpio -P
1
2
# 获取 swap_file_offset
filefrag -v /swapfile

记下第 1 横行数字的第 4 个数字,18055168。用下面这个好长的命令可以直接获取这个数字。

1
filefrag -v /swapfile | awk '$1=="0:" {print substr($4, 1, length($4)-2)}'
1
2
3
4
# 将 swap_file_offset 插入到 GRUB 配置文件
echo "18055168" >> /etc/default/grub
# 将 nvme0n1p4 的 UUID 插入到 GRUB 配置文件
blkid -s UUID -o value /dev/nvme0n1p4 >> /etc/default/grub

编辑 /etc/default/grub,加入

1
2
GRUB_CMDLINE_LINUX="cryptdevice=UUID=uuid-value:cryptroot root=/dev/mapper/cryptroot"
GRUB_CMDLINE_LINUX_DEFAULT="resume=/dev/mapper/cryptroot resume_offset=swap_file_offset"

请将 uuid-value 替换为倒数第 1 行的内容,swap_file_offset 替换为倒数第 2 行的内容,删去最后两行。如果你使用了 GRUB 主题并且主题不在 /boot,那就在 GRUB_THEME 前加上 #,硬盘没解密读取不了主题文件。

1
2
3
# 重装 GRUB
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
grub-mkconfig -o /boot/grub/grub.cfg

按下 Ctrl + D 退出 chroot,并执行 reboot 重启。

技巧 #

备份、加密和还原完成了,下面是加密后的使用技巧。

调整加密分区大小 #

之前创建了交换分区(swap partition),现在打算改用交换文件(swap file),所以要把删掉交换分区,把多出的空间加到加密分区。本来打算用 cfdisk 扩展加密分区,但是没成功,于是就用带 GUI 的 endeavouros 了。

  1. 打开 GParted
  2. 右击 /dev/nvme0n1p7 -> Delete
  3. 右击 /dev/nvme0n1p4 -> Open Encryption -> 输入密码解锁
  4. 右击 /dev/nvme0n1p4 -> Resize/Move -> 把 Maximum size 填入 New Size -> 按下 Enter -> Resize
  5. 点击 Edit -> Apply All Operations -> Apply

使用 GRUB 主题的方法 #

1
2
3
4
5
# 复制到 /boot
sudo cp -r /usr/share/grub/themes/whitesur-white-1080p /boot/grub/themes
# 修改配置文件
echo 'GRUB_THEME="/boot/grub/themes/whitesur-white-1080p/theme.txt"' | sudo tee --append /etc/default/grub
sudo grub-mkconfig -o /boot/grub/grub.cfg

感想 #

经历了这次加密我才知道可以把整个 Arch Linux 备份下来,那么以后换电脑的时候就不用从头安装系统了。虽然安装 Arch Linux 不是很难(装两三次就熟练了),但是配置还是很花时间,通过备份还原就再也不用全新安装 Arch Linux了,真爽!还原后只需要根据新电脑的硬件做些调整(更换 microcode、显卡驱动等)就行了。定时备份系统到移动硬盘也挺好的,电脑遗失了,买了新的也能很快还原之前的系统。

缘起 #

之前用 Windows 的时候,加密硬盘很简单,用 BitLocker 也就是点几个按键就行了。Linux 似乎没有类似的简单易用的工具,所以我用 linux 之后没加密过硬盘。带笔记本出门的时候总担心被偷,然后别人盗取笔记本里面的数据来假扮我……

电脑遗失是小事,资料被偷是大事。不过想偷我的资料也不是那么容易。我设置了 UEFI 密码,不知道这个密码就没办法启动 U 盘的系统,也就不能读取硬盘了。要么就把笔记本的硬盘拆下来,放到其他的电脑读取,要么重置 UEFI 密码。小偷有这些时间精力不如直接把笔记本卖了。

所以说要偷到我电脑的资料的人一定要有坏心肠、懂电脑软硬件、不怕违法犯罪。我也就是个普通人,花那么大劲偷我的笔记本资料,可谓是吃力不讨好。要偷资料也是偷大人物的嘛。所以我笔记本资料被偷的可能性应该超低吧。

虽然我说服过自己了,但每次带笔记本出门就特焦虑,一开始想象从笔记本被偷,然后马上快进到小偷拿到了我的 SSH 和 GPG 私钥……那我还是加密硬盘吧, 万一我以后成了大人物呢? 这样既安全又能缓解焦虑。

参考资料 #

这参考资料也就比我的毕业论文少 1 个 :)

  1. rsync - Full system backup - ArchWiki
  2. dm-crypt/Encrypting an entire system - LUKS on a partition- ArchWiki
  3. filesystems - Extend a LUKS encrypted partition to fill disk - Unix & Linux Stack Exchange
  4. dm-crypt/Drive preparation - dm-crypt specific methods - ArchWiki
  5. Migrate installation to new hardware - Top to bottom - ArchWiki
  6. Installation guide - ArchWiki
  7. https://medium.com/hacker-toolbelt/arch-install-with-full-disk-encryption-6192e9635281
  8. Arch install with full disk encryption | by Miguel Sampaio da Veiga | Hacker Toolbelt | Medium
  9. Swap - Swap file- ArchWiki
  10. Arch Linux: 12.2020 ISO Install With Encryption & i3 - YouTube
  11. rsync+btrfs+dm-crypt 备份整个系统 - 依云’s Blog
  12. Arch Linux 安装使用教程 - ArchTutorial - Arch Linux Studio

解决 Miix 510-12IKB(Miix 5 Plus)在 Arch Linux 休眠后触控板失效的问题

2021-10-14 00:00:00

最近在 Miix 510-12IBK(Miix 5 Plus)上安装了 Arch Linux,它休眠或睡眠后唤醒触控板就失效。我试了好多办法都没用,去官方论坛提问也没人回复。有天偶然发现 KDE Plasma 可以设置触控板开关的快捷键,设置好后遇到触控板失效按快捷键就好了。打开 System Settings 后在这里设置:Workspace -> Shorcuts -> Shorcuts -> System Services -> Touchpad -> Toggle Touchpad。

以 WPS 为例,手动构建和安装 AUR 的包

2021-10-11 00:00:00

更新:安装好旧版后发现最新的 WPS 国际版并没有界面模糊的问题,白折腾了。

1
2
# 安装国际版 WPS
yay -Syu wps-office

最近系统从 Kubuntu 切换到 Arch Linux 了,安装了最新的 WPS 国内版,还是遇到了界面模糊的问题。干脆就手动安装旧版的吧。makepkg 不支持 root 帐户,所以要使用非 root 帐户操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 安装 Git
sudo pacman -Syu git
# 获取 PKGBUILD
git clone https://aur.archlinux.org/wps-office-cn.git
cd wps-office-cn
# 查看旧版 commit
git log
# 切换到旧版(11.1.0.10161)
git reset --hard c7f6d16d3232488f53755137c58c21736e84f0e6
# 更新系统
sudo pacman -Syu
# 安装依赖并打包
makepkg --syncdeps # 简写:makepkg -s
# 安装打包好的 WPS,其实就是使用 pacman -U 安装
makepkg --install # 简写:makepkg -i
# 上面两条命令可一并写为 makepkg -si

本地 SSH 连接教程

2021-10-09 00:00:00

Secure Shell(安全外壳协议,简称 SSH )是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。SSH 通过在网络中创建安全隧道来实现 SSH 客户端与服务器之间的连接。SSH 最常见的用途是远程登录系统,人们通常利用 SSH 来传输命令行界面和远程执行命令。

——Secure Shell - 维基百科,自由的百科全书

本地电脑直接操作就可以了,为什么我还要用 SSH 连接呢?因为这样很酷我懒得从椅子上起来走到另一台电脑操作。本文把被连接的电脑称为服务端,另一台为客户端。开始操作前请确保两台电脑处于同一个网络。

安装 OpenSSH #

  1. 在两台电脑安装 OpenSSH

    1
    
    sudo pacman -Syu openssh
    
  2. 在服务端启用 SSH 服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # 启用 sshd.service
    sudo systemctl start sshd.service
    # 开机启动
    sudo systemctl enable sshd.service
    
    # 关闭开机启动
    # sudo systemctl disable sshd.service
    # 关闭 sshd.service
    # sudo systemctl stop sshd.service
    
  3. 查看状态

    1
    2
    3
    4
    
    ❯ systemctl status sshd.service
    ● sshd.service - OpenSSH Daemon
     Loaded: loaded (/usr/lib/systemd/system/sshd.service; disabled; vendor preset: disabled)
     Active: active (running)
    

    显示 Active: active 就说明成功了

  4. 查看服务端本地 IP 地址

    1
    2
    
    ip route get 1.2.3.4 | awk '{print $7}'
    # 192.168.3.100
    

    ip addr 查看也行

  5. 客户端连接服务端

使用 SSH 密钥登陆 #

用 SSH 密钥登陆就可以不输入密码登陆了。

  1. 在客户端生成 SSH 密钥

    1
    2
    
    ssh-keygen -t ed25519 -C "your computer model"
    # 一直按 Enter 就行
    
  2. 查看并复制 SSH 公钥

    1
    2
    
    ❯ cat ~/.ssh/id_ed25519.pub
    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHY84a2KfbwfkkKyoSvQk+thsKITpmdFzNbYoCs0SlkU magicbook14
    
  3. 连接服务端

  4. 导入 SSH 公钥

    1
    2
    3
    
    mkdir ~/.ssh
    touch ~/.ssh/authorized_keys
    echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHY84a2KfbwfkkKyoSvQk+thsKITpmdFzNbYoCs0SlkU magicbook14' >> ~/.ssh/authorized_keys
    

禁止密码登陆 #

禁用密码登陆后别人就不能暴力破解密码了,更加安全。接下来使用服务端操作,用客户端 SSH 连接操作也行。

1
2
# 备份配置
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
1
2
# 修改配置
sudo nano /etc/ssh/sshd_config

在里面加入:

1
PasswordAuthentication no
1
2
3
4
# 重启 sshd.service
sudo systemctl restart sshd.service
# 终止原有连接
pkill sshd

用 UFW 防火墙限制 IP #

使用服务端操作,配置并启用 UFW 后只允许特定 IP 访问,例如 192.168.3.177。

1
2
3
4
5
6
# 禁止入站连接(外部访问)
sudo ufw default deny incoming
# 允许 192.168.3.177 访问
sudo ufw allow from 192.168.3.177
# 终止原有连接
pkill sshd

查看 SSH 连接 #

1
ss | grep ssh

禁用 SSH 服务 #

1
sudo systemctl stop sshd.service

禁用服务不会终止已有 SSH 连接,要执行以下命令终止所有连接。

1
pkill sshd

终止 SSH 连接 #

终止 1 个 SSH 连接 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 查看用户,pts/1 就是 SSH 连接
❯ w
 18:20:06 up 1:08, 2 users, load average: 0.92, 0.54, 0.43
USER TTY LOGIN@ IDLE JCPU PCPU WHAT
hunter tty1 17:12 1:08m 53.14s 0.06s /usr/bin/startplasma-x11
hunter pts/1 18:18 14.00s 0.47s 0.47s -zsh

# 终止进程
❯ pkill -9 -t pts/1

# 查看用户,现在没有 SSH 连接了
❯ w
 18:22:23 up 1:10, 1 user, load average: 0.44, 0.48, 0.42
USER TTY LOGIN@ IDLE JCPU PCPU WHAT
hunter tty1 17:12 1:10m 58.05s 0.06s /usr/bin/startplasma-x11

终止所有连接 #

1
pkill sshd

参考资料 #

延伸阅读 #

给本地电脑配置 UFW(Uncomplicated Firewall)防火墙

2021-10-09 00:00:00

安装 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 安装 UFW 与图形界面
sudo pacman -Syu ufw ufw-extras gufw
# 禁止入站连接(外部访问)
sudo ufw default deny incoming
# 允许出站连接
sudo ufw default allow outgoing
# 启用 UFW
sudo systemctl enable ufw --now
# 检查 UFW 状态,显示 Status: active 就说明成功启用
sudo ufw status verbose

用法 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 允许 203.0.113.101 访问
sudo ufw allow from 203.0.113.101
# 开放 8080 端口
sudo ufw allow 8080

# 在规则前加上 delete 表示删除规则

# 取消允许 203.0.113.101 访问
sudo ufw delete allow from 203.0.113.101
# 取消开放 8080 端口
sudo ufw delete allow 8080

服务器注意事项 #

在服务器使用 UFW 必须先执行 ufw allow ssh(允许 SSH 连接),再执行 ufw default deny incoming,最后执行 ufw enable,不然会导致无法用 SSH 连接服务器。

延伸阅读 #

解决 Arch Linux(KDE Plasma)中 Locale LANG=C 的问题

2021-10-08 00:00:00

最近在 Arch Linux 上的 Git 显示不了中文,执行了 git config --global core.quotepath false 还是不行。git log 显示的中文变成这样了:

1
Markdown <E6><8A><80><E5><B7><A7><EF><BC><9A>Rmarkdown -> R Markdown

它应该是这样的:

1
Markdown 技巧:Rmarkdown -> R Markdown

查看 locale,发现 locale 居然不是en_US.UTF-8,而是 C,这是啥啊?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ locale
LANG=C
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"
LC_ALL=

我之前设置的 locale 明明是 en_US.UTF-8。

1
sudo localectl set-locale LANG=en_US.UTF-8

查阅万能的 ArchWiki 后发现我似乎在 KDE Plasma 把 locale 设置成 Default (C) 了(System Settings -> Regional Settings -> Formats -> Region)。

1
2
3
❯ cat ~/.config/plasma-localerc
[Formats]
LANG=C

把 Plasma 的 locale 配置文件删除后重启就行了。

1
rm -i ~/.config/plasma-localerc

修复 MagicBook 14(Ryzen5 3500U)在 Arch Linux 下亮度突然变 0 的问题

2021-10-06 00:00:00

2023 年 3 月 8 日更新:MagicBook 14 在以下内核亮度正常,已经不需要改内核参数了。

现在加上参数反而会导致无法调节亮度。如果之前改过内核参数,请删除并重新生成引导程序配置。


我的 MagicBook 14(Ryzen5 3500U)休眠/睡眠/插电/断电/开机后可能亮度降为 0。解决办法:启动时使用以下内核参数。

1
acpi_backlight=vendor

如果你用 GRUB,就修改 /etc/default/grub,在 GRUB_CMDLINE_LINUX_DEFAULT 加上内核参数,如果有多个参数就以空格分开。

1
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=5 acpi_backlight=vendor"

然后刷新配置文件:

1
sudo grub-mkconfig -o /boot/grub/grub.cfg

参考资料:[SOLVED] Failed to start Load/Save Screen Backlight Brightness / Laptop Issues / Arch Linux Forums

延伸阅读:What do the kernel parameters acpi_osi=linux and acpi_backlight=vendor do? - Unix & Linux Stack Exchange

Arch Linux 安装与配置记录

2021-09-28 00:00:00

使用感受 #

Arch Linux 用起来太爽了,我已经在主力电脑装上了!软件超多,官方仓库加上 archlinuxcn 仓库AUR,真的是什么都能安装。滚动更新保证软件都是最新版,还不用像 Ubuntu 那样苦苦地等半年更新,也不用准备一大段时间来更新系统,每次用电脑更新一下就行,也就几分钟的事情。ArchWiki 的资料超级全面,我要查的东西里面都有。Arch Linux 平时用起来比 Kubuntu 更快,开机才三四秒,关机也是几秒钟。之前用 Kubuntu 遇到的问题在 Arch Linux 上都没了:关机两三分钟才行、休眠后不会关屏幕、版本更新后要手动修改软件源、KDE Plasma 没有休眠按键。pacman 安装和卸载软件也超快,甩 apt 几条街。AUR 虽然什么软件都有,但都是未经审核的,我还挺担心遇到恶意代码。接下来学学 Bash 和打包软件才行,不然看不懂 AUR 上面的打包脚本。

安装 #

本文多次出现 cat 命令,例如:

1
2
3
4
 cat ~/.xprofile
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx

以上命令在本文的意思是创建或修改 ~/.xprofile,并添加

1
2
3
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx

Arch Linux 的安装教程有很多,我安装的时候主要看这三份。

我的电脑用 UEFI 启动,使用三个分区。

EFI 分区本来就有了,跳过创建与格式化 EFI 分区的步骤。创建 /swap 分区即可。感觉用命令行分区好难,我就用 U 盘启动 Kubuntu,用里面的分区软件来分区,用 Windows PE 也行。分区完就可以启动 Arch Linux 安装镜像了,先格式化再挂载。

1
2
3
4
5
6
7
8
9
# 格式化
mkfs.ext4 /dev/nvme0n1p4
mkswp /dev/nvme0n1p5

# 挂载
mount /dev/nvme0n1p4 /mnt # 一定要先挂载 / 再挂载其他
mkdir /mnt/efi
mount /dev/nvme0n1p1 /mnt/efi
swapon /dev/nvme0n1p5

配置 #

添加 archlinuxcn 源 #

1
2
3
❯ cat /etc/pacman.conf
[archlinuxcn]
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch
1
sudo pacman -Syu archlinuxcn-keyring

让 pacman 同时下载多个包 #

1
2
 cat /etc/pacman.conf
ParallelDownloads = 16 # 同时下载 16 个包

安装软件 #

Arch Linux 不支持更新部分软件(partial upgrades),所以我使用 pacman -Syu package 命令,先更新所有软件再安装需要的软件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
# 工具
sudo pacman -Syu trash-cli \
mplayer \
vlc \
flameshot \
spectacle \
obs-studio \
aria2 \
okular \
kdegraphics-mobipocket \
man-db \
gwenview \
os-prober \
pamac-aur \
ufw \
gufw \
ufw-extras \
ntfs-3g \
z \
virtualbox \
virtualbox-guest-iso \
virtualbox-guest-utils \
virtualbox-ext-oracle

yay -Syu dropbox \
google-chrome

# 美化
sudo pacman -Syu tela-icon-theme-git \
tela-circle-icon-theme-git \
grub-theme-whitesur-white-1080p-git

# 编程
sudo pacman -Syu openssh \
nodejs \
npm \
yarn \
webstorm \
webstorm-jre \
rstudio-desktop-git \
android-tools \
tldr \
github-cli \
git

# 学习
sudo pacman -Syu goldendict-qt5-git
yay -Syu anki-release-source


# 安装 ZSH 与插件
sudo pacman -Syu zsh \
oh-my-zsh-git \
zsh-theme-powerlevel10k \
powerline-fonts \
awesome-terminal-fonts \
zsh-syntax-highlighting-git \
zsh-autosuggestions \
zsh-completions-git

# 中文字体
sudo pacman -Syu wqy-bitmapfont \
wqy-microhei \
wqy-microhei-lite \
wqy-zenhei \
adobe-source-han-sans-cn-fonts \
adobe-source-han-serif-cn-fonts \
noto-fonts \
noto-fonts-cjk \
noto-fonts-emoji \
noto-fonts-extra

# 通讯
sudo pacman -Syu telegram-desktop \
thunderbird
yay -Syu wechat-uos \
deepin-wine-tim \
deepin-wine-wechat

# Fcitx5 输入法
sudo pacman -Syu fcitx5-im \
fcitx5-chinese-addons \
fcitx5-material-color \
fcitx5-pinyin-zhwiki

# 办公软件
# WPS
yay -Syu wps-office \
wps-office-mui-zh-cn \
ttf-wps-fonts \
wps-office-fonts
# LibreOffice
sudo pacman -Syu libreoffice-fresh \
libreoffice-fresh-zh-cn
# OnlyOffice
sudo pacman -Syu onlyoffice-bin

# 杀毒
sudo pacman -Syu clamav clamtk
sudo freshclam
sudo systemctl enable clamav-freshclam.service --now
sudo systemctl enable clamav-daemon.service --now
# 杀毒命令
# clamdscan --multiscan --fdpass

# 性能增强
yay -Syu auto-cpufreq
systemctl enable --now auto-cpufreq
sudo pacman -Syu linux-zen linux-zen-headers

配置 Fcitx5 输入法 #

1
2
3
4
5
6
❯ cat ~/.pam_environment
GTK_IM_MODULE DEFAULT=fcitx
QT_IM_MODULE DEFAULT=fcitx
XMODIFIERS DEFAULT=\@im=fcitx
INPUT_METHOD DEFAULT=fcitx
SDL_IM_MODULE DEFAULT=fcitx
1
2
3
4
 cat ~/.xprofile
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx

Fcitx5 的快捷键是 Control + Space,编程软件的补全快捷键也是这个,把 Fcitx5 的快捷键改为 Super + Space 以避免冲突。打开 Fcitx 5 Configuration,点击 Configure global options,把 Trigger Input Method 改为 Super + Space

配置中文优先级 #

noto-fonts-cjk 包括中文、日文、韩文字体,安装后有时候中文被错误地显示为日文。把中文字体优先级调到日语前面就可以解决这个问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
❯ cat ~/.fonts.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
 <alias>
 <family>sans-serif</family>
 <prefer>
 <family>Noto Sans CJK SC</family>
 <family>Noto Sans CJK TC</family>
 <family>Noto Sans CJK JP</family>
 </prefer>
 </alias>
 <alias>
 <family>monospace</family>
 <prefer>
 <family>Noto Sans Mono CJK SC</family>
 <family>Noto Sans Mono CJK TC</family>
 <family>Noto Sans Mono CJK JP</family>
 </prefer>
 </alias>
</fontconfig>
1
2
# 刷新字体缓存
fc-cache -fv
1
2
3
fc-match -s | grep 'Noto Sans CJK'
# 如果出现下面内容就代表字体优先级修改成功
# NotoSansCJK-Regular.ttc: "Noto Sans CJK SC" "Regular"

配置 GRUB #

取消 GRUB 的子菜单,并让它记住上次的启动项。这样选择内核的时候更方便。

  1. 编辑配置文件

    1
    
    sudo nano /etc/default/grub
    
  2. GRUB_DEFAULT=0 改为 GRUB_DEFAULT=saved

  3. 去掉 GRUB_SAVEDEFAULT=true 前面的 #

  4. 去掉 GRUB_DISABLE_SUBMENU=y 前面的 #

  5. GRUB_CMDLINE_LINUX_DEFAULT= 里加入 nowatchdog,以空格与其他内容分开,加入该参数可加快开关机速度

  6. Ctrl + O 保存,Ctrl + X 退出

  7. 更新配置文件

    1
    
    sudo grub-mkconfig -o /boot/grub/grub.cfg
    

配置休眠 #

  1. 查看 swap 分区名称

    1
    
    sudo fdisk -l
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    Disk /dev/nvme0n1: 476.94 GiB, 512110190592 bytes, 1000215216 sectors
    Disk model: WDC PC SN730 SDBPNTY-512G-1027 
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: gpt
    Disk identifier: DF3F41B3-00FB-45D1-8B23-3929872B9524
    
    Device Start End Sectors Size Type
    /dev/nvme0n1p1 2048 206847 204800 100M EFI System
    /dev/nvme0n1p2 206848 239615 32768 16M Microsoft reserved
    /dev/nvme0n1p3 239616 168011775 167772160 80G Microsoft basic data
    /dev/nvme0n1p4 168011776 938358619 770346844 367.3G Linux filesystem
    /dev/nvme0n1p5 938358784 971902975 33544192 16G Linux swap
    /dev/nvme0n1p6 971902976 972951551 1048576 512M Windows recovery environment
    /dev/nvme0n1p7 972951552 998117375 25165824 12G Windows recovery environment
    /dev/nvme0n1p8 998117376 1000214527 2097152 1G Windows recovery environment
    

    /dev/nvme0n1p5 就是 swap 分区。

  2. 配置 GRUB

    1
    
    sudo nano /etc/default/grub
    

    GRUB_CMDLINE_LINUX_DEFAULT 里加入 resume=/dev/nvme0n1p5,以空格与其他内容隔开。修改完就这样

    1
    
    GRUB_CMDLINE_LINUX_DEFAULT="loglevel=5 nowatchdog resume=/dev/nvme0n1p5"
    
  3. 更新 GRUB 配置文件

    1
    
    sudo grub-mkconfig -o /boot/grub/grub.cfg
    
  4. 重启后生效

    1
    
    reboot
    

配置 Git 与 Github CLI #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 导入 GPG 私钥
gpg --import armor.asc
# 用 GPG 签署 commit
gpg --list-secret-keys --keyid-format LONG
git config --global user.signingkey FRUR8JBULWM31RFB
git config --global commit.gpgsign true
# 设置编辑器为 nano
git config --global core.editor nano
# 显示中文
git config --global core.quotepath false
# 设置名称与邮箱
git config --global user.name "Joe"
git config --global user.email "[email protected]"

配置 ZSH 与插件 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 启用 Oh My Zsh
cp /usr/share/oh-my-zsh/zshrc ~/.zshrc
# 启用 Powerlevel10k 主题
echo 'source /usr/share/zsh-theme-powerlevel10k/powerlevel10k.zsh-theme' >> ~/.zshrc
# 启用 zsh-syntax-highlighting(语法高亮)
echo 'source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh' >> ~/.zshrc
# 启用 zsh-autosuggestions(自动提示)
echo 'source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh' >> ~/.zshrc
# 启用 zsh-completions-git(自动补全)
echo 'fpath=(/usr/share/zsh/site-functions $fpath)' >> ~/.zshrc
echo 'autoload -U compinit' >> ~/.zshrc
echo 'compinit -i' >> ~/.zshrc
rm -f ~/.zcompdump; compinit
# 启用 z
echo '[[ -r "/usr/share/z/z.sh" ]] && source /usr/share/z/z.sh' >> ~/.zshrc
# 把默认 Shell 修改为 ZSH
chsh -s /bin/zsh
# 进入 zsh
zsh
# 配置 Powerlevel10k 主题
p10k configure
# 配置 gh 补全(需要先安装 github-cli)
gh completion -s zsh > ~/_gh
sudo mv ~/_gh /usr/share/zsh/site-functions
# 配置 yarn 全局安装包的路径
echo 'export PATH="$PATH:$(yarn global bin)"' >> ~/.zshrc

防火墙 #

请看我的这篇教程:给本地电脑配置 UFW(Uncomplicated Firewall)防火墙

迁移 Thunderbird 数据 #

Moving Thunderbird Data to a New Computer

先把 ~/.thunderbird 文件夹从旧电脑放入网盘,在新电脑打开网盘内的 .thunderbird,把里面的内容复制到新电脑的 ~/.thunderbird,如果有重复文件就选择覆盖。

修复 Windows 引导 #

https://wiki.archlinux.org/title/GRUB#Detecting_other_operating_systems

安装 Arch Linux 的时候,按照这个教程把 EFI 分区格式化了,结果 Windows 10 的引导文件也没了,头疼。不过用优启通 PE修复了。修复方法:用 U 盘启动优启通,点击「开始菜单 -> 程序 -> 系统安装 -> NTBootAutoFix(引导修复工具)-> 选择 EFI 盘符 -> 开始修复」。优启通网盘下载的速度特慢,想快点修复引导可以去下载微 PE 工具箱,这个下载速度还行,里面也有类似的引导修复工具,不过我没测试过。

接下来用 Arch Linux 把 Windows 启动项添加到 GRUB。

1
sudo pacman -Syu os-prober

加入/取消注释这一行:

1
2
❯ cat /etc/default/grub
GRUB_DISABLE_OS_PROBER=false
1
sudo grub-mkconfig -o /boot/grub/grub.cfg

解决用笔记本作 OpenWrt 路由器遇到的免密码登陆与屏幕常亮问题

2021-09-22 00:00:00

用笔记本电脑作 OpenWrt 路由器有两个问题:不需要密码就登陆、屏幕不会自动关闭,解决办法如下。

关闭免密码登陆 #

编辑 /etc/config/system,把 config system 里的 option ttylogin '0' 改为 option ttylogin '1',改完后是这样的:

1
2
3
config system
 option hostname 'OpenWrt'
 option ttylogin '1'

关闭屏幕 #

1
2
3
4
opkg update
opkg install setterm
# 一分钟不动就关屏幕
setterm --blank 1

如果 --blank 选项不行就改成 --powerdown

缘起 #

上大学前(2017 年)买了联想 Miix 510-12IKB(Miix 5 Plus),后来大三的时候开始学编程。Miix 只有 8G 内存,实在是太小了。于是换了台 16G 内存的 Magicbook 14,从此 Miix 就长期处于闲置状态。最近打开几个月没开机的 Miix,屏幕居然多了两条竖线。也不知道是不是之前寄快递的时候压坏了。去查了保修时间,已经过期了。唉,看来还是不修了吧,修还得花钱。可这屏幕的两条竖线看着真不舒服,拿来刷视频都不行了。真为 Miix 的前途感到担忧,难道它年纪轻轻就要退休了吗?思考再三,我决定以后拿它作旁路由。我现在用树莓派 4B 作旁路由,它搭配 500Mbit 宽带使用没问题,和 1000Mb 宽带一起用就性能不足了。日后用上 1000Mb 宽带,Miix 就是树莓派的接班人啦。

接下来就测试一下,我把 OpenWrt 装到 U 盘,用 Miix 启动,确实能用来做旁路由。但有两个问题:不需要密码就登陆、一直开屏幕。免密码登陆不安全,一直开着屏幕费电,而且我担心屏幕一直开着会坏掉。上谷歌用中文和英文搜这两个问题,没有免密码登陆的相关资料。查到了屏幕常亮相关的中文帖子,解决办法居然是把屏幕拆掉或拔掉屏幕排线。嗯……解决不了问题,就解决引起问题的东西,这也不失为一种办法。可这平板电脑看起来就很难拆,拆坏就亏大了,还是别了。最后我去 OpenWrt 论坛问了这两个问题,很快就有热心人士帮忙解决了。原贴:

树莓派 4B 超频教程

2021-09-20 00:00:00

本文配套文章:在 OpenWrt 控制树莓派 Argon Mini Fan

准备散热器和充电器 #

树莓派 4B 温度达到 80°C 后 CPU 会降频,超过 85°C 后 CPU 和 GPU 都降频。所以超频前一定要给树莓派 4B 加上散热器以保证温度不超过 80°C。要是超频后达到 80°C 降频就得不偿失了。常见的散热器包括散热片、散热风扇、金属外壳等。我用的是 Argon Neo 和 Argon Mini Fan。用风扇不要到 80°C 才启动,那会都降频了,要在降频前启动,比如超过 60°C 就启动或者一直开着。

充电器电压至少 4.8 V,建议使用树莓派官方充电器或者 5V 3A 充电器。

超频设置 #

根据树莓派官方杂志的文章,把树莓派 CPU 超频至 2.0GHz。

  1. 修改 /boot/config.txt

    1
    
    nano /boot/config.txt
    
  2. 加入以下内容。

    1
    2
    3
    
    # overclock
    over_voltage=6
    arm_freq=2000
    
  3. 重启

    1
    
    reboot
    

OpenWrt 的额外设置 #

我使用的 SuLingGG/OpenWrt-Mini 固件自带 CPU 频率调节软件,需要进行额外配置。访问 luci 管理界面,点击 「系统 -> CPU 性能优化调节设置」,把「最大 Turbo Boost CPU 频率」调为 2000000

取消超频 #

如果超频后无法开机,就把内存卡拔下来插入电脑,修改 /boot/config.txt,把频率调到可以开机。如果实在不行就把添加的超频配置都删了。

测试 #

  1. 安装压力测试软件 stress-ng

    1
    2
    
    opkg update
    opkg install stress-ng procps-ng-watch
    
  2. 对 CPU 进行压力测试,使其频率达到 2.0GHz

    1
    
    stress-ng --cpu 0
    
  3. 开启另一个窗口查看 CPU 频率

    1
    
    watch -n 1 vcgencmd measure_clock arm
    
  4. 频率到达 2000000000 左右就对了,按下 Ctrl + C 关闭程序

进阶 #

树莓派超频的最高频率是 2.147GHz,但可能会导致树莓派无法重启(我的树莓派就是)。所以建议用 2.0GHz。树莓派可以超频 GPU,但官方人员说作用很小。

Our engineering team told us that the benefits from gpu_freq are marginal at best, and it should be removed if Raspberry Pi 4 fails to boot.

How to overclock Raspberry Pi 4 — The MagPi magazine

如果你要超频 GPU,就在 /boot/config.txt 加上这行代码:

1
gpu_freq=750

这样 GPU 就超频到 750 MHz 了。

加上这行代码后树莓派会一直以最高频率运行,建议别用。

1
force_turbo=1

完整的配置内容是这样的:

1
2
3
4
5
# overclock
over_voltage=6 # 增加电压
arm_freq=2000 # CPU 频率,单位是 MHz,最高 2147
# gpu_freq=750 # GPU 频率,单位是 MHz
# force_turbo=1 # 以最高频率运行

上述代码只把 CPU 超频至 2.0GHz。# 之后的内容不会被系统读取,换言之,配置前面加上 # 就代表禁用,没有 # 就代表启用。如果不用超频功能,就改成这样:

1
2
3
4
5
# overclock
# over_voltage=6 # 增加电压
# arm_freq=2000 # CPU 频率,单位是 MHz,最高 2147
# gpu_freq=750 # GPU 频率,单位是 MHz
# force_turbo=1 # 以最高频率运行

修改文件后重启才会生效。

参考资料 #

How to overclock Raspberry Pi 4 — The MagPi magazine

在 OpenWrt 控制树莓派 Argon Mini Fan

2021-09-15 00:00:00

本文配套文章:树莓派 4B 超频教程

本文测试于 SuLingGG/OpenWrt-Mini 固件,系统版本:ImmortalWrt 18.06-SNAPSHOT r0-b0fa0c9 / LuCI openwrt-18.06-k5.4 branch (git-21.247.81448-3061bdd)

配置 #

  1. 把风扇的档位调至 PWM

  2. 连接 OpenWrt

    1
    2
    
    # 请使用你树莓派的 IP,不要照抄我的
    ssh [email protected]
    
  3. 备份配置文件

    1
    
    cp /boot/config.txt /boot/config.txt.bak
    
  4. 编辑配置文件 /boot/config.txt

    1
    
    nano /boot/config.txt
    

    在底部添加以下内容,树莓派超过 60°C 时风扇就转

    1
    
    dtoverlay=gpio-fan,gpiopin=18,temp=60000 # 单位是 1/1000°C
    

    按下 Ctrl + O 保存,Ctrl + X 退出

  5. 安装插件

    1
    2
    
    opkg update
    opkg install kmod-hwmon-core kmod-hwmon-gpiofan kmod-i2c-core
    
  6. 重启

    1
    
    reboot
    
  7. 如果要修改温度就改动 /boot/config.txt 再重启

    1
    2
    
    nano /boot/config.txt
    reboot
    

测试 #

  1. 安装压力测试软件 stress-ng

    1
    2
    
    opkg update
    opkg install stress-ng procps-ng-watch
    
  2. 对 CPU 进行压力测试,使其超过 60°C

    1
    
    stress-ng --cpu 0
    
  3. 开启另一个窗口监控温度

    1
    
    watch -n 1 vcgencmd measure_temp
    
  4. 超过 60°C 后看看风扇转不转,如果转了就按下 Ctrl + C 关闭程序。温度下降后再看看风扇转不转,不转就对了

后记 #

Argon Neo 搭配 Argon Mini Fan 真好用啊!调好风扇之后树莓派就可以做一个安静的路由器了,只有温度过高时风扇才转。要是你的树莓派也用 OpenWrt,不要用 Argon Fan HAT。OpenWrt 不能安装 Argon Fan HAT 的控制脚本,装上风扇后它只会以 50% 的转速一直转。

默认的 config.txt #

这是 SuLingGG/OpenWrt-Mini 固件 /boot/config.txt 的内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
################################################################################
# Bootloader configuration - config.txt
################################################################################

################################################################################
# For overclocking and various other settings, see:
# https://www.raspberrypi.org/documentation/configuration/config-txt/README.md
################################################################################

# OpenWrt config
include distroconfig.txt

[pi2]
dtparam=i2c_arm=on,audio=on

[pi3]
dtparam=i2c_arm=on,audio=on

[pi4]
dtparam=i2c_arm=on,audio=on

[all]
# Place your custom settings here.
dtparam=i2c_arm=on,audio=on

参考资料 #

树莓派 4B OpenWrt 旁路由教程

2021-08-30 00:00:00

2022-12-04 更新 #

现在树莓派的价格贵得离谱,性价比很低。如果你还没买,建议你别买。如果你已持有树莓派,建议去闲鱼卖了,换成玩法更多、性能更强的 X86 软路由/迷你主机。我 2019 年 10 月买的树莓派 4B 4GB 内存版价格是 462 元,现在同一家店卖 1098 元。我在闲鱼以 950 元卖掉了树莓派 4B 4GB(含外壳、风扇、电源、HDMI 线、内存卡),买了 1095 元的零刻 EQ59(N5105、16GB RAM、500GB SSD)。EQ59 性能远超树莓派 4B,我在 EQ59 装了 Proxmox VE,可以装多个虚拟机,用途更多,比树莓派好用多了。

树莓派作路由器有什么功能? #

本文内容主要来自 SuLingGGOpenWrt-Rpi 文档,感谢 SuLingGG 编译的 OpenWrt 并写了详细的文档。我的吃灰派树莓派 4B 在墓里躺了近两年,又复活了。欢迎大家捐赠 SuLingGG 编译的 OpenWrt。如果本文太难懂,请到 Youtube 或者哔哩哔哩搜索「树莓派安装 OpenWrt」和「树莓派 旁路由」,看看教程视频了解过程。然后关闭这篇教程再回来看本文。本文主要内容为安装 OpenWrt 固件、旁路由(旁路网关)设置、后续设置(扩充容量、关闭电源灯……)。

请看 SuLingGG 的文档:

刷入固件 #

刷入固件前先准备好作案工具:

准备完毕,现在开始刷入固件:

  1. 把闪存卡插入读卡器,读卡器插入电脑
  2. 打开 balenaEtcher
  3. 点击「Flash from file」
  4. 选择解压好的「openwrt-bcm27xx-bcm2711-rpi-4-ext4-factory.img」
  5. 点击「Select target」,选择闪存卡,注意看容量对不对,请勿选择本机硬盘
  6. 点击「Flash!」
  7. 成功后把闪存卡插入树莓派,树莓派插电后红灯常亮表示正常

树莓派设置 #

路由器默认 IP 是 192.168.1.1,用户为 root,密码为 password。如果操作中出现问题导致无法继续配置,请重新刷入固件。

连接树莓派 #

选择一种连接树莓派的方式:

修改 WIFI 密码 #

为了防止别人连上 WIFI 捣蛋,先修改 WIFI 密码。

  1. 点击「网络 -> 无线 -> 修改」
  2. 点击「基本设置」,在「ESSID」填入 WIFI 名称
  3. 点击「无线安全」,在「密码」填入新密码
  4. 点击「保存&应用」

更改 LAN 口 IP 地址 #

我的路由器管理界面地址为 192.168.0.1,接下来要把树莓派的 IP 改为与路由器同一网段,即 192.168.0.x(0≤x≤255)。这里我把树莓派 LAN 口 IP 设置为 192.168.0.100。为了避免冲突,不要把树莓派 LAN 口 IP 最后一段设置成 0/1/255。请根据情况设置,不要照搬我的 IP。如果你的路由器管理界面为 192.168.31.1,那你可以把 IP 设置为 192.168.31.100。更改 IP 后请记好,以后管理界面都在 192.168.0.100。设置前在电脑可以打开终端(CMD 或 Powershell )检查是否冲突:

1
ping 192.168.0.100

如果显示「Destination Host Unreachable」就代表地址没被占用。如果没问题就选择一种设置方式将 LAN 口 IP 改为 192.168.0.100:

设置好后重新连接树莓派网络。

更改 LAN 口参数 #

连接路由器 #

断开电脑与树莓派的连接,用网线将树莓派接到路由器 LAN 口。

客户端设置 #

连接主路由的设备需要进行以下设置:

安装后的设置 #

建议修改管理员密码,其余设置按需调整。

扩充容量 #

固件只使用闪存卡 1G 左右的空间,剩下的空间就闲置了。我前面刷入了 ext4 固件,使用 Linux 系统的 GParted 调整 rootfs 分区即可扩充空间。rootfs 分区前后至少留出 4M 的空间3。详细教程:SD 卡分区扩容指南 | 美丽应用

修改管理员密码 #

  1. 点击「系统 -> 管理权」
  2. 在「密码」和「确认密码」处填入管理员密码
  3. 在「SSH 密钥」处填写公钥后可以 SSH 免密码连接
  4. 点击「保存&应用」

关闭电源灯 #

红色电源灯看着不舒服,于是我就关掉了。

点击「系统 -> LED 配置」,使用两个配置。

防火墙 #

我的树莓派刷入系统网络后没问题,但还是引用一下旁路由设置指南的内容,留给需要的人。

  1. 如果你的上级路由固件为 老毛子 Pandavan,树莓派做旁路由出现卡顿情况的话,请尝试关闭 Wan 口设置中的 “IPv4 硬件加速”

  2. 如果你严格按照此文章操作,但出现 无法上网、访问国内网站缓慢 等症状,可在完成上文全部步骤的基础上,在“网络 - 防火墙 - 自定义规则”中新增一行 iptables 规则并重启防火墙再试:

    1
    
    iptables -t nat -I POSTROUTING -j MASQUERADE
    

    若仍未解决,可以尝试删除原有两条规则并重启防火墙再试:

    1
    2
    
    iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53
    iptables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53
    
  3. 如果以上两个方法未能解决,请尝试在 Lan 口的“物理设置”中取消“桥接接口”的打勾,并在下方的“接口”中选择“eth0”,保存并应用再试。但请注意,此时树莓派的板载无线网卡将无法使用 (有信号但无法上网)。

  4. 本篇文章所述设置方法可能对某些型号的华为路由器无效,建议更换其他路由器再试。

超频与散热 #

超频后性能更强,相关教程请看我写的两篇博客。

问答 #

树莓派与路由器断开连接后,怎么让电脑连接树莓派? #

通过 WIFI 或网线连接树莓派,手动把 IP 设置为 192.168.0.100 的同一网段,即 192.168.0.x(0≤x≤255,x≠100),例如 192.168.0.1。此时就能连接,可以用浏览器进入管理界面 192.168.0.100 了。

忘了树莓派的 IP 地址怎么办? #

先把 microSD 卡插入 Linux 电脑,然后打开终端执行命令。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 查看分区
# user 为你的用户名
ls /media/user
# 11cd116c-beae-43ae-bfb5-b8ec18c9b98a boot disk

# 进入分区
# 请根据情况输入分区名称
cd /media/user/11cd116c-beae-43ae-bfb5-b8ec18c9b98a

# 查看 ip
grep "ipaddr" ./upper/etc/config/network
# option ipaddr '127.0.0.1'
# option ipaddr '192.168.0.100'

输出结果里的 192.168.0.100 就是 IP 树莓派地址。如果你不会用 Linux 就重新刷固件吧。

OpenWrt-Rpi 的插件太多了,有精简版吗? #

请使用 OpenWrt-Rpi 的姊妹版 OpenWrt-Mini,点击这里下载,安装方法和 OpenWrt-Rpi 一样。

参考资料 #


  1. microSD 才是正确的名称,但它常被误称为 TF 卡。两者的关系如下(摘录自维基百科):microSD卡原本称为TF卡(T-Flash卡或TransFlash),由摩托罗拉与闪迪共同研发,在2004年推出。不过闪迪无法自行将它推广普及化,前期仅有摩托罗拉的手机支持TransFlash。为了能将销路完全拓展,闪迪于是将TransFlash规格并入SD协会,成为SD家族产品之一,造就了目前使用最广泛的手机存储卡。 ↩︎

  2. microSD 是闪存(flash memory)卡,而不是内存(RAM,Random Access Memory)卡,但「内存卡」的说法更广泛。 ↩︎

  3. 「rootfs 分区前后至少留出 4M 的空间」的说法是在这里看到的,其实我也不知道为什么要留出 4M 空间。如果你知道,请告诉我。 ↩︎

第一次租国外的服务器

2021-08-10 00:00:00

最近想租个服务器玩玩,因为我讨厌手机号注册和提交身份信息,就不考虑国内厂商了。之前在知乎好几次看到有人推荐 Vultr,那就试试看吧。注册好就用 Paypal 充了 10 美元,结果就倒霉了。Vultr 提示我帐号审核可能要 24 小时。

Vultr 的帐号客服只在周一至周五的白天1上班,而且 Vultr 采用美国东部时间,比北京时间慢 12 小时。我这边晚上 9 点的时候,对面的客服才刚上班。没有即时聊天,只能发工单,以下是艰难的验证过程(时间为北京时间)。

第1 天 2021-08-09 #

16:03 #

注册并用 Paypal 充值 10 美元。

21:16 #

收到通知说需要提供身份证和信用卡照片来验证帐号。身份证只需要显示姓名和照片,信用卡只需显示姓名和后四位。

The following will be required to complete the form for verification sufficiently:

  1. A photo of your government ID (showing a name and photo only) and

  2. A partial photo of the credit card linked to the account (showing the last four numbers & name only).

注意,要抹掉信用卡安全码,不然有被盗用的风险。

21:40 #

我问客服审核后会不会销毁照片。

How do you deal with the photos after verification? Will it be destroyed?

第 2 天 2021-08-10 #

01:23 #

客服说审核后会删除照片。

This is currently our vetting process but, rest assured, all electronic copies will be deleted after review and you may cover any pertinent information you deem sensitive.

01:56 #

提交身份证和信用卡照片。身份证只有姓名和号码没涂抹。信用卡抹掉安全码和号码前面部分,只留下后 4 位,其他位置未涂抹。

02:19 #

客服说要拍到身份证的照片才行。

02:24 #

补发带照片的身份证。

04:39 #

客服说要手持身份证和脸一块拍照。

We were unable to validate some of the information provided. To help this process along, please provide the following:

  1. A photo of you holding the recently uploaded ID (next to face)

我吐了,怎么不早说。前面白搞了,又得等了。

16:29 #

上传手持身份证的照片,身份证只有姓名、号码、和照片没涂抹。

第 3 天 2021-08-11 #

03:18 #

认证还没行,客服询问我公司名称和服务器的用途。

We appreciate the information provided however, we need more information to validate your intended usage.

Please describe in as much detail as possible what you intend to do with your instances and provide the business name and organization URL(s) under which you offer services.

11:59 #

真是无语了,买个服务器也问东问西的,而且我都没有公司。就说买服务器用来学 Linux,也可能搭建个人网站。

I don’t own or belong to any business. I want to use remote servers to learn linux distros. I also want to build personal websites via servers.

12:01 #

认证还没行。客服认为我有多个帐号,说一人应该只用一个帐号,请我解释解释。

It appears you have multiple accounts with us. The system generally only allows one account per customer, can you please clarify why you need multiple accounts with us?

12:10 #

我又吐了,我第一次注册,哪来的多帐号。

Oh, no. I have and use only one account.

第 4 天 2021-08-12 #

02:30 #

客服说搞定了。

Thank you for providing the requested information. I have finalized your account authorization at this time. I appreciate your patience regarding this matter.

感想 #

没想到在 Vultr 买个服务器居然用了 4 天,真的吐血了。隔着 12 个小时联系真困难。第三天(8 月 11 号)都要气死了,把玩服务器的热情都耗尽了2,然后试了 Linode。用 Linode 倒是挺顺利的,注册完用银联信用卡作为支付方式,马上就能用了。早知道之前在 Vultr 申请退款算了。罢了罢了,就当是练英语吧。

我试了以下服务器:Vultr 的东京(Tkoyo),Linode 的东京、新加坡(Singapore)。在本地机器 SSH 连接过去,打字都卡,用科学上网也一样。用 Mosh 连接就好多了,但是要使用 tmux 才能滚动终端,最后发现用 Visual Studio Code 的Remote - SSH 插件连接延迟更低,也能滚动终端,还能处理服务器文件(上传、下载、编辑),十分好用。

收费方式 #

Vultr 和 Linode 都是按小时计费,每月 1 号扣钱。服务器关机占坑也会扣钱,删除才不扣钱。如果 Vultr 帐号有余额,那删除就会马上扣钱。Vultr 只有纽约(New York)有 2.5 美元/月和 3.5 美元/月的服务器,其余地区最便宜是 5 美元/月。Linode 最便宜是 5 美元/月。

Vultr 注册完就叫我充值,还有信用卡充值多少就送多少的优惠。看到这优惠就想充 100 美元,差点就中招了。还好只充了 10 美元。Linode 就比 Vultr 实在多了。Linode 一开始就绑定信用卡,不会叫人充值,用多少就扣多少钱。现在 Vultr 那边充了 10 美元还没怎么用,感觉有点浪费。还是用信用卡好,用多少就扣多少钱。

Vultr 和 Linode 都有注册用余额的活动,在谷歌搜「Vultr coupon」和「Linode coupon」就能找到了,通过 Vultr 和 Linode 用户的邀请链接注册也有优惠。后来我又注册了 DigitalOcean,注册后用优惠券白拿了 15 美元。DigitalOcean 和前面两家厂商也差不多,都是按小时计费,最便宜的服务器也是 5 美元/月。但 DigitalOcean 的帐单不是实时更新,一日更新一次。

如果你想注册 Vultr 或 DigitalOcean,可以试试我的邀请链接,注册后会有余额奖励。


  1. 我记得是早上 9 点到下午 6 点,查了一下时间,但没查到,也懒得找了。 ↩︎

  2. 我第一次搭建网站的时候以为一定要备案,经历了曲折的过程后就丧失热情了。看来热情等着等着就会耗尽啊。 ↩︎

在 Debian/Ubuntu 安装旧版 WPS Linux

2021-08-08 00:00:00

最近把 WPS For Linux 更新到最新的 11.1.0.10702,打开就提示「系统DPI不对称,WPS可能存在显示问题」,果然 WPS 都是模糊的。打算去官网找上个版本,结果只有最新版,气死我了!在谷歌搜了几次也找不到历史版本。随后我在万能的 AUR(Arch User Repository)找到了 WPS,这里有旧版的下载链接。以下是下载与安装步骤:

  1. 点击页面的 View Changes

  2. 在 Commit message 这里找到上个版本 11.1.0.10161

  3. 点击 upgpkg: wps-office-cn 11.1.0.10161

  4. 搜索 source_x86_64 = ,找到 + 开头的绿色文本,https://wdl1.cache.wps.cn/wps/download/ep/Linux2019/10161/wps-office_11.1.0.10161_amd64.deb 就是我们要的下载地址

  5. 安装后要删除配置才能用

    1
    2
    3
    4
    5
    6
    
    # 下载
    wget https://wdl1.cache.wps.cn/wps/download/ep/Linux2019/10161/wps-office_11.1.0.10161_amd64.deb
    # 安装
    sudo apt install ./wps-office_11.1.0.10161_amd64.deb
    # 删除配置文件
    rm -rI /home/hunter/.config/Kingsoft/
    

后记:我从未用过 Arch Linux,我一开始把仓库克隆下载就查看 PKGBUILD 文件,歪打误撞地得到了下载地址。后来才发现下载地址就在 .SRCINFO 文件,之前真的白费力气了。突然感觉 Arch Linux 真的是啥软件都有人打包,连 UOS 独占的微信都有,搞得我都想从 Kubuntu 换过去了。Ubuntu 半年才更新一次,每次更新完都要手动处理软件源,实在是烦。Arch Linux 这种滚动更新的 Linux 发行版对我来说很有吸引力,有空的时候要试试。

以下是先前惨痛的尝试。


这有个 Git 仓库,里面的 PKGBUILD 就是安装脚本。在里面能找到旧版的下载链接。

克隆仓库到本地。

1
2
3
4
git clone https://aur.archlinux.org/wps-office-cn.git
cd wps-office-cn
git log --pretty=oneline --graph --abbrev-commit
git reset --hard c7f6d16

现在看到上个版本 11.1.0.10161 对应的提交是 c7f6d16,回退到这个版本。

1
2
3
4
5
6
7
git reset --hard c7f6d16
grep "pkgver=" PKGBUILD # 查看版本号,版本号为 11.1.0.10161
grep "source_x86_64=" PKGBUILD # 查看下载网址,网址为 https://wdl1.cache.wps.cn/wps/download/ep/Linux2019/${pkgver##*.}/wps-office_${pkgver}_amd64.deb

# ARM 设备的应该用下面这条命令
# grep "source_aarch64" PKGBUILD
# ARM 版的网址为 https://wdl1.cache.wps.cn/wps/download/ep/Linux2019/${pkgver##*.}/wps-office_${pkgver}_arm64.deb

把下载网址(https://wdl1.cache.wps.cn/wps/download/ep/Linux2019/${pkgver##*.}/wps-office_${pkgver}_amd64.deb)的 ${pkgver##*.} 替换为版本号的最后一串数字(10161),把 ${pkgver} 替换为版本号(11.1.0.10161),最后获得的网址就是 WPS 11.1.0.10161 的下载网址:https://wdl1.cache.wps.cn/wps/download/ep/Linux2019/10161/wps-office_11.1.0.10161_amd64.deb。现在就可以下载安装,安装完后要删除配置文件才能运行。

1
2
3
4
5
6
# 下载
wget https://wdl1.cache.wps.cn/wps/download/ep/Linux2019/10161/wps-office_11.1.0.10161_amd64.deb
# 安装
sudo apt install ./wps-office_11.1.0.10161_amd64.deb
# 删除配置文件
rm -rI /home/hunter/.config/Kingsoft/

如果你想安装其他版本,不用重复我前面的步骤。在这里找你想要的版本,把版本号替换到我前面找到的网址就行了,这个网址一直没变过。要是不行就从头做起吧。

用笔记本电脑屏幕玩 Nintendo Switch

2021-08-05 00:00:00

缘起 #

我从去年末尾开始玩健身环大冒险,没毕业前在出租屋自己一个人玩。毕业后回家都是在客厅玩,要是有人在场我就不好意思了,于是有时候训练计划就被打断了。前些天和女朋友去广州把出租屋的行李都寄回家,出门带了NS(Nintendo Switch)和笔记本电脑。收拾完东西和女朋友玩马力欧赛车,已经打包好显示器了,就只能看着 NS 的屏幕玩。两个人都看得眼睛累死了,女朋友就问我能不能把 NS 接到笔记本电脑上玩。我说:「能,买个采集卡。便宜的也得四五百吧,感觉不便宜。」一般笔记本电脑的 HDMI 只能输出信号,不能输入信号,所以直接接上是接收不到 NS 画面的。

笔记本通常不能接收 HDMI

我以为采集卡都是四五百起步,去淘宝看,还是有便宜的啊,买了 87 块的绿巨人采集卡,型号是 LJN-CJQ002。正好可以解决玩健身环的问题。除了健身环外,分级比较高的游戏也不太适合在客厅玩,比如 AI 梦境档案。上次我在客厅玩的时候突然来了这样一句台词:

说胡话的伊丽丝

总感觉在客厅怪尴尬的,赶紧按下主页键退出。只要我按得快,就没人注意到。

采集卡的使用方法 #

  1. 官网下载并安装 OBS1 Studio
  2. 打开 OBS,进入自动配置
    1. 选择 Optimize just for recording
    2. Base(Canvas) Resolution = 电脑的分辨率
    3. FPS = 60
    4. Apply Settings
  3. 接线:Nintendo Switch(NS) -> 底座 -> HDMI 线 -> 采集卡 -> 电脑
  4. 给 NS 和电脑插电2
  5. 设置视频信号
    1. 点击 OBS 下方 Source 的 + 按钮
    2. Video Capture Device -> Create new -> OK
    3. Device = 带 USB 名称的
    4. Video Format = 先试试默认的,不行就换,我用的设备用不了 YUYV 格式,其他格式都行
    5. Resolution = 电脑分辨率
    6. Frame Rate = 60 FPS
    7. OK
    8. 在画面空白处右击,勾上 Enable Preview
  6. 设置音频信号
    1. 点击 OBS 下方「Audio Mixer -> Mic/Aux」右下角的齿轮按钮
    2. Advanced Audio Properties
    3. Mic/Aux -> Audio Monitoring -> Monitor Only (mute output)
    4. Close
    5. 如果这个办法不行,就在 Source 添加 Audio Input Capture,然后用上面的步骤设置
  7. 放大预览画面
    1. 右击预览画面
    2. 勾上「lock preview」
    3. 右击预览画面
    4. Preview Scaling -> Canvas
    5. 点击菜单栏的 View
    6. 取消所有勾上的选项,包括 Docks 里面的
    7. View -> Fullscreen Interface(F11),按下 F11 退出全屏
    8. 如果需要恢复默认界面,就点击 View -> Docks -> Reset UI,然后再勾上之前的选项

使用感受 #

感觉玩游戏的时候帧率只有三四十,达不到产品页说的 60 帧。玩马力欧赛车卡得影响发挥,玩健身环还行,梦境档案没问题,Youtube 能播放 1080P 60FPS 的视频。问客服:「帧率低怎么回事?和采集卡配置或笔记本配置有关系吗?」客服说:「没有配置要求,有独立显卡更流畅。」我的笔记本是 Magicbook 14(AMD Ryzen 5 3500U ,16G RAM),用女朋友的小新 Air 14(AMD Ryzen 5 4600U,16G RAM)感觉更流畅。要是不需要省钱、录屏、直播,还是买便携式显示器比较好。最后还有一些待解决的问题:


  1. OBS 的全称是 Open Broadcaster Software。 ↩︎

  2. 笔记本电脑不插电会变慢。 ↩︎

解锁 Twitter 帐号

2021-07-25 00:00:00

Twitter 也太坑了,我刚注册完,一登陆就锁定我帐号。联系客服解锁一个月后又锁定了。解锁要我验证手机号,可中国手机号也收不到验证码。罢了,反正我也不想绑定手机号。还是照旧发邮件让客服解锁。点击 Contact Us 里的 Locked and suspend account issues 就可以联系客服了,在 Description of problem 填入以下内容:

Dear Sir or Madam.

My account was locked, but I did not violate the Twitter Rules at all. I just tweeted several times and followed one account. I can not use China phone number to verify because it can not receive confirmation code. Please help me to unlock my account. This account was locked second time. Could you tell me why my account was locked twice.

Thank you in advance.

然后马上就收到自动回复说我违反了 Twitter 规则

We’re writing to let you know that your account has been flagged for unusual behavior that violates the Twitter Rules, and has been locked until you take the following steps…

我看了规则内容,我也只是正常使用,并未违反规则。此时要再申诉一次,还是填原来的内容。这次才收到回复说会继续审核申诉:

Thanks for your report. It looks like this is connected with your original case # 16409264056, so we’ve added it to that first report.

We’ll continue our review with this information.

我的申诉内容除去客套话,真正有效的部分应该是这些:

My account was locked, but I did not violate the Twitter Rules at all. I can not use China phone number to verify because it can not receive confirmation code. Please help me to unlock my account.

也许第二句也可以省略掉:

My account was locked, but I did not violate the Twitter Rules at all. Please help me to unlock my account.

如果你也遇到了一样的问题,可以试试发这些内容,可根据自己情况作改动。

被微信绑架

2021-07-09 00:00:00

大学基本上都是用微信处理学校的事务,比如收通知、联系老师。唯一的例外是收作业,基本上作业都是用电子邮件交,不过也有用微信的。在大学用微信办公 4 年了,我真的是气到吐血。微信本来是个聊天软件,微信办公,什么狗屁玩意。我必须要好好吐槽一番滥用微信的现象。

上大学加了超多群,这就是噩梦的开始。以下是我加的群:

一个通知可以从学院群出现,然后传到专业聊天群、专业通知群、班级聊天群、班级通知群。为什么同一条通知我要看好几次,我的时间很宝贵好吧。更恶心的是,微信不能修改发出的信息。微信交流成本太低了,动动手指就能发信息。微信发通知的人经常不好好写,发出了有错的通知,然后又发一次修正内容,这条内容又在好几个群跳动。所以每位同学都要看一次带错误的通知,又看一次修正内容。年轻人的大好青春,怎么就浪费在这了。搞个网站发通知多好啊,错了就修正,同学看到的永远是最新版,还能用 RSS 订阅。微信就用来提醒大家去看就行了。再不济发邮件也好啊。

本来聊天群和通知群就是各司其职,但是发通知的人有时候就在聊天群发通知,后面的人聊着聊着,通知就淹没了。有同学去问发通知的人一些问题,他就不耐烦,说之前发过了。100 条信息里面藏了 1 条通知,我根本不想看大家聊天好吧。虽然我很讨厌去聊天群检查通知,但是我已经被迫成了熟练的聊天群通知打捞员。不仅聊天群干兼职,通知群也干兼职。从大四开始,辅导员为了刷就业率,成天在通知群发招聘信息。从此通知群沦陷了,充斥着该死的招聘广告,恶心程度和电梯广告有得一比。我真的是宁愿去聊天群打捞通知。我现在巴不得时光倒退到没有手机电脑的时代,把通知贴到宿舍楼下得了。要是口口相传就更好了,交流成本高了人就会好好说话。

微信办公会模糊生活和工作的界限。本来我只是拿微信来聊天的,结果上大学加了一堆群和好友,真的不是说删就删啊。有时候我只想看朋友的信息,不想看学校的信息。但是打开微信就是全部信息都混杂在一起,即使人不在学校,打开微信一看,感觉自己还在上学。要是专门建一个工作号区分生活和工作就好多了,不过刚上大学那时完全想不到微信办公那么恶心。我现在已经注册了工作专用的微信号,下次不用受那么多苦了。

微信和部分微信用户还有很多恶心的地方:

即使没有微信办公那些屁事,我也不喜欢用微信这样即时通讯软件与人交流。微信交流前要先加好友,有些人我都不认识,为什么要一开始就建立一个关系呢?有些人只是和我谈一两件事,但还是得加好友才能开始谈,我感觉谈完了好像就没有再联系的必要。要是我不删除好友,我的好友列表就要不断膨胀了。我觉得删除好友很残忍,显得我很无情。要是有人给我发信息的时候发现被我删了,也不知道他怎么想我。有些人加我好友的时候也不说明一下,我都不知道是干嘛的,就更不想加了。我不喜欢一开始就建立关系,所以最近一两年几乎没加过新的好友。加我好友的也大多被我拒绝或者忽略了。

微信未读信息的小红点让我很焦虑,仿佛不赶紧回复不行。有些人发的信息不知所云,我压根不想回复。但是碍于好友这层关系,我还是回复了。最近半年好多了,可以习惯不回复了。我也不喜欢打一两句话就发出去这样聊天,一下子整件事完整说出来我看着比较舒服。一条一条地看真的很麻烦。

那什么交流方式比较适合我呢?电子邮件吧。电子邮件有邮箱地址就能发,不需要加好友。而且收到邮件不回复或者不马上回复心理也比较放松。收到邮件可以加标签或者放入不同文件夹,事后再处理也行。我身边的人都用微信交流,我用电子邮件估计会水土不服。现在已经加了微信的就不管了,下次有人找我要联系方式我就给邮箱。微信就只用来和亲人联系。其实我觉得 Telegram 比微信好多了,但身边人不用也是没办法。社交软件就是这样,用的人多了,即使它是垃圾还得用。很想告诉微信好友我改用电子邮件了,不知道会有什么反应。

现在毕业了,还没处理完档案派送的事情,还留着好多群。毕业后辅导员发信息都不往通知群发了,看信息看得我怒火中烧……


  1. 准确来说,Linux 官方版是 UOS 独占。其他 Linux 发行版一般用 Wine 运行 Windows 版。 ↩︎

小孩能玩游戏吗

2021-06-25 00:00:00

有个亲戚家的男孩让我印象很深刻。男孩现在五年级,放假的时候就宅在家打手机游戏。据我观察,应该是玩大逃杀类射击游戏,俗称吃鸡1。男孩玩的时候常说粗话,有时候是开语音骂的,一般都是骂别人玩得差。有时候也指挥别人,比如「XXX,把枪给我」。玩一个小时能说三四十分钟粗话,一个小时说的粗话比我一年都多,基本上流行的粗话都说了。我当时听到那么多粗话真的感觉很不舒服,真的是该管管啊。难道男孩家长不管吗?并非如此,男孩说粗话的时候家长也会制止,男孩就嬉皮笑脸的,似乎没认识到说粗话对别人的伤害。家长管教收效甚微,慢慢就懒得管了。

我从男孩家长那了解到男孩放假的时候基本都是一直打手机游戏,家长也尝试过没收手机不让玩,但是男孩又哭又闹,家长也是束手无策。我觉得家长已经从管教到放弃了。身为游戏玩家,我也挺理解男孩的处境的,竞技类游戏不是说停就停的。其实竞技类游戏就和体育比赛差不多,如果一个运动员在比赛中途被家长叫停,那这位运动员会输掉比赛,并且可能面临处罚。这位运动员肯定气坏了,打游戏也是这个道理。

我也很理解男孩为什么那么沉迷游戏。上学本来就很无聊,放假也没事做,总得找点事打发时间,游戏刚好是唾手可得的娱乐方式。我小学的时候都是盼望周五放学在家打小霸王。我有个朋友说:「父母不懂教育孩子是因为他们忘记自己是怎么长大的。」如果家长能回忆一下自己的读书经历,想一想背书刷题是不是很无聊,那应该就能理解为什么小孩要打游戏,真的是要放松一下啊。家长既不想了解游戏,也不想让男孩打游戏,就只能用没收手机这种没用的手段。

小孩玩游戏没问题,不过要玩适合的。玩游戏也有讲究,不是随便玩的。首先我推荐用家用游戏机玩游戏,目前主流的厂商是微软、索尼和任天堂,买这三家最新的主机准没错。我推荐游戏机是因为里面的游戏一般都有分级,买游戏的时候看上面标注的分级信息就知道合不合适小孩玩。游戏分级系统就是用于帮助消费者选择适合的内容。美国和日本都有专业的分级机构,有专业机构给游戏分级,家长挑选游戏也更简单。以下是 ESRB(Entertainment Software Rating Board;娱乐软件分级委员会)的分级制度。

标志 分级 描述
E所有人 其中内容适合6岁或以上的玩家。被分为此类的游戏通常包含少量的幻想或者适度的暴力,或者有轻度的不良语言。
E10+10岁以上 其中内容适合10岁以上的玩家。被定为此级别的游戏通常会包含具有卡通效果的暴力、或者最少限度的争议主题。
T青少年 其中包含适合13岁(英文Teen指13-19岁青年)以上的玩家的内容。此级别游戏会包含暴力、争议主题、粗鲁幽默、少量血腥。
M成熟 其中包含适合17岁以上玩家的内容。此类别游戏有强烈的暴力血腥粗口宗教的内容。
AO仅限成人 其中包含内容只适合18岁以上的成人。此类游戏通常有强烈的暴力倾向、明显的性和裸体场景。
RP有待评级 产品已经提交ESRB审定,正在等待分级结果。这个标志主要是在游戏正式发布前的宣传材料中使用。而在可能分级为T级或以上时,会加入一句"May contain content inappropriate for children."(可能含有儿童不宜内容)的免责声明。

我觉得男孩玩的大逃杀类游戏真的不太适合他的年龄。我不知道他具体玩的是哪款游戏,我就查了同类型的 PUBG Mobile。PUBG Mobile 的分级是 Teen,也就是 13 岁才能玩,而五年级的学生一般是 11 岁,这显然不适合男孩玩。其实我也想知道他玩的是啥,但我真的很讨厌别人说粗话,就没过去问,只好靠猜了。

从游戏内容上来说,我也觉得不太合适。大逃杀类游戏经典模式是这样的:把 100 个人丢到岛上相互厮杀,最后存活的队伍或人胜利。100 个人里面赢家就只有 1 个人或队伍,剩下的都是输家。这是很残酷的游戏,要是一直输肯定充满挫败感。我看男孩常常骂人,估计是没赢多少局。我觉得玩游戏就是为了开心,玩单机游戏就挺好的。单机游戏关卡难度一般都是固定的,玩家只要技术进步了,就能过关,这样也会有持续的快乐。而竞技类的游戏是和人对抗的,玩家技术进步的时候可能匹配到的对手也强了,所以总是有输有赢。竞技类游戏真的不适合每一个人,菜鸟体验就差。

以上只是我作为游戏玩家的一些思考,「小孩怎么玩游戏」这个问题肯定不是那么简单。中国一直有污名化游戏的现象,2000 年光明日报称电脑游戏为「电子海洛因」,同年国务院发布游戏机禁令,直到 2014 年上海自贸区细则发布后才解禁游戏机销售。2006 年甚至出现了惨无人道的网瘾电击疗法。需求决定供给,只要还有家长有驯服孩子的需求,那这些粗暴的行为矫正机构仍然会横行霸道。

看到那么多污名化游戏的现象,我真的特别难过,孩子打游戏有什么错呢?很希望有人能出来为游戏正名。后来我真的读到了一本为游戏正名的书,叫做《边游戏,边成长》。这是一本讲述如何带小孩玩游戏的书,作者是心理学者叶壮。作者不仅支持自家小孩打游戏,还列举了打游戏的好处,我读的时候真的特别感动。以前我看论证打游戏合理性的文章说游戏只是一项娱乐活动,没什么特别的。《边游戏,边成长》就高超多了,它说打游戏的好处。

比如经常玩动作游戏的人,眼睛更容易注意到一些细节,这在审题、阅读论文以及看药瓶上的小字时,都十分有用。

除了注意力之外,游戏玩家的多任务处理能力也不错,这可以帮助他们在日常生活中更好地应对那些不得不“三心二意”的场景,以及在学校与职场中更好地同时兼顾多个不同的任务。

好玩的游戏一定是能激活玩家情绪、情感的游戏,而优质的游戏也会通过激发孩子的情绪感受,帮助孩子更好地收获多样化的情绪体验,甚至排解压力。

哈佛大学主要的教学医院贝斯以色列女执事医疗中心发布的一项研究发现,如果腹腔镜外科医生每周能玩超过三个小时的电子游戏,在手术中的错误就会比他们那些不玩游戏的同事少37%。

一个打游戏认真的孩子,起码具备了“认真对抗压力”的能力,他只是有可能不愿意把这种能力用在学习上罢了。但是我们必须要承认,这种能力是一种很重要的优质技能,并且不少游戏对培养这样的能力有着显而易见的好处。

——《边游戏,边成长》

我前面写那么多,真的希望消除大家对游戏的偏见,希望不要有孩子被改造了。在现实中遇到男孩这样的「网瘾少年」,我心情就很复杂,我很同情他被家长阻止打游戏。我想了解他玩的是什么游戏,但听到粗话就不想靠近。我也想帮帮他,但又觉得很无力。如果要帮他,肯定要改变他家长对游戏的误解,这已经超过我能力范围了,人的观念真的是很难改变啊。我想带他玩既适合他又好玩的游戏,但会不会玩了之后他就缠着找我打游戏,我也没那么多时间陪他玩。会不会玩了之后他让家长也买台游戏机,那我不是加剧了矛盾?这事真的挺复杂的,心里真是五味杂陈。

参考资料 #


  1. 在绝地求生游戏获得第一名后屏幕会显示「大吉大利,晚上吃鸡」,所以绝地求生亦被成为「吃鸡」。 ↩︎

密码管理器

2021-06-24 00:00:00

网上冲浪十余载,帐号越来越多。真的很难记得住每个网站的帐号密码,把帐号密码直接存在笔记软件也不安全。是时候用密码管理器了。我的需求是:跨平台(Linux、Windows、Android、浏览器)、便宜。上网搜寻后发现比较多人推荐 1Password 和 LastPass,它们都是每月3 美元。LastPass 有免费套餐,但只支持一类设备,电脑或者移动设备。所以用 LastPass 还是要付费才行。

最后选中 Bitwarden,它是跨平台开源软件,而且还免费。付费会员也比较便宜,每年 10 美元。现在终于不用记那么多密码了,大脑都轻松了。

注意事项:Bitwarden 的密码一定要记好,忘记了是没法重置1的,只能根据填写的密码提示回忆。实在是想不起来可以通过删除帐号来清空数据,然后再重新注册。


  1. 其他密码管理器的密码也无法重置,这算是密码管理器的特点了。 ↩︎

红米 AC2100 刷入 OpenWrt 固件

2021-06-19 00:00:00

相关文章:红米 AC2100 刷 breed 后刷回官方固件

红米 AC2100 刷机的主要步骤:获取 SSH 访问权限、刷入 bootloader(Breed)、刷入固件。

准备工具 #

配置网络 #

  1. AC 2100 WAN 口连接调制解调器或者原路由器 LAN 口,或者通过无线中继的方式联网
  2. AC2100 的 LAN 口连接电脑,全程都要保持网线连接
  3. 访问 192.168.31.1,配置网络,保证 AC2100 可以联网

降级 #

  1. 下载 2.0.7 固件备用链接),接下来就利用这个固件的漏洞来刷机
  2. 在浏览器访问 192.168.31.1->常用设置->系统状态->手动升级->加载固件,保留数据->开始升级

获取 SSH 访问权限 #

  1. 访问 http://192.168.31.1/cgi-bin/luci/;stok=<STOK>/web/home#router,输入密码登陆,登陆后记下网址里面 <STOK> 位置那串字符,例如 c8630022ae04sjf92af0ejd83e0330f1
  2. 把刚刚获取的 <STOK> 填入 http://192.168.31.1/cgi-bin/luci/;stok=<STOK>/api/misystem/set_config_iotdev?bssid=Xiaomi&user_id=longdike&ssid=-h%3B%20nvram%20set%20ssh_en%3D1%3B%20nvram%20commit%3B%20sed%20-i%20's%2Fchannel%3D.*%2Fchannel%3D%5C%22debug%5C%22%2Fg'%20%2Fetc%2Finit.d%2Fdropbear%3B%20%2Fetc%2Finit.d%2Fdropbear%20start%3B,然后访问,网页会显示 {"code":0}

修改 root 账户密码 #

  1. 访问 http://192.168.31.1/cgi-bin/luci/;stok=<STOK>/web/home#router,记下 <STOK>
  2. <STOK> 填入 http://192.168.31.1/cgi-bin/luci/;stok=<STOK>/api/misystem/set_config_iotdev?bssid=Xiaomi&user_id=longdike&ssid=-h%3B%20echo%20-e%20'admin%5Cnadmin'%20%7C%20passwd%20root%3B,访问,此时密码设置为 admin
  3. 如果要设置其他密码就重复前两步,把网址的 admin 改为你想设置的密码,记下密码

刷入 Breed #

Breed 是由 HackPascal 开发的 bootloader,有了它之后 AC2100 刷成砖也能再刷机。

  1. 打开终端程序,例如:Windows PowerShell

  2. 依次执行以下代码,一次一行,# 开头的不需要执行

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    # 登陆 root 账户,输入 root 账户密码
    ssh [email protected]
    # 下载 Breed
    curl https://breed.hackpascal.net/breed-mt7621-xiaomi-r3g.bin --output breed-mt7621-xiaomi-r3g.bin
    # 备用链接
    # curl https://cdn.jsdelivr.net/gh/CyrusYip/blog-static/other/breed-mt7621-xiaomi-r3g.bin --output breed-mt7621-xiaomi-r3g.bin
    # 刷入 Breed
    mtd write /tmp/breed-mt7621-xiaomi-r3g.bin Bootloader
    # 重启
    reboot
    
  3. 等待重启,只有蓝灯常亮代表重启完毕

刷入临时固件 #

  1. 下载临时固件
  2. 拔下 AC2100 的电源,用牙签按住 Reset 键,插电,蓝色闪烁时松开
  3. 访问 192.168.1.1,增加环境变量,字段为 xiaomi.r3g.bootfw,值为 2,保存。不做这一步 AC2100 就无法启动了。
  4. 备份固件(可跳过)
  5. 固件更新->固件->选择文件->选刚刚下载的临时固件->勾选自动重启->上传->更新

刷入 OpenWrt #

  1. 下载 OpenWrt 固件

    恩山论坛有很多 OpenWrt 固件选择一款合适自己的就行。我用的是这个,下载地址在这里,下载文件名含有 sysupgrade 的文件。自己编译也行。

  2. 重启完后访问 192.168.1.1,如果被占用了就拔掉 WAN 口的网线

  3. System -> Backup / Flash Firmware -> Flash image -> 取消勾选 Keep settings and retain the current configuration -> continue

  4. 访问 10.0.0.1,配置 AC2100

后记 #

第一次给路由器刷机,一搞就是几个小时,天都亮了,太艰难了。一开始我是连着 WIFI 弄的,结果一直不行,后来才发现要接网线。网上有各种方法,有些方法要安装软件,看着就头疼啊。我觉得现在我这个方法还算简单,只需要终端和浏览器,这两个软件每台电脑都有。

看了那么多教程,我觉得 OpenWrt 网站的资料写的最清晰易懂。可以先从这里入手,不行再看其他教程。不过我没测试过这里面的所有内容,我也不知道是不是都有效。另外,这个帖子提到了 AC2100 有内存坏块可能导致无法刷机。

参考资料 #


  1. 1 条网线从 LAN 口连接到电脑,另 1 条从 WAN 口连接到调制解调器(猫)。如果本来就有路由器的话,可以只用 1 条网线从 LAN 口连接到电脑,然后用无线中继的方式连接原有的 WI-FI。 ↩︎

牙的回忆

2021-06-09 00:00:00

第一次蛀牙 #

不记得小学几年级开始有的蛀牙,去看牙医的时候大概五六年级。下排的牙齿左右两边各有一个蛀牙,超过一半都烂了。医生还发现我右上方有个突出的牙齿,说我初中的时候可以去箍牙(矫正牙齿/口腔正畸),这样牙齿会比较好看。

嗑瓜子的鬼 #

小学(或是初中)住宿的时候,在一个夜深人静的晚上,舍友听见了有人在厕所嗑瓜子。嗑瓜子的声音间隔特别规律,应该是两三秒发出一次声音。舍友走到厕所却发现没人,但是嗑瓜子的声音还在持续……

谜底写在文章后面了,你可以先猜猜故事的真相。

学不会的音标 #

初中的时候自学编程,源代码的有不少不懂的英文单词,于是打算先学英语。我用的书是《李阳国际音标与美国音标入门》。自学音标的时候,/ð/ 和 /θ/ 这两个音标总是学不会,怀疑是自己的牙齿位置不对导致无法发这两个音。于是就去矫正牙齿了。一般人矫正牙齿都是为了好看,我是为了学习,真的是朵奇葩。

矫正牙齿之后我还没放弃学音标。当时音标吧有位老哥发帖说免费教音标,我就加了他的 QQ,老哥的昵称是 Reborn。Reborn 哥推荐我学赖世雄的《美语音标》1,赖世雄水平真是高,听了他的讲解我就学会了。

但是矫正的牙套都戴好了,没法回头了,直接拆掉的话牙齿就会处于未矫正完成的状态,比矫正前还糟糕。没办法,矫都矫了,只能继续。戴牙套的第一个星期特别煎熬,咬有点硬的食物整排牙齿都痛,唯一吃了不痛的食物就是粥了。吃东西也危险,有次吃无穷鸡腿的时候把牙套的线扯出来了,然后就要去医院请医生弄好。从那以后,无穷鸡腿就进了零食黑名单。

矫正好后拆掉牙套,我妈发现我的牙有点歪,然后去质问医生。医生说少拔了一颗牙,问退钱还是重新弄。我去市人民医院问了牙医,医生也是说要拔一颗才行。我懒得换医院了,又回到原来的医院弄了。初三开始弄,好像大一才弄好,还得戴半年保持器。这么说也过了好几年呢,回想起来感觉真不容易,要是一开始知道那么麻烦估计就不想做了。

韩国年糕也很危险 #

大四上学期一门课的期末论文上交时间本来是下个学期开学前,但是老师突然把期末作业的上交时间提前到五天内。当时我就一边在心里痛骂老师,一边拼凑论文,也没时间做饭了。女朋友帮我点了外卖,无骨炸鸡和韩国年糕。吃着吃着的时候,我嘴里吐出了一块白色的骨头。嗯……无骨炸鸡怎么有骨头呢,而且还是白色的,鸡骨头不是这个颜色啊。嗯……我舌头有种奇怪的感觉,好像右下方补过的牙缺了一块。啊啊啊啊啊!白色骨头是我的牙,居然被年糕粘掉了,补牙材料真的不行啊。突然要赶论文就很火大了,还遇上这样的糟心事,于是又痛骂了老师。这个老师平时对学生太差了,骂一百遍都嫌少。

又有几个蛀牙 #

去医院补被年糕粘掉的牙,叫医生顺便检查其他牙齿有没有问题。医生发现我还有五六个蛀牙。天哪,我一直以为我只有两个蛀牙,居然又多了几个,真是要命。第一次只补了一颗。第二次去的时候补了几颗,电钻碰到牙齿里的肉,痛到流泪了。在补牙过程中,我无数次悔恨自己没有好好爱护牙齿。补一颗牙四五百,学生医保还不报销治疗费,都是自费,心痛死了。补完牙后医生建议我用巴氏刷牙法和电动牙刷。上丁香医生看巴氏刷牙法才知道我的刷牙方法一直是错的。后来买了电动牙刷,刷牙真的比手动的方便,还更干净了。顿时感觉白刷了 20 年的牙。电动牙刷真是伟大的发明。

感想 #

平时一定要正确刷牙,有蛀牙一定要补啊。到不能补牙的时候很悲惨的,只能镶牙或者种牙了。镶牙会磨损旁边两颗牙齿,种牙就把假牙钉到骨头里。想想都觉得可怕,而且还贵,种牙是几千上万一颗。幸亏我补了牙,没走到这个地步。看来还是要定期检查牙齿才行啊,预防是最好的治疗。

补牙
镶牙
种牙

谜底 #

鬼故事发生的时候我睡着了,我第二天才听舍友说这件事。起床的时候我发现一个补过的牙缺了一小块,而且缺的那小块不知道哪去了。故事应该是这样的:我就是「嗑瓜子的鬼」,晚上磨牙还把牙磨掉了一小块,那小块应该吃进肚子了吧。当时觉得磨掉牙齿这件事太丢脸了,就没告诉舍友,也不知道他们后来晚上会不会害怕。


  1. 我按照《把你的英语用起来!》自学英语,这本书也推荐了《美语音标》。我也不太清楚我一开始是从哪里知道这本音标教程。我不记得具体和 Reborn 聊过什么,总之就是对他有很深的感激之情,那他应该帮到我了。所以是先从他那里知道的。啊,人的记忆也不是那么好使,真的是不写下来就慢慢遗忘啊。 ↩︎

Git 笔记

2021-06-07 00:00:00

术语 #

配置 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 名字和邮箱必须设置,其他按需要配置就好
git config --global user.name "your-name"
git config --global user.email "your-email"

# 设置远程分支后才能用 `git push`
git config --global push.default simple

# 显示中文路径
git config --global core.quotepath false

# 设置编辑器
git config --global core.editor "nano"

# 设置换行符为 LF
git config --global core.autocrlf input

# 设置好看的 git log
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
git config --global alias.lgs "log --pretty=oneline --graph --abbrev-commit"

.gitignore #

.gitignore 用于忽略不需要的文件,例如:

1
2
3
4
node_modules
.DS_Store
.idea
.vscode

命令别名(alias) #

配置命令别名后操作更简单,Oh My Zsh 的 git 插件就自动配置了别名。如果不用 zsh,手动添加别名到 ~/.bashrc 也行。

1
2
3
4
5
6
alias ga='git add'
alias gc='git commit -v'
alias gl='git pull'
alias gp='git push'
alias gco='git checkout'
alias gst='git status'

配置 SSH 密钥 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 生成 SSH 密钥(ed25519 比 RSA 安全)
ssh-keygen -t ed25519 -C "注释"

# 查看密钥
cat ~/.ssh/id_ed25519.pub

# 复制代码
xclip -selection clipboard < ~/.ssh/id_ed25519.pub

# 添加密钥到 GitHub
xdg-open https://github.com/settings/ssh/new

# 接收 GitHub 公钥
ssh -T [email protected]

GitHub 文档

创建仓库 #

1
2
3
4
5
# 初始化当前目录
git init

# 初始化文件夹
git init directory-name

分支 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 查看分支
git branch

# 创建分支
git branch branch-name

# 切换分支
git switch branch-name
git checkout branch-name

# 创建并切换分支
git switch -c branch-name # -c --create
git checkout -b branch-name # -b(branch)

# 删除分支
git branch -d branch-name

# 合并分支
git checkout main # 切换到接受代码的分支
git merge patch # 把 patch 分支的代码合并到 main

远程仓库 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 克隆仓库
git clone repo-link
git clone repo-link local-repo-name # 克隆并重命名
git clone repo-link . # 克隆到当前文件夹(不推荐)
gh repo clone username/repo-name

# 添加远程仓库
git remote add remote-name repo-link

# 查看远程仓库
git remote -v

# 删除远程仓库
git remote remove remote-name

# 推送代码
git push remote-name local-branch:remote-branch
git push -u remote-name branch-name # 设置上游
git push remote-name branch-name
git push

# 拉取代码
git pull remote-name branch-name
git pull

查看状态 #

1
2
3
4
5
# 详细内容
git status

# 精简内容
git status -sb 

把文件加到暂存区 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 添加文件
git add file-name
git add file-1 file-2

# 添加目录
git add directory-name

# 添加所有文件
git add .
git add *
git add --all

恢复暂存区的文件 #

1
2
3
4
5
# 恢复所有
git reset -- .

# 恢复某个文件或目录
git reset -- file-name

隐藏未提交的内容 #

1
2
3
4
5
6
7
8
# 隐藏暂存区
git stash

# 查看隐藏内容
git stash list

# 恢复内容
git stash pop

清空暂存区 #

1
git clean -di

删除文件 #

以下方法删除的内容仍然可以被恢复。如果要完全删除,请参考完全删除 Git 仓库的文件

1
2
3
4
5
6
# 方法 1(推荐)
git rm file-name

# 方法 2
rm file-name
git add file-name

提交代码 #

1
2
git commit -v # 在提交说明底部显示 diff
git commit -m "提交说明" # 不推荐

查看历史 #

1
2
3
4
5
6
7
# 查看历史
git log
git log --pretty=oneline --graph --abbrev-commit
git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit

# 查看所有历史(git reset 之前的都有)
git reflog # reflog 只在本地,不会上传到远程仓库

版本回退 #

1
2
3
# --hard 会删除暂存区的文件
git reset --hard commit-id
git reset --hard HEAD

解决冲突 #

合并提交 #

1
2
3
4
5
6
7
8
9
# 合并提交
git rebase -i HEAD~3
git rebase -i commit-id

# 取消 rebase
git rebase --abort

# 继续 rebase
git rebase --continue

困难的恋爱

2021-06-04 00:00:00

困难的恋爱 #

毕业以后恋爱就变困难了。之前学校天天见,现在就开始异地恋了,非常不习惯。女朋友目前做教师,我打算做程序员,目前在家里学前端开发。我很粘女朋友,异地恋太煎熬了,于是就在思考怎么结束异地恋。

做程序员得去大城市找工作,女朋友目前在小城市工作。我上 BOSS 直聘搜了下,小城市压根就没有前端开发的工作。看这样子还是得异地恋。现在只有两个方案:女朋友迁就我去大城市工作、我迁就女朋友去小城市工作。

正如袁凡所说

但也许你进入社会后会面对一个残酷的现实,那就是找工作这件事上高学历好学校是一块好的敲门砖。

女朋友迁就我不太可行,去大城市做教师也得研究生起步了。本科学历真的是不够用,女朋友也没有读研的想法。走这条路实在是牺牲太大了。

我迁就女朋友也不太行啊,小城市真的没前端开发的工作。最后我想到一个折中方案:我去大城市工作几年,积累技术和经验,然后找份远程工作,搬过去女朋友那边。问了一个程序员朋友在国内远程工作可不可行。朋友说:「可以,但是只懂前端不够用,要成为成为全栈工程师才行。自己能独立完成任务才行,不然可能需要即时的交流,这就得在公司上班了。」嗯,这个方案可以。也有想过自由职业,但觉得一开始就这样不太容易,真的不知道要干什么,还是先去打工赚钱养活自己吧。现在就继续努力学习吧,加油!

人生的意义 #

上大学之前读书都有别人设定好的目标,比如中考和高考。只要按部就班的学习和考试,慢慢就过去了,也不会纠结人生意义这种问题。上大学之后就更自由了,没有什么固定的目标,考试六十分万岁,不用像高考那样争个你死我活。一下子失去目标,整个人都迷茫了,感觉生活没什么意义。和心理咨询师谈了很多次迷茫的问题,最后也没找到我的目标。

现在和女朋友分隔两地,我真的特别珍惜这段恋情,所以我就有了个人生目标:结束异地恋。现在学编程也有累、无聊、不想学的时候,但是想着要结束异地恋,就会休息过后再继续学习。以前总觉得人生没什么意义,现在好像突然就有意义了,能和女朋友在一起就够了。感谢女朋友一直的陪伴、照顾和支持。遇到她之前,我的生活黯淡无光。相恋后,我的生活已是五彩缤纷,充满希望。

不要注册 cn 域名

2021-05-25 00:00:00

去年想学习搭建网站,当时啥也不懂,以为建站一定要有服务器、域名和备案。于是在腾讯云租了一年服务器,付费的时候看到 cn 域名才 17 块,就顺便买了一个。备案过程十分曲折,要填很多个人信息。备案后那段时间没空学建站,然后因为一周1内没有解析网站,备案就自动注销了。真的是气死,辛辛苦苦备案完,还给我注销了。最近又看了些 cn 域名的资料,发现有挺多坑人的地方。

cn 域名没有隐私保护(whois protection)。国内域名注册商说的隐私保护根本是自欺欺人,在它们隐私保护就是在自己的查询服务隐藏注册人信息。但在别的地方是能查到的,在中国互联网络信息中心2可以查到每个 cn 域名注册人的姓名和邮箱。

注册 cn 域名必须使用真实身份信息。要是你想用假信息注册来保护隐私,那就太天真了。审核的时候不会通过的。真的不想用自己的信息注册的话,要么以公司名义注册,但公司的法定代表人还是能查到的。要么叫别人注册域名之后给你用,可谁愿意冒这种风险呢。

cn 域名无法删除。如果你觉得 cn 域名暴露了你的隐私,那对不起,这是不能注销的3。你只能修改邮箱地址,然后等到它过期。或者转让给别人,不过感觉把别人推到火坑里不太好啊。

cn 域名有被停用的风险。2008 年,有人以跳水奥运冠军吴敏霞拼音注册了 wuminxia.cn,结果被中国互联网络信息中心(CNNIC)回收了域名,并转交给国家体育总局。此域名在 2021 年 2 月 28 日被优视科技注册,呵呵。2009 年,牛博网被域名注册商万网停止解析。

其他 CNNIC 的臭事我就不讲了,感兴趣就去看维基百科吧。


  1. 其实我不记得具体时间是不是一周,大概是这样吧。 ↩︎

  2. GodaddyWhois.com 和命令行程序 whois 也可以查询。 ↩︎

  3. 您好,域名属于即时产品。域名注册成功后不能删除,也是无法进行注销的。若您不需要此域名,您可以等到域名到期不续费即可。——腾讯云 ↩︎

把域名转移到便宜的域名注册商

2021-05-19 00:00:00

2023-05-02 总结:最便宜的域名注册商是 Cloudflare,但它有些缺点:不能改域名服务器、WHOIS 信息会显示注册人地址的国家与省份/州,不过其余信息会被隐藏。不喜欢 Cloudflare 的话可以选择 Porkbun。注册或转移域名 60 天后才能转移域名,这是 ICANN1(互联网名称与数字地址分配机构)的规定。请选择 ICANN 认证注册商(ICANN Accredited Registrars)。要是去买三手四手的域名,出了问题 ICANN 都保不了你。


最近发现了两个域名价格对比网站:TLD Listdomcomp,在上面搜了一下我的域名 cyrusyip.org,发现续费最便宜的是 Sav(10.15 美元) 和 Porkbun(10.72)。我用的 Namecheap 续费要 15.16,相比之下 namecheap 真的不太 cheap。虽然网上可以搜到 20% 优惠券,用了之后(12.12)还是比较贵。而且每次都要找优惠券,有点麻烦。由于Sav 的评价不太好,我就决定转移到 Porkbun。转移费是 10.72,转移后域名使用期限会加 1 年。所以其实转移是免费的,转移费拿去续费了。

转移步骤:

点击确定之后过一两分钟就好了,也保留了原来的 DNS 记录,所以网站在转移中不会被关闭。以后每年都可以省 28 块人民币了,非常好!要是有优惠券就更省钱啦,不过好像没有续费优惠券。都那么便宜了,我还想用优惠券,实在是太贪心了。Porkbun 的界面很简洁,还用了粉色,我太喜欢了!

又及,我发现最便宜的域名注册商可能是 Cloudflare。它的收费等于成本价。

Cloudflare Registrar will only ever charge you what we pay to the registry for your domain.

Cloudflare 的续费价格是 10.11,比 Porkbun 便宜了 0.61。

2021-08-28 更新:今天把域名迁移到 Cloudflare 了。步骤如下:

  1. Cloudflare 主页点击 Add site,根据提示添加域名
  2. Cloudflare 主页点击 Registrar -> Transfer,按提示操作

操作成功后扣了 10.11 美元,转到 Cloudflare 后就不能更改域名服务器了,除非把账户升级为 Business(200 美元/月)。域名转到 Cloudflare 之后域名 WHOIS 信息会显示国家和省份/州。原来低价域名是推广 Cloudflare 的手段。本站目前用 Vercel 部署,Vercel 和 Cloudflare 都提供 CDN,这岂不是套了两层 CDN。果然 Vercel 文档说 Cloudflare 会稍微降低网站性能:

We don’t recommend that you enable the Cloudflare proxy unless you have specific constraints for your project since it will introduce a minor performance penalty to your website due to the additional hop.

按照说明把 Proxy status 改为了 DNS only,然后把 Quick Start Guide 那些功能都关掉。因为博客的 RSS 用太多 Vercel 流量,我又用 Cloudflare 缓存了


  1. ICANN /ˈaɪkæn/,全称 Internet Corporation for Assigned Names and Numbers(互联网名称与数字地址分配机构),是负责分配域名和 IP 地址的非营利组织。 ↩︎

为什么要接纳自己

2021-05-14 00:00:00

接受自己好的一面 #

前段时间通过了论文答辩,过程十分顺利,回答得也不错。结束之后答辩老师也没有说论文有问题。那天我还遇到了几位同学,她们的论文都被指出有问题。再过几天学院公布了第二次答辩名单,我们专业有 20% 的人要去二辩。

和其他同学对比的话,我应该算挺好的,感觉很开心。好像这是第一次为超过别人而感到自豪。本来想发朋友圈表达喜悦,想想还是算了,微信有那么多同学,怕被他们说我炫耀。

以前我渴望认同,而发朋友圈就是一个轻松获得认同的方式。有人点赞我会很开心,没有就很失落,甚至觉得尴尬。我发现发照片或包含强烈情绪的动态会有更多人点赞,但是那些长长的生活感悟就没什么点赞。为了点赞我甚至还压抑表达内心所想,越来越少发感悟,就只发那些觉得会有人点赞的内容。

现在我不想被朋友圈点赞裹挟,也不想否认自豪的感觉,所以还是在内心为自己高兴就好。人无论处于什么境地,都有思想的自由。

我前段时间开始在 Stack Overflow 答题,我回答的第一道题有 3 个赞,感觉很开心。我甚至会为了点赞去回答问题

前面我说了不想被朋友圈点赞裹挟,现在又说很享受 Stack Overflow 的点赞,这是自相矛盾吗?实则不然。朋友圈点赞和 Stack Overflow 点赞的意义不一样。朋友圈的点赞代表喜欢,而 Stack Overflow 的点赞(upvote)代表答案有用(helpful)。朋友圈有很多赞,代表大家很喜欢我发的动态。Stack Overflow 有很多赞,代表我的答案帮到很多人,是对我知识和经验的认可。

Stack Overflow 的赞会促使我去学习和输出知识,而朋友圈的点赞会促使我去讨好别人。学习对我来说是有益的,讨好别人是有害的,所以我不愿意被朋友圈裹挟,但很愿意被 Stack Overflow「裹挟」。可见点赞并不总是邪恶的,只要点赞对我来说有益,我就愿意受它的影响。

接受自己不好的一面 #

在 Stack Overflow 答了几道题之后就没什么可以回答的了。然后我就去 Stack Exchange 的英语学习版块(English Language Learners)看了看。我想:英语可是我的专业啊,赶紧来答几道题。看了一轮,发现这些题目还是有些难度,最后还是找到了一道比较简单的:「sit 和 my lap 之间用 on 还是 in」。我写了个答案分析 on 和 in 的差别。万万没想到有人点了个反对,还有人说用逻辑分析习语(idiom)是不对的

看到反对和评论的第一反应是十分羞耻,像是当着全班的面答错问题。不过这个的范围比一个班级大多了,全世界都能看到我的回答。真担心我的读者发现我身为一个英语专业生还回答得那么糟糕,简直想把这个答案删了。其实也不用那么担心,毕竟我的没什么名气,只有三个常驻读者。

我还是忍着羞耻感仔细地看了那条评论,确实说得有道理。英语语法有规律,但也有例外,尤其是习语,这些例外只能是死记硬背。还有个答案的角度是对比 sit in lap 和 sit on lap 的使用频率,最后发现 on 用得更多。我觉得这个角度就比我那个好多了。

最后还是没删答案,写博客的人哪能那么小气呢!把答案留下来是因为

  1. 我觉得我的答案还是有些道理。
  2. 删了答案那条颇有道理的评论会消失。
  3. 提醒自己下次好好写答案。
  4. 强迫自己接受不如他人的感觉。求知是从承认自己无知开始的,哪个大师不曾是菜鸟呢?习惯这种感觉有很大好处,别人的反对意见可能是看待问题的另一个角度。

接纳自己 #

之前看心理学科普书说要接纳自己,曾经这对我来说是件很费解的事,为什么要接纳自己呢?现在我能接受自己好的一面,也能接受自己不好的一面,似乎想明白了为什么要接纳自己。接纳自己就是为了更好地做自己,而不是成为他人期待的样子,也不是成为他人。成为他人既痛苦,也不可能,还是做自己比较舒服。

念头不等于现实

2021-05-07 00:00:00

颓废 #

大学最后一个学期没有课,唯一的任务就是写毕业论文和答辩1。有好几个月的时间做,时间很充分,但是我的焦虑感很严重。有时候会被焦虑压垮,要通过吃油炸食品和熬夜看动画来释放。本来前段时间是在坚持锻炼的,但是压力大了就停止了。最痛苦的是不能从论文中脱离。

写到不想写的时候,身体会很自然地停下来,切换到浏览器看看视频和文章。放松一下也没什么不好的,可我就是强迫自己继续写。看视频的时候就不能好好投入,责备自己没有好好写论文。所以我会看一段时间就回去写一点,写了又不想继续,然后去看视频。看了视频就自责,然后回去写一点。写论文又焦虑,于是去看视频,看了视频又自责,自责了就通过看视频来逃避这种感受。就这样不断循环。所以这几个月常常熬夜。想当年,高考前我都不会失眠,睡得可香了。在大学我也是摸鱼能手,居然就栽在论文上了。

焦虑 #

这个状态挺糟糕的,既没有好好写论文,也没有好好放松。除了浪费时间,内心也十分痛苦。一是对论文进度的焦虑,二是对浪费时间的自责。

对论文的焦虑是对未知的焦虑。第一次写毕业论文,很多东西都不了解。不知道要多久才能写完,也不知道写得合不合格。当我不了解一件事情的时候,我往往会想得比较糟糕。我会把论文当成很难的事情。对答辩的焦虑亦是如此。

现实 #

其实写论文和答辩的难度远比我想象的低,我的论文上交之后通过了。答辩也顺利通过,答辩老师连修改意见都没有。我们专业有 20% 的同学没通过第一次答辩,看来我做得还不错。

审视 #

当我脑海浮现论文和答辩很难很可怕的念头时,我一下就相信了。于是就开始焦虑,陷入颓废的状态。实际上,每天写花三四个小时的话,一个月就能搞定了,但是我焦虑了好几个月。念头可能是事实,也可能不是。可人常常把自己的念头当作现实,从而陷入不必要的焦虑中。啊,人类是多么不理智。

有两个分辨念头和现实的方法。一是觉察脑中浮现的念头,不要第一时间去相信,可以把它放到一边慢慢审视。觉察能力可以通过修禅/冥想来提升。二是直接去做那些感觉可怕的事情,通过实际状况来修正自己的念头。


  1. 答辩就是和几位老师介绍一下自己的论文,然后再回答老师的问题。 ↩︎

缅怀我的长发

2021-05-06 00:00:00

缘起 #

高三那年我诊断出强迫症和焦虑症,症状是一学习就咳嗽1得很厉害。于是没去上学,平时就在学校附近的出租屋自己复习。有天刷知乎看到讲男生留头发的帖子,我看照片还挺帅气啊。干脆我也试试看吧,反正也不用去上课了,不用在意别人的目光。我留长发有三个原因:感觉好看、懒得打理头发、懒得去理发店。上网买了个帽子,就这样开始留头发了。

留头发有三个阶段:能打理、不能打理、成功。

歧视 #

打理头发是留头发过程中最简单的事情了,可怕的是被歧视。例如:

其实我遇到这些歧视都超级伤心,真没想到周围有人对男生留长发有那么大偏见。我觉得随便批评别人很不礼貌,只要别人没损害他人利益,那就没什么好批评的。但世上就是有人看到不符合自己观念的事情就批判一番,真是没办法。旁人的评价感觉还行吧,最寒心的就是父母也歧视我。唉,毕竟做父母不用考试。我并不期待他们能支持,但还是希望别歧视吧。

剃发 #

上大学前听闻军训要剪寸头,就去理发店把鬓发剪了,扎了发髻。本来我打算留全部头发,但是就快要剪寸头了,就去把鬓发剪了,这样把头发扎起来看着还不错。

军训开始前去理发店剪寸头,心里居然很平静。可能被歧视得多了,也不差被迫剪掉了。现在想起来还有点遗憾,不是遗憾长发没了,是遗憾没把长发捐献给癌症患者。

军训完之后感觉自己短碎发看着也不错,还不需要用发蜡打理头发,然后就一直留这个发型。以前没试过那么短的发型,没想到这个发型挺适合自己的,这算是因祸得福吧。

偶尔想再留长发,但是女友强烈反对,她觉得我的长发很难看。真是一物降一物,没想到最后栽在女友手里了。现在,我只能看着身份证上的照片,缅怀我的长发。大家有办法说服我女友吗?救救想留长发的我吧。

影响 #

我留长发还是有些影响力。有个高中同学2觉得这样很酷,上大学他也留了长发。村里有个小孩不想剪头发,就和他爸爸说你看某某家的哥哥,人家留长头发就不用剪。


  1. 谈恋爱后咳嗽就好了。 ↩︎

  2. 这位同学有时候打扮风格异于常人。有同学说他很捞(「捞」在粤语是俗气的意思),他总会说:「捞到尽头自然潮。」 ↩︎

关于心理咨询的疑问

2021-04-30 00:00:00

最近感觉心理咨询的谈话内容挺值得记录的,就写下来了。

假装重要 #

我在前三四个星期的一次咨询聊到在大学被剥削的感觉,结束时咨询师觉得这个话题挺重要的,便邀请我下次再谈。然后最近这两三次的咨询都在聊在大学被剥削的感觉。其实我不太想聊被剥削的事情,感觉都聊得差不多了,没什么内容可谈了。虽然我不想谈了,但感觉自己最好还是遵从专业人士的意见吧,继续深入谈同一个话题或许更好。

上周六要考英语专业八级,所以取消了周五的咨询。今天距离上次咨询已经两个星期了,我都忘记上次聊了什么。实在是没法继续聊上次的话题,就和咨询师说忘记上次聊了什么。我在第一次聊被剥削这个话题时,咨询快结束时还说了很多话。咨询师当时就觉得我意犹未尽,那么这对我来说肯定很重要。于是他在结束时就说这很重要,下次再谈。

其实也不是这个事有多重要,只是我不想咨询的时间那么快结束,就在结束前几分钟猛说话。咨询师以为我猛说话是代表这个话题很重要,我又觉得咨询师的意见比较专业,要相信他。于是我们就谈了两三次这个话题,仿佛在一起演戏,把不重要的当成重要。

咨询结束前猛说话是不能接受咨询结束的表现。这有点像我不想睡觉的感觉。有时候我睡觉时感觉自己当天虚度光阴,很想学习一下,补救这虚度的一天。这就是不能结束一天要结束的事实。到了晚上,这一天就是结束了,已经不能补救了。想做的事明天再做便是。如果熬夜补救虚度的光阴,然后导致作息紊乱,这岂不是得不偿失。

咨询亦是如此,反正每周都做一次,不用急于一时,还是慢慢适应结束的时刻吧。现在就顺着自己心情,聊聊感兴趣的话题吧。专业人士也会误解,还是相信自己吧。

关于咨询的疑问 #

咨询有什么用 #

对我来说,咨询的作用是排解负面情绪和获得心理成长。我现在做心理咨询没有什么特定目标。现在心理咨询起作用的方式和正念冥想很像,做的时候不追求什么,过了一段时间自己就了些变化。

心理动力学流派1则认为咨询师是要帮助来访者发现潜意识。好像发现潜意识之后人就会变好,感觉很玄乎,这个道理我也不太理解。

咨询有目标吗 #

从咨询师的角度来说,短程咨询2有目标,例如:让来访者情绪平稳。长程咨询可以没有具体的目标。我一开始做咨询也有目标,就是减轻咳嗽3这个强迫症状。慢慢缓解后就没什么目标了。现在还在继续咨询的原因主要是排解负面情绪,之前试过停止咨询,感觉太难受了,就恢复了。

咨询会结束吗 #

咨询会结束。

咨询什么时候结束 #

来访者心理慢慢成熟后,咨询师不能帮到来访者了,这时就该结束了。咨询师和来访者都在成长的话,那咨询应该会维持比较久。也有一些比较遗憾的结束方式,比如:咨询师从单位辞职了、来访者无法负担咨询涨价。


  1. 我的咨询师就是心理动力学流派的。 ↩︎

  2. 心理动力学流派的短程是 24 周,认知行为流派是 12 周。我是听咨询师讲的,希望没记错。 ↩︎

  3. 咳嗽最严重的时候就是一学习就咳嗽,看书做题都不行。这是我得过最奇怪的病了。这世上居然有让人不能学习的病,不知道有没有人羡慕。这个症状很奇怪,它的治愈方法也很奇怪。我谈恋爱之后咳嗽就好了,心理医生都没法治愈的症状,居然被女朋友治愈了。没想到恋爱还有这等功效,女朋友真的是我的救命恩人啊。 ↩︎

行李清单

2021-04-27 00:00:00

收拾行李对我来说一直是个大难题。每次出门都要收拾很久,还常常丢三落四。现在终于想到了个好办法——行李清单。出门前先写一份行李清单,然后把清单上的东西都拿出来,再逐一清点,有的东西就打个勾。就像这样:

即使用了清单,我还是会漏掉一些东西或者该做的事,收拾行李实在是太难了……不过我又想到了一个办法——想象要做的事情。把从出发地到目的地之间的事情都想象一遍。

其实可以做一份总清单,把所以可能带的东西和要做的事都写进去。每次出门前就复制一份总清单,然后根据情况做增删,增加的内容就再补充到总清单。这样就不用想那么多了。清单挺适合保存到 GitHub Issues 的,存在那里比较方便,手机电脑都能用。

论文格式是人类退步的滑梯

2021-04-25 00:00:00

四个月前吐槽了毕业论文参考文献有两种样式,现在写完论文了,感觉还得再吐槽一番。写论文倒没有多难很难,但更难的是论文格式。论文格式要求真的是太害人了,不吐不快。学院给了一份 5400 字的格式规范。规定特别详细,规定了字体、行距、页边距、页眉页码、参考文献格式……为什么都写了那么长的规范,还不给份模板?难不成要学生从空白文档开始调格式吗?经过我的一番调查,其实是有模板的,但今年没有。那行吧,就拿上一届学生的论文作模板咯。但这论文格式又年年都变,拿上一届学生的来用也得调整。制定格式规范的人啊,你们这帮人到底给学生和导师挖了多少坑。难不成你们是打印店的卧底?这格式规范文件后缀名居然是 .doc,肯定是个祖传文件。

真是出师不利,为了避免被格式折磨到不想再写,我还是先不弄格式了。初稿是用 Typora 写的,在干净的界面上写论文真是如沐春风。写完之后就要把内容迁移到 Word 文档了。多亏女友调好了她论文的格式,我就直接拿她的作模板了。要是没女友相助,我估计直接去第二次答辩了,真是万分感谢。把内容一点一点地粘贴到 Word 文档里面。光是这件事就花了我两三个小时,一边粘贴一边爆粗。

除了要应付繁杂的格式要求,还要担心踩到好心人挖的坑里。有同学在专业群问查重的问题,辅导员就建议直接把文本粘贴到维普网页上,或者上传 PDF。但论文是用 Word 写的,直接复制内容或者上传 PDF 可能会导致维普检测把不该查重的部分也查了。学校就只提供两次查重机会,学校只认这两次的结果。要是上传了错误的文档就浪费了一次宝贵的机会。幸亏维普对粘贴到网页的内容有字数限制,不然同学要中招了。还有同学发了模板,我的火眼金睛看到一句重要的话——「模板不能涵盖所有情况,请参考『附录一 格式规范』。可今年的格式规范是「附录二」,所以这个模板不是今年的。根据上面写的班级,应该是去年的模板。有的同学就更厉害了,发了前年的模板。要不是我头脑清醒,早就被害惨了。

那些纠结细节的同学也很可怕,他们会在群里问没规定的格式怎么弄。比如标题下面要不要加空行,页眉的校徽的位置和大小怎么设置。我觉得格式问题真的没那么复杂,首先遵守学院的格式要求,没规定的格式就参照上一届学生的论文。可靠的信息来源就两个:格式规范、学院老师的答复。辅导员和同学的答复都不见得可靠。这些同学在群里问一问还没什么,就怕他们去问学院的老师。不问就不是问题,问了就成了问题。希望大家能好好分辨信息的可靠性,不要见风是雨,搞得人心惶惶的。

吐槽了那么多,我也在想怎么解决论文格式这个大难题。我觉得写作应该遵循「内容与样式分离」。什么是内容与样式分离?举个例子,论文的一级标题格式是居中加粗四号宋体,我现在要写两个标题,分别是「翻译过程」和「案例分析」。

「内容与样式分离」的做法是这样的:

  1. 设置「标题 1」样式
    1. 居中
    2. 加粗
    3. 四号
    4. 宋体
  2. 输入「翻译过程」
  3. 把「翻译过程」设置为「标题 1」
  4. 输入「案例分析」
  5. 把「案例分析」设置为「标题 1」

「内容与样式不分离」的做法是这样的:

  1. 输入「翻译过程」
  2. 设置「翻译过程」的格式
    1. 居中
    2. 加粗
    3. 四号
    4. 宋体
  3. 输入「案例分析」
  4. 设置「案例分析」的格式
    1. 居中
    2. 加粗
    3. 四号
    4. 宋体

感受到两者的差距了吗?「内容与样式分离」会减少重复劳动。「内容与样式分离」就是定义样式,再把样式应用于内容,这样就不需要写一个标题就调一次格式。在毕业论文这件事上,我觉得可以用文档转换器 pandoc 来实现「内容与样式分离」。

  1. 新建一个模板文档(template.docx)

    1
    
    pandoc -o template.docx --print-default-data-file reference.docx
    
  2. 把模板的「标题 1」的样式设置为「居中加粗四号宋体」

  3. 新建一个空白文档(input.docx)

  4. 在空白文档填写「翻译过程」,并将其设置为「标题 1」。

  5. 把模板的「标题 1」样式应用于空白文档。

1
pandoc input.docx -o output.docx --reference-doc template.docx

现在打开输出文档(output.docx),你会发现「翻译过程」已经变成了「居中加粗四号宋体」。

铺垫了那么多,终于要说解决格式问题的办法了。学院负责制作模板,打包 pandoc 和转换文档的脚本。学生只管写,写完后打开脚本让 pandoc 负责排版。这样学生和导师都可以关注于内容,而不是纠结格式,甚至还可以用 Markdown 写论文。

其实我觉得写论文还是用 R Markdown 最省心。理科生要输入数学公式,所以会用 LaTeX,那再迁移到 R Markdown 也不是难事。可我们文科生好像没有必须不用 Word 的理由,估计还得在 Word 的泥潭里躺一万年。

/æ/ 在美式英语的 3 种发音

2021-04-13 00:00:00

/æ/是一个曾经让我很迷惑的发音。多数情况下,我听到的 /æ/ 和在《美语音标》听到的是一样的。但我总感觉 /æ/ 跟在鼻音 /m/、/n/、/ŋ/ 后面就变音了。后来我看了 Rachel’s English 的讲解视频才发现原来 /æ/ 有 3 种发音,/æ/ 的发音取决于它后面的音标。

光看文字应该很难理解,可以点开剑桥词典,搜索 bad、land 和 gang,点击 US 旁边那个喇叭按钮,听一听 3 种发音。不过最好还是看这个讲解视频哔哩哔哩),边看边练习应该更易懂。

随想

2021-04-01 00:00:00

本文用于记录难以成文但值得分享的内容,不定时更新。

2020-11-21 #

Hugo 单页文章列表 #

config.tomlarchivePaginate 设置一个很大的值(比如 99999),这样存档页面就不会分页了。

2021-03-05 #

完全浸泡干香菇 #

泡干香菇时,干香菇总是浮在水上,让我很头疼。现在发现了一个让干香菇完全浸泡在水里的方法。

  1. 放香菇到碗里
  2. 倒满热水
  3. 反扣汤锅锅盖到碗上

拯救鸡胸肉 #

鸡胸肉高蛋白,低脂肪,是有营养的食物,但就是吃起来啃纸一般。最近发现了两个拯救改善鸡胸肉口感的方法——用肉锤把肉锤烂、加淀粉。这两个方法可以一起用,先把肉锤烂,再放淀粉。

2021-03-07 #

阿斯巴甜 #

阿斯巴甜是甜度高热量低的甜味剂,可用于代替食糖,从而减少热量摄入。阿斯巴甜很适合减脂/健身人群。我称之为人类伟大的发明、神奇的化学物质、减脂伴侣。阿斯巴甜真的超甜,用的时候先放一点点试试味道。如果放一勺下去可能会甜死。

2021-04-14 #

Microsoft Edge 浏览器 #

Linux 版的 Edge 浏览器现在可以登录微软帐号侧面标签页的功能超级好用,而且不翻墙也能装插件。现在 Edge 已经是我的主力浏览器了。

Vertical tabs

2021-05-02 #

自我牺牲 #

当孩子需求和父母认为的不一致时,父母还执意以自己的方式对孩子好,这就成了自我感动。对一个人好,就应该以他希望的方式,而不是自己认为的方式,或是社会公认的方式。

父母以一种自我牺牲的方式对我好的时候,我很难接受这种好。首先,我不需要这种好。第二,我会有亏欠感。我感觉压力太大了,我可不希望父母做让自己不舒服的事情。良好的关系,应该是轻松舒适的。

2021-05-13 #

域名续费优惠券 #

今天在 Namecheap 上续费域名,在谷歌搜 namecheap renewal coupon 就找到个减 20% 的优惠券。之前买域名的时候好像没用优惠券,真亏。

单身狗

2021-03-21 00:00:00

单身是个中性的词,而狗除了指动物以外的定义都是贬义。

  1. 即犬
  2. 十二生肖之一,配地支的“戌”。
  3. 比喻坏人。
  4. 奉承,巴结。
  5. 詈词。表示极端鄙视。

——《现代汉语大词典》

当单身遇上狗,单身也就笼罩着一层贬义的色彩。但是,贬义也不是绝对的。对于爱狗人士来说,狗是可爱的动物,反而变成褒义词了。英文中和狗相关的用法也比较正面,如 lucky dog(幸运儿)。

十年前听到单身狗这个词的时候,感觉还挺有趣,自称单身狗就是单身人士的幽默。但是现在越听越不喜欢。首先,我不能理解为什么要给自己贴上贬义的标签。反正我不以单身狗自称,我不想贬低自己。其次,我不能理解为什么有人见到情侣就要自称单身狗,仿佛自我贬低还有种快感。

谈恋爱之后我就更不喜欢单身狗这个词了。有时候同学说我在撒狗粮1,我听了就觉得特别尴尬,仿佛我牵着女友的手都伤害了单身的同学。要是我亲女友一口,那有些同学岂不是要洗眼睛了。也许自嘲的人是有些嫉妒吧。当一个人自称单身狗时,看似自嘲,但其实有些攻击性。


后记:我讨厌单身狗一词可能是因为我太严肃了,总是咬文嚼字。说单身狗的人不见得有恶意,只是觉得好玩。


  1. 撒狗粮的意思是「情侣在单身人士面前表达爱意」。与其对应的词是吃狗粮,意思是「单身人士看到情侣表达爱意」,喂吃狗粮亦适用于恋爱人士。 ↩︎

动漫这个词不能再用了

2021-03-21 00:00:00

动漫最初的意思是「画和画」,出自 1998 年创办的中华动漫出版同业协进会与 1998 年创刊的动漫杂志《动漫时代》。我也是坚定地认为动漫的意思是「动画和漫画」。

2015 年的时候,有次听同学说动漫是大人看的(动画),动画是小朋友看的。我听到动漫的这个定义的时候十分震惊,动漫居然可以指深刻的动画。根据我最近的观察,动漫目前最流行的意思是动画,并不是深刻的动画。

我觉得赋予动漫一个新定义的行为完全不可理喻。接下来我说的原义是「动漫=动画+漫画」新义是「动漫=动画」。首先新义和原义有冲突,会让人造成误解。其次,新义不符合语言习惯。原义的「动」「漫」二字是从「画和画」里摘取出来的,这种缩写是符合语言习惯的。但是我真的不理解新义是怎么回事,难不成动漫是「会画」,但动画不一定是从漫画改编的。

最近上网发现不符合语言习惯的新义居然是最流行的,还有衍生词:国漫(国产动画)、日漫(日本动画)。看到国漫和日漫,我第一反应就是漫画,真的想不到是动画。创造国漫一词的人不会想到国产漫画的缩写也是国漫吗?自从新义流行后,关于动画和漫画的词真的是越来越混乱了。哪怕现在多数人觉得动漫就是动画,我也不能接受这个定义。

现在动漫有两个意思了,我还是要说:「动漫的意思应该是动画和漫画!」 我只好放弃用这个有歧义的词,改成说动画和漫画。我觉得交流用通俗大众的词语更好,造新词会增加交流成本。例如,大家都知道动画、日本动画、国产动画的意思,但是番剧、日漫、国创就不见得人人都懂。

凉粉、烧仙草和龟苓膏的区别

2021-03-15 00:00:00

总结:凉粉、烧仙草和仙草冻是同一种食物,龟苓膏则是另一种食物,它们都是黑色凝胶。凉粉不苦,原料是草本植物仙草、薯粉/淀粉。龟苓膏苦,正统龟苓膏的原料是龟板、土茯灵、甘草等中药材。

左边是仙草冻,右边的是龟苓膏

今天去超润甜品店买糖水给女友喝,去买的时候突然想起了一个我纠结很久的问题:凉粉、烧仙草和龟苓膏看起来都很像,它们到底有何区别。到了超润甜品,先点了女友要的椰汁红豆仙草西米和炸馒头。然后我翻遍了菜单,发现没有凉粉,就打包了烧仙草和龟苓膏,打算回家后边看维基百科边吃。不过转念一想,既然来了店铺,为何不直接问店员呢,他们才是最懂行的啊。吃了那么多年,实在是不想吃得不明不白,我就问了店员姐姐。

我:请问凉粉、烧仙草和龟苓膏有什么区别?

姐姐:凉粉就是(烧)仙草,龟苓膏是另一种食品,龟苓膏是苦的。

(姐姐耐心地科普……)

姐姐:你是哪里人?

我:我广东的。

姐姐:广东人都唔知,我打死你,你唔饮糖水咩?(广东人都不知道,我打死你,你不喝糖水吗?)

没想到被嘲讽了,这居然是广东人的常识!

我:那你们店的烧仙草和龟苓膏是自己做的还是买到现成的?

姐姐:你家的饭是什么做的?

我:用米煮的。

姐姐:那不就是咯,我们也是买原料回来自己做的。

又被嘲讽了……

不过姐姐人挺好的,拿了两个杯子分别舀了几勺烧仙草和龟苓膏给我,让我试一试。它们看起来真的没什么区别,吃起来口感也差不多,但味道就有差别了。仙草有点甜,龟苓膏则是有点苦。回家之后看了维基百科的相关条目,再结合姐姐的科普,整理了一下三者的区别。

龟苓膏 凉粉/烧仙草/仙草冻
形状 黑色凝胶 黑色凝胶
味道 有点苦 有点甜
原料 龟板、土茯灵、甘草等中药材 仙草、薯粉/淀粉
左边是仙草冻,右边的是龟苓膏
左边的是台湾烧仙草,右边的是龟苓膏

简单来说,凉粉和烧仙草是同一种食物,龟苓膏则是另一种食物。不苦的黑色凝胶叫仙草冻,又称凉粉1烧仙草2,原料是草本植物仙草、薯粉/淀粉。苦的黑色凝胶叫龟苓膏,正统龟苓膏3原料是龟板、土茯灵、甘草等中药材。烧仙草还有另一种形态——台湾烧仙草,即仙草冻加奶茶、红豆、绿豆、芋圆、花生等配料。

要感受凉粉、烧仙草和龟苓膏的区别,最好还是去糖水店点来吃,可以仔细观察和感受,还可以和店员交流。一定要记住,如果店员问你是哪里人,千万别说是广东人。

参考资料 #


  1. 北方有种白色的食物也叫凉粉,以大米、豌豆或绿豆为主要材料制作而成。为了和北方凉粉相区别,仙草冻又称黑凉粉。 ↩︎

  2. 仙草冻还叫仙人粄草粿。 ↩︎

  3. 我之前吃过的王老吉龟苓膏,成分表里面有凉粉,看来龟苓膏也可能加有凉粉。 ↩︎

让 Hugo 自动给图片加上说明文字

2021-03-11 00:00:00

本文测试于 Hugo extended 0.125.6,完整的网站代码在此

将图片标题用作说明文字(推荐) #

Markdown 插入图片的代码是这样的:

1
![描述](链接 "标题")

对于的 HTML 代码:

1
<img src="链接" alt="描述" title="标题" />

用户的光标放在图片时,会有提示框,其内容为标题。提示框不好用,因为用户不一定会把光标移动到图片上,而且用户可能不用鼠标。

既然提示框不好用,那我们可以将标题转换为图片底下的说明文字,这样用户一定会看到。对应的 HTML 代码为:

1
2
3
4
<figure>
 <img src="链接" alt="描述">
 <figcaption>标题</figcaption>
</figure>

配置流程:

  1. 在博客根目录下新建 layouts/_default/_markup/render-image.html

    1
    2
    
    mkdir layouts/_default/_markup/ -p
    touch layouts/_default/_markup/render-image.html
    
  2. render-image.html 填入以下内容(如果不需要修改样式,可以去除 class="..."):

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    {{ if .Title }}
     <figure class="figure">
     <img class="img" src="{{ .Destination | safeURL }}"
     {{- with .Text }} alt="{{ . }}"{{ end -}} >
     <figcaption>{{ .Title }}</figcaption>
     </figure>
    {{ else }}
     <img class="img" src="{{ .Destination | safeURL }}"
     {{- with .Text }} alt="{{ . }}"{{ end -}} >
    {{ end }}
    

![](https://upload.wikimedia.org/wikipedia/commons/1/1f/Wiki-sisters.png "维基娘的姊妹们(由左至右):维基共享资源娘、维基百科娘、维基语录娘")显示成这样

如果不需要说明文字,就写 ![维基娘的姊妹们(由左至右):维基共享资源娘、维基百科娘、维基语录娘](https://upload.wikimedia.org/wikipedia/commons/1/1f/Wiki-sisters.png)

将图片描述用作说明文字(不推荐) #

不推荐此方法的原因是 <img> 元素的 alt 属性(描述)<figcaption> 元素意义不同,将前者转换成后者是错的。

如果你的 Markdown 图片链接是只有描述没有标题,也可以把描述变成说明文字。render-image.html 需要改为:

1
2
3
4
<figure class="figure">
 <img class="img" src="{{ .Destination | safeURL }}" alt="{{ .Text }}">
 <figcaption>{{ .Text }}</figcaption>
</figure>

![维基娘的姊妹们(由左至右):维基共享资源娘、维基百科娘、维基语录娘](https://upload.wikimedia.org/wikipedia/commons/1/1f/Wiki-sisters.png)显示成这样

样式 #

我给本站加了一些额外的样式,你可以根据你的需求调整你的网站。!important 必要时才用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* remove figure margin */
figure {
 margin: 0 !important;
}
figure > figcaption {
 color: gray;
}
/* image is not clickable because photoswipe is disabled */
p > img {
 cursor: auto !important;
}
/* center img */
// :has only works for browsers after December 2023, see https://caniuse.com/css-has
p:has(.img), .figure{
 text-align: center;
}

用表格添加说明文字 #

我还在 Stack Overflow 发现了一个怪招:使用单列双行的表格,第一行放图片,第二行放说明文字。

1
2
3
|![](图片链接)|
|:-:|
|说明文字|
1
2
3
| ![](https://www.storywarren.com/wp-content/uploads/2016/09/space-1.jpg) |
|:--:|
| Space |

参考资料 #

女生节、妇女节和女神节

2021-03-08 00:00:00

昨天是 3 月 7 日,有人在大学的群里发红包庆祝女生节,这我才发现女生节到了。上网维基看了看女生节的条目,发现女生节的日期是 3 月 7 日,刚好是妇女节的前一天。根据我在大学的观察,女生节的庆祝方式一般就是男生们买礼物给女生们,或是男生们表演节目给女生们看,以表达男生对女生的关心。大学生似乎对此习以为常,不过我总感觉不对劲。我认为女大学生就是独立个体了,为什么还需要特别关心呢?女生真的享受这种关心吗?我感觉这个节日有种对女性的歧视,仿佛女生是弱势群体,需要特别照顾。反过来,我在男生节收到礼物感觉是无功受禄,受之有愧。收了男生节礼物实在是不好意思,只好在女生节也送礼物给女生。

今天是妇女节,又称国际妇女节、三八国际妇女节、国际劳动妇女节,英文是 International Women’s Day。妇女节是纪念妇女为争取政权、男女平等所获成就的国际性节日。妇女节是值得纪念的节日,不过今天大学的微信群没人发红包庆祝妇女节了,也没人发信息庆祝妇女节,好像大家都不大在意呢。翻朋友圈,发现了有趣的现象。有四五条动态用女神节或女王节表示庆祝,只有一条动态用妇女节这个词。看来大家知道这个节日,但似乎不喜欢妇女这个词。我觉得不喜欢的原因是妇女听起来像家庭主妇(专做家务而无职业的妇女),家庭主妇经济上依附于丈夫,而且可能年纪较大。我们的社会文化存在对女性的年龄歧视:年轻女性更有价值,年长女性更没价值。所以女性害怕被贴上年纪大的标签。其实现代汉语大词典中妇女的定义是「成年女子的通称」,不过在现实使用的时候倒是有家庭主妇的感觉。

至于女神节和女王节,这是商家把妇女节变成购物节的手段,皆在鼓励女性消费。女神和女王,听起来高高在上的,完全和男女平等不沾边。1857 年 3 月 8 日,美国纽约的制衣和纺织女工走上街头,抗议恶劣的工作条件和低薪。3 月 8 日,这曾经是女性抵抗资本家的日子,现在资本家却把它变成了用以谋利的购物节。唉,我只能骂一句:「我呸!」

既然妇女可能包含年龄歧视,女神节和女王节又偏离了妇女节的原意。那该怎么说呢?中国人见面总不能来一句「Happy women’s day」吧。我觉得可以把 Women’s Day 译为女性节或女人节,女性和女人都是中性词。女人还有妻子的意思,所以可能女性这个词会好些。张佳玮把 Women’s Day 译为女节,这个译法更为简洁。


后记:妇女节是国家假期,妇女放假半天。

为自己而活

2021-02-18 00:00:00

现在是凌晨四点十三分,我又失眠了。脑海里闪烁着白天和亲戚的对话。

(我目前大四。)

亲戚:你实习了吗?

我:没,不打算实习了。我不喜欢现在读的英语专业。打算毕业后学一年计算机,我对计算机感兴趣。

亲戚:你学计算机也没个确定的方向,学半年之后又不喜欢怎么办?不如先实习,积累一下工作经验。

啊,这话听了真他妈难受。虽然也不是第一次听了,但每次听到还是很难受。「如果学了新东西可能有一天不喜欢了,那还不如从事所学专业的工作」这算什么狗屁道理。我读了三四年英语专业,经过深思熟虑,最终承认了自己不喜欢这个专业,决定毕业后自学一年计算机。目前才刚刚开始不久,就直接被判了死刑,奉劝我去做百分百讨厌的英语工作。但实际上,不试试看,鬼知道喜不喜欢啊,未来的事情难以预测,还才开始没多久就被否定了的滋味真他妈难受。

从我出生开始,仿佛都是被社会期待推着走的,我有几分几秒是为自己而活呢?我就像一个木偶,在社会拟定的人生脚本下演出。小学的任务是做功课;初中的任务是考高中;高中的任务是考大学;大学的任务是拿学位证;毕业后的任务是找工作。填高考志愿算是第一次为自己的人生作选择吧。本来我是想去广东药科大学读康复治疗专业,不过高考分数只能让我读一所民办本科学校,不能选择心仪的学校,结果就去了民办本科学校读自己擅长的英语,现已验证第一次的人生选择并不如人意。

本以为上了大学之后,没有高考这种特定目标会轻松些许,但实际上大学生活并不是一个自由的状态。首先,大学里有不少浪费时间的杂事。其次,读大学依然有「特定任务」:好好听课,不能挂科,考过英语专业四级和八级1。一开始我也是这样做的,可是我慢慢地开始觉得越来越没劲。按照学校的安排去听课和考试,跟读中学也没什么区别。英语专业的主要课堂内容就是文学,而我也不喜欢文学。我是一个很讨厌做题和考试的人。大二下学期为了备考专四,我练了一段时间的听力训练,居然还做了十套试卷。其实我内心并不是特别想考专四,只不过看到大家都在准备着,我也就随大流了。随之考完专四后,我在学业上突然没了目标,接着就抑郁了一个月。

大二下学期,发现自己不喜欢本专业之后,感到很迷茫,也不知道自己该做什么。后来我内心有个声音叫我去学编程,大概是因为我曾经喜欢过编程吧,初中的时候还学过一个星期的 AutoIt v3,当时觉得十分有趣。不过那时发现了自己都不认识 local 和 global ,感觉学起来很费劲,所以就先去自学英语。花时间学习英语之后,初中英语成绩提高了不少。迫于学业压力,久而久之就忘记了学英语的初衷是能够轻松地学习编程。

在大三上学期的某个凌晨,我失眠了,就去看知乎和哔哩哔哩来打发时间,结果越看越无聊。然后我就打开存放许久的 Python 教程,搭建好环境之后写下 Hello World,突然那种有趣的感觉又回来了。接着我去学了半本 Python Crash Course, 2nd Edition 之后又学了 Python for Everybody,依然觉得有趣。这是我人生中重新拥有了自己喜欢做的事情,学了编程之后就很少再有平日里那种抑郁的感觉。我发现计算机科学是个很适合自学的科目,GitHub 上就有个学习路线。于是和家人商量,打算毕业后自学一年计算机,尝试去做个程序员。

按照学习路线上课之后我又开始觉得没劲了,这不就上大学的课程吗?又回到那种按部就班的状态了。于是我放弃了学习路线,对什么感兴趣就做什么,学了些杂七杂八的东西:Markdown、HTML、Git、LaTeX、R Markdown2。还用 blogdown 搭建了博客,2020 年 11 月 1 日发布了第一篇文章。我就这样拥有了第二件喜欢做的事情:写博客。要是没有前面的尝试,我就不会发现我喜欢编程和写作,尝试还是很重要的。

不过我还是很担心自学一年的编程水平不足以让我成为一个程序员,可能又因为长时间放下了英语而找不到英语工作。啊,我又陷入了毕业后必须找工作的人生脚本了。写下这段我才发现自己对工作有些执念:先选择自己喜欢的,不行再考虑熟悉的英语。其实工作那么多,也不用吊死在一棵树吧。编程作为一个兴趣爱好也不错啦。

要我一直做不喜欢的事情,我会抑郁,我就是这样的人。所以我不想再为别人的期待而活了,不想再过那种抑郁的生活了。现在也不想设立什么学习目标了,毕业后的一年就随心所欲做所有感兴趣的事情吧。我想为自己而活,哪怕一年也好。尝试一年后,如果能以喜欢的事情为职业就最好了,实在不行才做其他。最后,引用《不能承受的生命之轻》的一段话作为结尾吧,它代表了我的心声。

人永远都无法知道自己该要什么,因为人只能活一次,既不能拿它跟前世相比,也不能在来生加以修正。没有任何方法可以检验哪种抉择是好的,因为不存在任何比较。一切都是马上经历,仅此一次,不能准备。

所以,对于我来说,在当下经过思考的决定就是最好的。


  1. 专四专八是英语专业的等级考试。专四是大二下学期(第四学期)考的,专八是大四下学期。专四专八一生最多考两次,第一次考不过,下一年可以重考。 ↩︎

  2. 现在看来,其实不是杂七杂八的东西,它们都是写作工具。或许无论我再怎么做不喜欢的事情,内心还是会默默地把我引导到喜欢的事情上。 ↩︎

催生的恶果

2021-02-06 00:00:00

昨天听了故事 FM 的第 458 期节目《拉拉自述:和直男接吻后,我决定再也不相亲了》。故事讲述人小A今年 29 岁,生活在贵州贵阳,是一名中学老师,是女同性恋者。

一开始小A和讲述了和女友小柚的甜蜜故事,后来发现小柚已经结婚,还有另一位女朋友,于是她们最后分手了。小A为了确认自己会不会对男生有好感,就去相亲,认识了一位理工男。答应和理工男恋爱后,男友和小A亲吻,小A不喜欢,就推开了男友。男友洗完澡穿着内裤出来,小A看了看,觉得男人的身体没有美感。和男友同床睡觉时,男友的鸡鸡顶着小A,小A觉得特别恶心。就这样小A确定了自己就是女同性恋,和男友分手了。

小A家比较传统,父母绝对不会接受小A出柜。为了不伤害父母,小A选择形婚。小A和认识十几年的 gay 朋友结婚了。婚后两人住一套房子,互不干涉。有天小A心情不好,男方妈妈给小A准备的丰盛的晚餐,都是小A喜欢的食物。看着老人如此忙碌,小A觉得要把这个善意的谎言圆下去。

听完这个故事,感觉故事里的每个人都是受害者,为每一个人感到难过。小A为了满足父母对结婚的要求,不得已撒了个弥天大谎。理工男到分手那一刻也不知道为什么自己的初恋还没维持一天。Gay 的母亲并不知道小A和 Gay 的婚姻是形婚。

我觉得这场悲剧的源头就是「传宗接代」的观念了,要是小A的父母不要求小A结婚生子,那就没有这场悲剧了。一部分人不生小孩,人类也不会灭绝啊。对于部分国人来说,「传宗接代」似乎是和「地球围着太阳转」一样不容置疑。你问他们「为什么一定要生儿育女」,他们会说「这就是很自然的一件事啊」。我觉得结婚和养小孩真的是很严肃的事情。小孩不是生下来就行了,养小孩需要花费大量的金钱和精力,并且还得养十八年。如果结婚和养小孩不能给自己带来快乐,那就别做,只为了满足父母去生小孩,到头来委屈的还是自己。自己的感受最重要,一定要遵从自己内心的感受。说到底,对自己负责的只有自己,往父母挖的坑里面跳了,最后还得自己爬出来,他们是没办法给你逝去的青春负责的。

在哔哩哔哩看未删减动画

2021-02-02 00:00:00

2021-02-18 更新:哔哩哔哩港澳台动画仍然有些是删减版的,我现在改用巴哈姆特动画疯看动画了,终于不用担心删减和下架了。会员价格是每月 99 新台币(22.8 人民币)。不开会员也能看所有动画,只是看 30 秒广告,然后可以看 720P 画质的动画。不过不开会员就不能通过年龄验证,可能有些动画看不了。所以最好还是开通一次吧。

在国内看动画最头疼的就是审核问题,但凡动画有点血腥暴力或是色情就难逃广电总局的魔掌了。轻则删减,重则下架。不过我发现哔哩哔哩的港澳台区域动画是未删减的,所以我现在会优先考虑看港澳台动画。使用国内网站直接打开港澳台动画是没法看的,不过我们可以直接到港澳台使用哔哩哔哩客户端把动画缓存下来。哈哈,开个玩笑。其实我想说的是怎么绕过区域限制。有几个绕过区域限制的方法,分别是翻墙、使用哔哩哔哩 UWP、使用脚本程序。

翻墙 #

使用翻墙软件翻墙到港澳台就可以直接看港澳台动画了,这个方法最靠谱。

哔哩哔哩 UWP #

打开 Microsoft Store,搜索「哔哩哔哩 UWP」,安装由逍遥橙子开发的「哔哩哔哩 UWP(第三方)」。打开哔哩哔哩 UWP直接就能看港澳台动画了。以前是可以直接看港澳台动画,现在显示的某些民间汉化组资源,应该也是未删减的。

脚本程序 #

注意,用脚本的默认接口已无效,测试于 2021 年 2 月 2日。目前要用脚本应该只能使用自定义代理服务器了,请参考解除B站区域限制的文档。

用脚本之前要先安装脚本管理器,打开 Tampermonkey 官网进行安装。接着安装解除B站区域限制或者Bilibili 港澳台。开启其中一个脚本,翻墙登陆 B 站打开港澳台动画就能看了。

轻松安装 Python 和 Python 包

2021-02-01 00:00:00

安装 #

Python 新手遇到的第一个问题就是安装 Python,我看好多教程都推荐到官网下载。这当然是正确的做法,不过还有更简单的方法。

Windows #

在 Win 10 安装 Python 特别简单,直接在 Microsoft Store 搜索 Python 就可以安装最新版本。注意,软件开发者是 Python Software Foundation。安装好之后在 Command Prompt 输入 Python 就可以使用了。

Microsoft Store 里的 Python

如果你用的是 LTSC 版本,就点击这里下载 Microsoft Store 安装包。如果是 LTSB 就点击这里下载安装包。如果你用 Win 10 之前的 Windows,就安装 Miniconda 吧,参考下面的用法。

Linux #

在 Linux 发行版一般都自带 Python,可以直接用。比如 Ubuntu 20.10 就自带 Python 3.8.6。不推荐采用源码编译,源码编译可能会覆盖系统自带的 Python。如果要用最新版的 Python,就先安装 Miniconda,再创建虚拟环境:

1
2
conda create --name py3.9 python=3.9 # 创建包含最新 Python 3.9 的环境
conda activate py3.9 # 激活环境

取消激活的命令是:

1
conda deactivate

取消自动激活环境的命令是:

1
conda config --set auto_activate_base false

如果你从事计算科学的工作,可以直接安装包含相关软件包的 Anaconda。其实 Miniconda 也可以安装 Anaconda 包含软件包:

1
2
conda create --name ana anaconda # 创建包含 Anaconda 所有软件包的环境
conda activate ana # 激活环境

设置镜像源 #

咱们大中华局域网连接 Internet 时,时而断线,时而速度慢。这时候就可以给 conda 和 pip 设置国内的镜像源,速度马上就飞起来了。

pip 镜像源 #

下面用的是清华镜像站的 pypi 镜像

临时使用:

1
pip install --index-url https://pypi.tuna.tsinghua.edu.cn/simple some-package

设置为默认:

1
2
3
# 先升级 pip 再配置
pip install --index-url https://pypi.tuna.tsinghua.edu.cn/simple pip --upgrade
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

conda 镜像源 #

下面用的是清华镜像站的 Anaconda 镜像

先建立文件:

1
2
conda config # 生成配置文件
nano ~/.condarc # 编辑配置文件

然后把下面的内容复制到 .condarc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
channels:
 - defaults
show_channel_urls: true
default_channels:
 - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
 - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
 - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
 conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
 msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
 bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
 menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
 pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
 simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud

最后运行 conda clean -i 清楚缓存,确保用的是清华源的索引。

安装 Python 包 #

我们可以用 pip 和 conda 安装 Python 包。

pip 用法 #

从 Microsoft Store 安装的 Python 自带 pip,Linux 的话就用自带的包管理器安装。

1
sudo apt install python3-pip

检查一下 pip 是否安装成功:

1
2
$ pip --version
pip 21.0.1 from /home/xxxx/.local/lib/python3.8/site-packages/pip (python 3.8)

有显示结果就是安装成功了。运行 pip --help 可以查看帮助,下面介绍几个最常用的命令。

conda 用法 #

运行 conda --help 可以查看帮助,安装、卸载和升级同样可以作用于多个软件包。

附录 #

诗婷

2021-01-31 00:00:00

偶遇 #

不记得是高二下学期还是高三上学期,我突然右脚大拇指痛,于是去康复科看。医生诊断完就给我开单子,我拿着单子瘸着走出去。刚走出去就遇到一个医生姐姐,看起来像是大学刚毕业的。她关心地问我:「你有什么问题?」我就说了一下脚趾痛的问题。我也算是医院的常客了,遇到的大多数医生一般都是问症状,开单子,很少会关心病人。突然被医生姐姐关心一下,而且我还不是她的病人,我感觉既意外又高兴。

出了诊室,看了看墙上贴着的员工表,原来刚刚的医生姐姐叫诗婷,是康复治疗师。拿着单子缴费之后我就回去给脚做电疗。因为当时我对运动学感兴趣,做完电疗就去问她运动学的问题,她也很耐心地给我讲,感觉她是个很友善的人。接下来几天还要做电疗,每次做完电疗就找诗婷聊聊天,不过我不太记得具体聊过什么了,大概是高中生活的糟心事吧。就记得诗婷很有同理心,很能理解我。我们还互加微信好友,交换了手机号码。高中的我敏感、自卑、朋友少1,很多情绪都是埋在心里,不曾向外人诉说,能有诗婷这样理解我的人真的十分开心。我和诗婷见面的次数可能还没有十次,但是她给我带来的影响却不小。在这记录几件记忆深刻的事吧。

提问 #

有次我在微信问诗婷解剖学问题,我问的第一个问题,诗婷是有回答的。接下来的问题,诗婷就没有直接回答,反而说:「你不读相关书籍的话,我现在解答了这个问题,下次你还是不懂。」一开始听到这话我是觉得不舒服的,感觉很惭愧。可能这就是忠言逆耳吧。这种惭愧感一直在我心里,因为我也没怎么读解剖学的书,所以就没再问那些问题了。不过诗婷说得也对,我没怎么读解剖学教材就来问,她回答了,我也是一知半解。作为一个求知者,还是要有这种惭愧感,尽量不提愚蠢的问题,不要浪费别人的耐心。

探索一个问题,没去查资料就去问别人,不就是大家嫌弃的伸手党吗?后来我学了编程,读了《提问的智慧》How To Ask Questions The Smart Way),也看了网上很多提问帖,才深刻地意识到不寻求答案就问别人,往往会问出一些过于宽泛的问题,浪费别人的时间。如果在网上提了一个糟糕的问题,可能别人会忽略或者教育你,最严重的就是骂你。所以提问前最好先自己去尝试寻找答案,让别人看到你是个主动的人,这样别人更愿意帮你一把。刚刚提到的《提问的智慧》是一篇很好的提问指南,虽然讲的是计算机的问题,不过里面的思想还是通用的。

电话 #

高三的时候感觉心理压力特大,想去看心理医生。在家试探性地问爸妈,我爸妈很嫌弃,他们觉得(电视剧那种疯疯癫癫的)神经病才需要去看心理医生。我就特别苦恼,最后鼓起勇气给诗婷打了个电话2。我就说了我想看心理医生,说出了我的纠结,问她有什么建议。她就鼓励我去试试看。那通电话聊了好久,可能有一两个小时,诗婷一直耐心地听。沉默的时候我感觉很尴尬,害怕诗婷说「没事就先挂了」。不过诗婷也没说那句话,直到我把想说的都说完了,我才挂的电话。打完电话心都是暖暖的。我从未料想过有人会听我讲一两个小时的话。这样的倾听本身就很治愈,也减轻了我对看心理医生的羞耻感。现在想起来,感觉诗婷就像《窗边的小豆豆》3里面的小林校长,会耐心地把话听完。

我没和爸妈打招呼就自己去看心理医生了,跟医生说了我会用手抠指甲4和捏耳朵,严重时还会弄出血,然后做了几个心理量表。诊断是焦虑症和强迫症。原来抠指甲、捏耳朵叫强迫行为,是强迫症的一种症状,另一种症状叫强迫观念。医生说这些强迫行为是用来缓解焦虑的。看到这个诊断的时候,我不是伤心,而是感觉心里有块石头落地了,我从小学就有的强迫行为终于能治了。

高三的时候我还有个特别奇怪的强迫症状:一学习就咳嗽。当时可能是压力太大了,焦虑情绪加重,出现了躯体化的症状。当时,一去上课就会不断咳嗽,晚上回到宿舍(看到同学在挑灯夜读)也会不断咳嗽。发展到最后就是看书就咳嗽,完全没法复习了。于是我就不去上课了,在学校对面租了个房子自己待着,有时回学校拿复习资料,月考的时候才回去。一看书就咳嗽实在是太难受了,很难复习,所以高三我就只复习了比较喜欢的英语和生物。我以为我就只能读个专科了,最后考了个民办本科,对高考结果还挺意外。

其实咳嗽的问题我从高一下学期进了重点班就有了,一直去医院看,但一直治不好。要不是去看了心理医生,咳嗽都成绝症了5。想想都觉得后怕。真是多亏诗婷的鼓励。

婚礼 #

认识诗婷没多久,她就要结婚了。她也邀请了我去喝喜酒。能被邀请还是蛮开心的。我当时还准备了一封五十块的红包。刚去的时候也不知道该坐哪一席,诗婷就请接待员带我去和她妹妹坐一桌。感觉自己特别受重视。谁知坐着坐着就有接待员过来问我是哪边的亲戚,我说我是诗婷的朋友,然后接待员就把我带到诗婷同事的那一桌。不过这样也挺好的,我在那桌还遇见一位同乡的医生姐姐。一开始发现谁都不认识,感觉怪尴尬的,后来有个认识的人就没那么尴尬了。在那第一次吃到龙虾,超好吃6。喝完喜酒离开后我才发现我忘给红包了,真是十分遗憾。

大学 #

刚上大学的时候觉得大学的糟心事太多了,比如浪费时间的素质培养计划和无聊的政治必修课。遂与诗婷吐槽这大学生活和想象的完全不一样。诗婷回复:「那你觉得大学是怎么样的呢?」我就愣了一下,思考了一下。我以为的大学生活是课业不多,有充裕的时间干其他事,但现实就是大学也有不少我讨厌的事情。看来我很容易以为自己的想法就是真相,然后发现大学它不是这样,现实证明了我的想法是错的,打击了我的自恋感。但这样的抱怨也于事无补,还不如学着在大学摸鱼呢。

联系 #

我一直很感激诗婷对我的帮助。过年的时候我都会发新年祝福给她7,也会寒暄几句。可是几年过去了,好像祝福语都用完了。和朋友保持关系对我来说一直是个难题,要是不见面似乎在微信也不知道聊啥。或许,相见不如怀念吧,把回忆化作文字记录也挺好的。

本文发布前就发给诗婷看了,这是她的回复:

看完你对我的描写觉得心里非常暖,没想到自己不经意的举动会对你有帮助,谢谢你。希望你越来越好。到时候毕业工作了一定要记得和我分享你的喜悦。


  1. 高中只是个时间限定词,其实我现在还是敏感、自卑、朋友少,不过不会因此而那么苦恼。 ↩︎

  2. 我给谁打电话都特紧张,打电话对我来说真的是很需要勇气的事情。 ↩︎

  3. 《窗边的小豆豆》讲述了小豆豆上小学时的一段真实的故事。小豆豆因淘气被原学校退学后,来到巴学园。小林校长听小豆豆吧啦吧啦讲了四个小时的话。在这期间,小林宗作校长不但没有丝毫的厌倦和不耐烦,反而一边点头一边询问:「还有呢?」,最后直到爱讲话的小豆豆没有话可讲。 ↩︎

  4. 我表舅听闻了我的症状,对我说:「抠指甲也挺好的,都不需要指甲钳了。」我听了这个说法感觉很放松,原来抠指甲还有这等好处,换个角度思考,这件事就变积极了。 ↩︎

  5. 看了两年多的心理医生都没完全治好咳嗽,后来上大学谈恋爱之后居然不治而愈了,真是不可思议。 ↩︎

  6. 后来和家人去这家餐厅吃龙虾,这次吃的龙虾反而不好吃,有苦味。 ↩︎

  7. 我不喜欢群发祝福语,每年最多给几位亲朋好友发自己写的祝福,诗婷就是其中一位。 ↩︎

用 Python 计算水电费

2021-01-29 00:00:00

前段时间搬家了,在之前住的小区还有些费用没缴,包括水电费、物业费和公摊费。一开始用计算器和笔算,好几次算错。每次出错都要从头到尾算一遍,算得我很烦躁。我突然灵机一动,为什么不用 Python 算这笔麻烦帐呢?于是我就在 R Markdown 里用 Python 算帐了。把数字都放到变量里,搞错了就改某个变量的值,然后计算机就会计算出正确的结果,很快就算好了。计算机果然很擅长做这种琐碎的计算。

注意,以下代码用到的 f'{expr=}' 是 Python 3.8 引入的语法。另外,在 blogdown 里运行代码最好用 .Rmarkdown 而不是 .Rmd。用 .Rmd 之后目录显示不正常,用 .Rmarkdown 就没问题1

Python 版本 #

1
2
import sys
print(sys.version)
1
2
## 3.8.6 (default, Jan 27 2021, 15:42:20) 
## [GCC 10.2.0]

公摊费 #

1
2
3
4
5
6
公摊费单价 = 20
公摊费已出帐 = 190.72
公摊费未出帐 = 20 * 6
# 最近 6 个月的公摊费都未出帐,只好按照平均价格计算了。
公摊费总价 = 公摊费已出帐 + 公摊费未出帐
print(f"{公摊费总价=}")
1
## 公摊费总价=310.72

物业费 #

1
2
3
4
物业费单价 = 125.7
物业费月份 = 2
物业费总价 = 物业费单价 * 物业费月份
print(f"{物业费总价=}")
1
## 物业费总价=251.4

水费 #

1
2
3
4
5
水费单价 = 4.75
水费起数 = 90
水费止数 = 103
水费总价 = (水费止数 - 水费起数) * 水费单价
print(f"水费总价:{水费总价}")
1
## 水费总价:61.75

电费 #

1
2
3
4
5
电费单价 = 0.65
电费起数 = 2474
电费止数 = 2569
电费总价 = (电费止数 - 电费起数) * 电费单价
print(f"{电费总价=}")
1
## 电费总价=61.75

总价 #

1
2
总价 = 物业费总价 + 水费总价 + 电费总价 + 公摊费总价
print(f"{总价=}")
1
## 总价=685.62

完全删除 Git 仓库的文件

2021-01-25 00:00:00

步骤 #

完全删除 Git 仓库的文件是我不常用的操作,但好像每年都得用几次。还是把方法摘抄于此作为备忘录,省得下次再去搜索。这个方法是从 GitHub 文档抄来的,操作比较危险,请先把本文看完再尝试。可以把整个仓库复制到另一个地方来尝试前 2 步。

  1. 重写历史

    1
    2
    3
    
    git filter-branch --force --index-filter \
    "git rm --cached --ignore-unmatch path/to/your/file" \
    --prune-empty --tag-name-filter cat -- --all
    

    注意,此命令需要在仓库根目录运行。如果反悔了就用 git reflog 查看第 2 条历史的哈希值,然后用 git reset --hard commit-id 就可以恢复了。

  2. 清除 reflog

    1
    2
    3
    
    git for-each-ref --format="delete %(refname)" refs/original | git update-ref --stdin
    git reflog expire --expire=now --all
    git gc --prune=now
    

    执行了这些命令之后文件就真的消失了,无法恢复。

  3. 推送更改到远程仓库

    1
    2
    
    git push origin --force --all
    git push origin --force --tags
    
  4. 联系 GitHub 删除 pull requests 中的文件

上述操作十分危险,如非必要,千万别在公开的仓库使用,不然会给开发者带来麻烦。使用后 Git 历史会被重写,别人 git pull 时会出错。

技巧 #

下面这几条技巧可以帮你避免添加不必要的文件。

大学抢课技巧

2021-01-10 00:00:00

让大学生感到惊心动魄的事情,除了期末考试,可能还有抢选修课。如果没能抢到心仪的课,就只能捡漏了,不管愿不愿意,都得上一个学期的课。作为抢课界的博尔特,我就来分享一下我的抢课技巧。

  1. 使用更快的网络

    可以用 Speedtest 测试网速。如果要和舍友挤着用校园网的话,可以试试手机热点。

  2. 给电脑插电,并关闭睡眠功能

  3. 提前 1 小时登陆教务系统

    抢课前几分钟才登陆就可能就登陆不上了

  4. 打开几个浏览器的无痕浏览窗口登陆教务系统

    无痕窗口

    只要内存够,能开几个窗口就开几个,窗口越多,机会越大。

  5. 排列好窗口,留一个窗口看北京时间

    很多个无痕窗口
  6. 到时间后点击所有窗口的选课按钮

  7. 不要刷新,慢慢等待

沸水除臭法

2021-01-10 00:00:00

前段时间家里突然有股恶臭,闻起来像是动物尸体的味道,闻到都想吐。有时能闻到,有时又闻不到,甚是烦恼。在家转了好几圈都没发现臭味的源头,太气了。把垃圾都打包放到门外,没用。打开门窗通风,也没用。最后发现洗手盆过滤网上有些剩菜,低头闻一闻,果然是这里。终于破案了,赶紧把剩菜倒掉。结果事情并没有那么简单,把剩菜倒掉之后洗手盆水管那里还是很臭。

在网上搜索一番,找到了一个除臭办法

  1. 准备材料
    • 小苏打粉
    • 白醋
    • 沸水
  2. 混合盐和小苏打粉
  3. 倒到排水孔
  4. 倒入白醋
  5. 倒入沸水

小苏打粉和白醋混合之后会发生化学反应,产生白沫,发出滋滋滋的声音。感觉很神奇很好玩,又弄了几次,结果把半包小苏打粉和半瓶醋都用完了。终于没有臭味了。从此以后,我每次洗完碗都会把洗手盆的过滤网清理干净,再也不敢把剩菜留在那里了。


后记

只用小苏打粉和白醋就能产生化学反应,不用加盐也行。看到益辉说是热水才是除臭的根本,热水融化了硬油脂。益辉的评论让我开始怀疑自己之前的认知。于是在网上考究了一番,发现了好几个不同观点。刚刚又看到列弛说热水杀死了产生臭味的细菌。总结一下:

  1. 二氧化碳疏通管道(馒头汤

    碳酸氢钠和醋酸反应,产生二氧化碳,利用二氧化碳的气流和气压,疏通管道。

  2. 小苏打混白醋的产物没有清洁作用(科普中国

    小苏打混白醋产生的二氧化碳和乙酸钠是不具备任何清洁能力的。但如果把二者分开使用,其实都有一定清洁作用。小苏打可用于去油污,白醋可用于去水垢。

  3. 热水融化了引起臭味的硬油脂(谢益辉

    我猜测这三样材料里面真正除臭的是开水,我不太明白酸碱中和的化学反应在这里面有什么用(只会产生二氧化碳和醋酸钠的盐),我感觉单纯的开水加碱或者开水加酸应该都是可以的。如果你的洗碗池用了滤网,那么下水道堵的原因可能是动物油脂的积累。我以前把水槽下的水管子拆开看过,里面有很多硬油脂。用开水烫化了就通了。

  4. 热水杀死了产生臭味的细菌(张列弛

    请循其本。食物残渣为什么会臭?是因为产生了不好闻的气体。为什么会产生不好闻的气体?是因为有些成分被分解,产生了对鼻子来说不友好的物质。为什么会那些成分会被分解?是因为残渣里滋生了大量微生物。所以,除臭务尽,重在杀菌。没有什么菌是一盆热水不能解决的,如果有,那就两盆。

那到底什么物质才有除臭作用呢?「二氧化碳疏通管道」的说法比较牵强,就那一点点气体,产生的气压估计不能疏通管道,也许搋子产生的气压更大。「小苏打混白醋的产物没有清洁作用」的说法由化学副教授进行科学性把关,可信。那现在可以排除掉小苏打混白醋了。既然小苏打混白醋之后就没用了,那产生作用的就只有热水了。我觉得列驰从源头思考的方法很合理,可以解释为什么热水可以除臭。

按照解决问题源头的思路,最好还是不要让食物残渣流入下水管。没有堆积的食物残渣,也就不会滋生大量微生物了。厨房洗手盆自带的过滤网的孔比较大,于是我买了个不锈钢水槽过滤网叠在上面。这个的过滤网的孔比较小,更容易过滤掉食物残渣。在网购平台搜索「水槽过滤网」就可以买到了,有不锈钢的和一次性的。

blog 的词源

2021-01-01 00:00:00

Jorn Barger 在 1997 年 12 月发明了 weblog 这个词,weblog 由 web 和 log 组成,指的是用于写日记的个人网站。Peter Merholz 这人比较特别,把 weblog 念成 we blog,简称 blog,然后这个缩略词就流传下来了。blog 除了作名词,亦可做动词,意为写网络日志。而写网络日志的人则称为 blogger。blog 有以下译名:

地区 译名
中国大陆 博客
台湾 部落格、网志
港澳 网志
马新 部落格、网志

blog 本来以文本为主体,但也有以其他内容为主体的 blog。以图片为主体的 blog 叫作 plog(photo blog),例如微信朋友圈。视频形式的 blog 叫作 vlog(video blog),例如【冰冰vlog.001】带大家看看每个冬天我必去的地方。内容字数有限的 blog 叫作 microblog(一条 Twitter 推文只能输入 280 个英文字母或者 140 个中文字),例如 Twitter、Facebook 和新浪微博。现在我来考考你,音频形式的 blog 叫什么呢?提示一下,音频的英文是 audio。揭示答案之前,先来学习一下各种 blog 的念法吧。

正确发音:

名称 发音
blog /blɒɡ/
plog /plɒɡ/
vlog /vlɒɡ/

错误发音:

名称 发音
blog /biːlɒɡ/
plog /piːlɒɡ/
vlog /viːlɒɡ/、/vəlɒɡ/

现在来揭晓答案啦,音频形式的 blog 叫作 alog(audio blog),才怪!其实音频形式的 blog 叫作 podcast,中文译名是播客,例如故事 FMSteve 说。podcast 的发音是:/英式 ˈpɒdkɑːst; 美式 ˈpɑːdkæst/。


参考文献:

做笔译需要什么能力

2020-12-29 00:00:00

疫情刚爆发的时候在家上学,比较清闲,就在网上尝试兼职笔译。在网上搜了一圈笔译网站,感觉有道人工翻译和我译网还可以。有道人工翻译需要投简历,我没什么可写懒得写就去试试我译网了。注册了帐号之后,不能直接接单,得通过测试才行。参加考试这个事也是六七个月之前了,有些细节可能记得不太清楚。

选择考试题目

先选择语言,再选择领域。选择领域领域的时候一下就蒙了,在学校做的翻译练习几乎都是文学作品翻译。作为英语专业学生,真的是除了英语,啥都不会 ε(´סּ︵סּ`)з。

第一次选择了 App 领域,我猜应该是翻译手机应用的界面吧。结果一做题目,完全不是我猜那个意思,总之都是我看不懂的东西。每月只有一次考试机会,我又注册多一个帐号再试一次,这次选了游戏领域(英译中)。题目是 8 道判断对错题和 2 道翻译题,考试时常为 30 分钟,每月只能考一次。

判断题例子 1

判断题例子 2

翻译题例子 1

电子游戏的内容还能理解,但是扑克牌的知识我完全不懂,没辙了。翻译的时候还遇到一个问题:有些英语概念我能理解其涵义,但是却不会用中文表达,或者用很长的中文才能表达原意。我猜这是因为我经常用英英词典,导致有些英文概念我只能用英文解释。总结一下,做笔译除了要懂外语,还要有扎实的母语功底、双语转换能力、了解特定行业的知识。

中文功底不扎实,译出来的中文会很长。双语转换能力差可能会导致想不到简洁的表达。拿「bossy」这个词来举例子,它的英文意思是「always telling other people what to do, in a way that is annoying」1,要是我来翻译只能会直接就说「总爱告诉别人怎么做」。但词典的中文解释为「爱发号施令的,专横的」,比我的简洁多了。我知道中文的「发号施令」和「专横」,但是就是想不到可以用这两个词。双语转换能力差还会导致一个毛病:不必要的中英混杂2。有些概念可以用中文表达,为什么讲中文的时候非要用英文单词呢?比如「你用什么翻译 App3」可以说成「你用什么翻译软件/应用」。比如「我收到了微软的 offer」可以说成「我被微软录取了」。不了解行业知识就会没法翻译,让一个不懂法的人去翻译合同,那肯定是要闹笑话的。

学外语这个问题很好解决,市面上有很多学习教程了,选一个开始学就行。免费的和收费的都有。我自学英文是从看《把你的英语用起来!》开始的,这本书从学音标开始讲英语自学法,十分适合零基础的同学。虽然方法很多,但是比选学习方法更重要的是要真的去学习。了解行业知识也不难解决,学习英文和中文的资料就行。如果想做金融方面的笔译,那就读中文的金融教材和英文的金融教材。想翻译游戏,就玩同一个游戏的英文版和中文版。剩下的提升中文功底和双语转换能力我就不太了解了。


延伸阅读:


  1. bossy 的解释出自朗文当代高级英语辞典(英英.英汉双解)(第五版) ↩︎

  2. 我不是反对所有中英混杂,提及专业术语时用英文没什么关系,但是日常用语完全没必要用英文,用多了中文都退化了。另外,说话满口英文,不见得别人都能听懂。 ↩︎

  3. app 是 application 的缩写,应该念 /æp/而不是念 A-P-P 这三个字母。不过说 A-P-P 很多,A-P-P 已经变成「正确」读法了。/æp/ 是 1 个音节,应用/软件是 2 个音节,A-P-P 是 3 个音节。用 A-P-P 既错误又繁琐(多动一两下嘴巴)。 ↩︎

命令行实例

2020-12-25 00:00:00

本文用于收集我记不住的实用的命令行用法。

docker #

1
2
3
# 启动已停止的容器
docker start f357e2faab77 # restart it in the background
docker attach f357e2faab77 # reattach the terminal & stdin

docker - I lose my data when the container exits - Stack Overflow

下载视频和音频 #

1
2
3
4
5
# 下载视频
you-get https://www.iqiyi.com/v_m72044yl88.html
yt-dlp https://www.youtube.com/watch\?v\=9gfECJHQElo
# 下载 Youtube 歌单(最佳质量、附上序号)
yt-dlp --output '%(playlist_index)s-%(title)s.%(ext)s' -f 'bestaudio' 'https://www.youtube.com/watch?v=T4SimnaiktU&list=PLfAuqOtSFlrAwfk6j3PlSXhssBXzcXREw'

批量转换媒体格式 #

1
2
# 把当前文件夹的所有 .webm 文件转换为 .mp3
for i in *.webm; do ffmpeg -i "$i" "${i%.*}.mp3"; done

去广告看视频 #

1
you-get -p vlc https://www.iqiyi.com/v_m72044yl88.html

转换图片格式 #

先安装 ImageMagick

1
sudo apt install imagemagick

把 jpg 图片转换为 png 图片:

1
convert input.jpg output.png

合成音频文件 #

SoXMp3Wrap 都可以用来合成 MP3。他们的区别如下(测试文件大小为 3.7 MB 和 4.4 MB)1

SoX Mp3Wrap
速度 10.5 秒 瞬间
还原文件
正常播放(Elisa、VLC、mpv) 只有 mpv 可以正常播放,其他播放器播放听起来不太对劲
输出文件/源文件 97.7% 100.006%

SoX #

安装 SoX

1
sudo apt-get install sox libsox-fmt-mp3

input-1.mp3input-2.mp3 合成为 output.mp3

1
sox input-1.mp3 input-2.mp3 output.mp3

注意,要按顺序输入待合成的 MP3,把输出文件写在最后。

Mp3Wrap #

安装:

1
sudo apt install mp3wrap mp3splt

input-1.mp3input-2.mp3 合成为 output.mp3

1
mp3wrap output.mp3 input-1.mp3 input-2.mp3

注意,这次是先写输出文件,再写输入文件。输出的文件会加上后缀 MP3WRAP,所以生成的输出文件名为 output_MP3WRAP.mp3。这个后缀用于提醒用户文件是 Mp3Wrap 生成的,可以用 Mp3splt 还原:

1
2
mp3splt -w output_MP3WRAP.mp3
# -w Warp Mode,用于拆分由 Mp3Wrap 和 AlbumWrap 生成的文件

上述命令会把 output_MP3WRAP.mp3 还原成原来的 input-1.mp3input-2.mp3(不会删除 output_MP3WRAP.mp3)。还原出来的文件和原文件是一模一样的。

拆分音频文件 #

把 MP3 拆分多个文件,每份 30 秒:

1
ffmpeg -i input.mp3 -f segment -segment_time 30 -c copy out%03d.mp3

剪辑视频 #

剪取 input.mp4 06:01.511 至 06:23.841 的内容,并保存为 output.mp4

1
ffmpeg -ss 06:01.511 -to 06:23.841 -i input.mp4 -c copy output.mp4

选项含义:

这些选项一定要按照顺序写,不然剪出来的视频时间不对。

我试过把 -i 先写在前面,也就是这样:

1
ffmpeg -i input.mp4 -ss 06:01.511 -to 06:23.841 -c copy output.mp4

结果输出视频开头的时间总是不对,然后整了两小时都不知道咋回事。遇到 ffmpeg 以前,我都不知道命令行软件的选项位置会影响结果。啊!多么痛的领悟!如果把 -c copy 选项也去掉的话,输出视频又正确了,真是搞不懂啊。

1
ffmpeg -i input.mp4 -ss 06:01.511 -to 06:23.841 output.mp4

视频遮罩 #

在 1080P 的视频底部加上 100 像素高的黑色长方形遮罩。文档:https://ffmpeg.org/ffmpeg-filters.html#drawbox

1
ffmpeg -i input.mp4 -vf "drawbox=x=0:y=(ih-100):w=iw:h=100:color=black:t=fill" output.mp4

输出好看的 PATH 变量 #

如果直接用 echo $PATH 查看 PATH 变量,结果很难看,很难分清楚哪个目录是哪个。

1
2
$ echo $PATH
/home/user/bin:/home/user/.local/bin:/home/user/bin:/home/user/.local/bin:/home/user/bin:/home/user/.local/bin:/home/user/bin:/home/user/.local/bin:/home/user/miniconda3/condabin:/home/user/bin:/home/user/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

tr ':' '\n' <<< "$PATH",结果一目了然。<<< 把右边的 $PATH 传递到左边,tr ':' '\n'$PATH 中的 : 转换为换行符。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ tr ':' '\n' <<< "$PATH"
/home/user/bin
/home/user/.local/bin
/home/user/bin
/home/user/.local/bin
/home/user/bin
/home/user/.local/bin
/home/user/bin
/home/user/.local/bin
/home/user/miniconda3/condabin
/home/user/bin
/home/user/.local/bin
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/snap/bin

参考:shell - Show PATH in a human-readable way - Unix & Linux Stack Exchange

KDE Plasma #

1
2
3
4
# 重启 Plasma
kquitapp5 plasmashell; sleep 2; kstart5 plasmashell
# 重新加载软件列表
kbuildsycoca5

参考:kwin - Can I restart the KDE Plasma Desktop without logging out? - Ask Ubuntu

设置华为笔记本充电阈值(charge threshold) #

1
echo "40 70" | sudo tee /sys/devices/platform/huawei-wmi/charge_control_thresholds

  1. 测试文件为紅蓮華(input-1.mp3,3.7 MB)和 from the edge (input-2.mp3,4.4 MB),播放器为 Elisa、VLC、mpv。 ↩︎

用 BASH 脚本更新各类软件包

2020-12-25 00:00:00

刚用 Ubuntu 的时候,觉得用 apt 安装软件很方便,从此不再需要像用 Windows 一样要去官方下载软件。用久了也发现 Ubuntu 软件仓库有两个问题:软件不是最新版本、缺少某些软件。要解决这些问题,除了添加 PPA 以外,就只能用的其他包管理器作为补充。用多了包管理器,每次更新都要输入好几个命令,太麻烦啦。还是用 BASH 脚本一次性更新各类软件包吧。

先安装待会需要用的软件,apt-fast 用于加速 apt 的下载速度,pipupgrade 用来更新 Python 包,npm-check 用于更新 npm 包。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# install apt-fast
# homepage: https://github.com/ilikenwf/apt-fast

sudo add-apt-repository ppa:apt-fast/stable
sudo apt-get update
sudo apt install apt-fast

# install pipupgrade
# homepage: https://github.com/achillesrasquinha/pipupgrade
pip install pipupgrade

# install npm-check
# homepage: https://github.com/dylang/npm-check
sudo npm install -g npm-check

下面就是更新脚本,将它保存到 PATH 变量包含的目录里,然后授予执行权限就可以直接在命令行运行了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/bin/bash -x

# apt
sudo apt update
sudo apt-fast upgrade -y

# tlmgr
sudo tlmgr update --self --all

# npm
sudo npm-check --global --update--all

# pip
pipupgrade --ignore-error -y

# cran
Rscript -e "update.packages()"

不指定路径运行自己写的软件

2020-12-24 00:00:00

在 Linux 上运行自己写的软件必须指定路径才能运行。举个例子,我写了个 BASH 脚本,把代码保存到 hello_world 文件,放到 ~/Desktop,并赋予它运行权限。

1
2
#!/bin/bash
echo "Hello, World!"

如果我们在终端执行 hello_world 会怎么样呢?

1
2
3
4
$ hello_world
Command 'hello_world' not found, did you mean:
 command 'hello-world' from snap hello-world (6.4)
See 'snap info <snapname>' for additional versions.

完全不行啊。必须在终端输入 ~/Desktop/hello_world 才能运行 hello_world

1
2
$ ~/Desktop/hello_world
Hello, World!

怎么样才能像 mv 一样可以不输入路径就运行呢?要回答这个问题就得先弄清楚为什么 BASH 可以直接执行 mv。原因就是 PATH 变量,它保存了可执行文件的目录。输入 tr ':' '\n' <<< "$PATH" 可以查看 PATH 变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
tr ':' '\n' <<< "$PATH"
/home/user/miniconda3/condabin
/home/user/bin
/home/user/.local/bin
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/snap/bin

当你不指定路径,直接输入一个命令(例如 mv)时,BASH 会从上面的目录中查找是否有这个程序,有就运行,没有就显示 command not found。所以把我们前面写的 hello_world 放入其中一个目录就可以运行了。

1
2
3
$ mv ~/Desktop/hello_world /home/user/.local/bin
$ hello_world
Hello, World!

随便找个目录丢进去也不太好,要删除文件的时候不小心删除了系统自带的软件就糟糕了。最好自己建立一个目录专门储存自己写的软件,一般 ~/bin 用于储存用户自己的软件。那我们可以把自己写的软件存到 ~/bin。设置步骤:

  1. 建立目录

    1
    
    mkdir ~/bin
    
  2. 把目录添加到 PATH 变量

    export PATH="/home/hunter/bin:$PATH" 添加到 ~/.bashrc1(如果用 zsh 就添加到 ~/.zshrc

  3. 打开新的终端窗口

现在把软件放到 ~/bin 之后直接在终端输入就可以运行了。


延伸阅读:


  1. .bashrc 用于初始化 BASH,每次打开 BASH,BASH 都会运行 .bashrc。 ↩︎

键盘使用技巧

2020-12-21 00:00:00

以前用 Windows 的时候用鼠标比较多,一直忽略键盘右边的按键。转到 Linux 之后才发现被我忽略的按键是多么好用。

缩写全称对照表 #

了解按键的全称有利于记忆按键的功能。

缩写 全称 符号
Tab Tabulator / Tabular
Alt Alternate
Ctrl Control
Del Delete
Shift
Backspace
Enter
PgUp PageUp
PgDn PageDown
Ins Insert
PrtSc Print Screen

Backspace、Delete #

Backspace 用于删除光标左边的 1 个字符,Delete 用于删除光标右边的 1 个字符。

Tab、Shift Tab #

Tab 用于缩进内容,Shift Tab 用于取消缩进。

PageUp、PageDown、Home、End #

PageUp 用于将光标或页面前移 1 个屏幕的大小,PageDown 用于讲光标或页面后移 1 个屏幕的大小。Home 在编辑器中的作用是把光标移动到行首,在阅读器中的作用是把页面移动到文档开头。End 在编辑器的作用是把光标移动到行尾,在阅读器中的作用是把页面移动到文档结尾。

Ctrl ←、Ctrl →、Ctrl Backspace、Ctrl Delete #

Ctrl ← 用于把光标左移 1 个单词的长度,Ctrl → 用于把光标右移 1 个单词的长度。Ctrl Backspace 用于删除光标左边的 1 个单词,Ctrl Delete 用于删除光标右边的 1 个单词。

Shift + 移动光标的按键 #

Shift + 移动光标的按键用于选中字符。Shift ←/→ 用于选中 1 个字符,按住 Shift,再按多几次 ←/→ 可以选中多个字符。Shift ↑/↓ 用于选中长度为 1 行的内容。Shift Home/End 用于选中光标至行首或行尾的内容。Shift PageUp/PageDown 用于选中 1 个页面的内容。

Insert、PrtSc #

Insert 用于切换文本输入模式。文本输入有两种,一是覆盖模式,在此模式下新输入的字符会代替光标处的字符;另一个模式是插入模式,在此模式下新输入的字符会插入到光标处,原本在光标后的字符后移。

PrtSc 全称是 Print Screen。在 KDE Plasma 中,PrtSc 用于调用 Spectacle 来截图。在 Windows 中,按下 PrtSc 之后系统会把全屏截图保存到剪切板,在编辑器(如 MS Office)中按下 Ctrl V 就可以粘贴截图。按下 Alt PrtSc 则会截取当前窗口。

延伸阅读 #

Markdown 技巧

2020-12-15 00:00:00

使用 R Markdown #

常用的 GitHub Flavored Markdown 功能很少,熟悉之后可以使用更强大的 R Markdown

R Markdown 的优势:

有序列表只有第 1 个数字有作用 #

下面的有序列表显示效果一样

1
2
3
1. 香蕉
2. 葡萄
3. 菠萝
1
2
3
1. 香蕉
1. 葡萄
1. 菠萝
1
2
3
1. 香蕉
111. 葡萄
10. 菠萝

它们都会显示为

  1. 香蕉
  2. 葡萄
  3. 菠萝

另外,列表数字可以不以 1 为开头。比如

1
2
3
2. 香蕉
2. 葡萄
2. 菠萝

会显示为

  1. 香蕉
  2. 葡萄
  3. 菠萝

在有序列表中使用同一个数字有两个好处:一是不需要自己手动编号、二是使用 Git 管理文件时变动更少。

图片 + 超链接 #

超链接的语法是[链接文本](链接网址),图片的语法是![图片标题](图片网址)。把图片当成超链接的文本,放到超链接的语句中就可以生成带有超链接的图片,也就是[![图片标题](图片网址)](链接网址)

这是 GitHub 的吉祥物 Octocat

![octocat](https://octodex.github.com/images/kimonotocat.png)

octocat

这是 Github 的链接

[GitHub](https://github.com/)

把它们拼凑起来就可以制作带有 GitHub 链接的 Octocat

[![octocat](https://octodex.github.com/images/kimonotocat.png)](https://github.com/)

octocat

如果点击上面的图片不会跳转到 GitHub,可能是因为本站使用了 PhotoSwipe 这样的相册插件。你可以把代码复制到 Markdown 编辑器试试看。

使用包含 ``` 的代码块 #

如果代码块里包含 N 个 `,在需要代码块前后使用 N+1 个 ` 把它包裹起来,不然就会显示错误。下面内容会显示为两个空代码块夹着 print("Hello, World!")

1
2
3
4
5
```
```
print("Hello, World!")
```
```

上面代码的正确写法应该是

1
2
3
4
5
````
```
print("Hello, World!")
```
````

它会显示为

1
2
3
```
print("Hello, World!")
```

用空行隔开不同元素 #

用空行隔开不同元素可以消除歧义,避免错误的显示效果。如果不用空行的话,可能会出现意料之外的显示效果。如果我用下面的写法列举我喜欢吃的食物,猜猜 Markdown 编辑器会如何显示。

1
2
3
4
5
6
7
8
水果:
1. 椰子
2. 香蕉
3. 菠萝
烧腊:
1. 叉烧
2. 烧鸭
3. 白切鸡

点击这里查看不同解析器的结果,有三种显示效果。

第 1 种显示效果
第 2 种显示效果
第 3 种显示效果

现在你应该知道不加空行的严重后果了。我们再来看看那个错误示范。

1
2
3
4
5
6
7
8
水果:
1. 椰子
2. 香蕉
3. 菠萝
烧腊:
1. 叉烧
2. 烧鸭
3. 白切鸡

它应该包含了 4 个元素:依次为段落、有序列表、段落、有序列表。在元素间加上空行显示就没问题了。点击这里查看效果,这次所有解析结果都正常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
水果:

1. 椰子
2. 香蕉
3. 菠萝

烧腊:

1. 叉烧
2. 烧鸭
3. 白切鸡

Typora 标题自动标号 #

Typora 自动标号的截图

给 Typora 文章、目录和大纲的标题编号:

为什么 Markdown 的标题叫 ATX 和 Setext #

Markdown 有两种标题写法,一种是 ATX 标题(ATX headings):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# foo

## foo

### foo

#### foo

##### foo

###### foo

另一种是 Setext 标题(Setext Headings):

1
2
3
4
5
Title 
=====

Subhead 
-------

有很长一段时间,我都没法区分哪种标题是 ATX,哪种是 Setext。我特地用词典查 ATX 和 Setext,根本查不到释义。最近在谷歌和维基百科的帮助下,才发现原来这两个标题写法不是 Markdown 原创的,是出自 atx(the true structured text format)Setext (Structure Enhanced Text)

避免使用其他标记语言 #

Markdown 的格式有限,要使用 Markdown 不支持格式,就只能混用其他标记语言。但是混用标记语言可能会让你陷入追求格式的泥潭。举个例子,Markdown 不支持右对齐,要对文字右对齐只能使用 HTML 代码,比如 <div style="text-align: right">右对齐的文字</div>。这么做有两个问题,一是要输入长长的代码,二是转换成 HTML 以外的文档(如 PDF)右对齐代码就失效了。下面这个 Markdown 文件转换成 PDF 的话,右对齐会失效。

1
2
3
4
5
> 春有百花秋有月,夏有凉风冬有雪。
>
> 若无闲事挂心头,便是人间好时节。
>
> <div style="text-align: right">——无门慧开</div>

春有百花秋有月,夏有凉风冬有雪。

若无闲事挂心头,便是人间好时节。

——无门慧开

其实在 R Markdown 中有让右对齐代码同时对 HTML 与 PDF 生效的方法(参考资料:9.6 Custom blocks (*) | R Markdown Cookbook)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
---
output:
 pdf_document: default
 html_document: default
---

<style>
.flushright {
 text-align: right;
}
</style>

> If your mind isn’t clouded by unnecessary things,
>
> This is the best season of your life.
>
> ::: {.flushright data-latex=""}
> --- Wu-Men
> :::

在 R Markdown 中加载 LaTeX 宏包 #

extra_dependencies #

1
2
3
4
5
---
output: 
 pdf_document:
 extra_dependencies: ["bbm", "threeparttable"]
---
1
2
3
4
5
6
7
8
---
output: 
 pdf_document:
 extra_dependencies:
 caption: ["labelfont={bf}"]
 hyperref: ["unicode=true", "breaklinks=true"]
 lmodern: null
---

header-includes #

1
2
3
4
5
6
---
header-includes:
 - \usepackage{bbm}
output:
 pdf_document
---

in-header #

1
2
3
4
5
6
---
output:
 pdf_document:
 includes:
 in_header: mystyles.sty
---
1
2
3
4
5
% mystyles.sty
\usepackage{bbm}
\usepackage{threeparttable}
\usepackage{booktabs}
\usepackage{expex}

参考资料 #

trash-cli 中文手册

2020-12-12 00:00:00

rm 是常用的 BASH 命令,但是误删文件后比较难恢复。用 trash-cli 更安全,它只是把文件移动到回收站,这样误删文件也很容易恢复。我觉得这个软件挺好用的,就把官方文档翻译成中文了。

trash-cli——FreeDesktop.org 回收站的命令行界面 #

Donate

trash-cli 用于移动文件到回收站,同时会记录文件的原地址、删除日期和权限。trash-cli 和 KDE、GNOME、XFCE 使用同一个回收站,你可以在命令行或脚本运行 trash-cli。

trash-cli 提供以下命令:

trash-put 把文件或目录移动到回收站
trash-empty 清空回收站
trash-list 列出回收站文件
trash-restore 恢复回收站文件
trash-rm 删除回收站文件

用法 #

移动文件到回收站:

$ trash-put

列出回收站文件:

$ trash-list
2008-06-01 10:30:48 /home/andrea/bar
2008-06-02 21:50:41 /home/andrea/bar
2008-06-23 21:50:49 /home/andrea/foo

搜索回收站文件:

$ trash-list | grep foo
2007-08-30 12:36:00 /home/andrea/foo
2007-08-30 12:39:41 /home/andrea/foo

恢复回收站文件:

$ trash-restore
0 2007-08-30 12:36:00 /home/andrea/foo
1 2007-08-30 12:39:41 /home/andrea/bar
2 2007-08-30 12:39:41 /home/andrea/bar2
3 2007-08-30 12:39:41 /home/andrea/foo2
4 2007-08-30 12:39:41 /home/andrea/foo
What file to restore [0..4]: 4
$ ls foo
foo

删除所有回收站文件:

$ trash-empty

删除回收站中 n 天前被回收的文件:

$ trash-empty <days>

示例:

$ date
Tue Feb 19 20:26:52 CET 2008
$ trash-list
2008-02-19 20:11:34 /home/einar/today
2008-02-18 20:11:34 /home/einar/yesterday
2008-02-10 20:11:34 /home/einar/last_week
$ trash-empty 7
$ trash-list
2008-02-19 20:11:34 /home/einar/today
2008-02-18 20:11:34 /home/einar/yesterday
$ trash-empty 1
$ trash-list
2008-02-19 20:11:34 /home/einar/today

只删除符合某种模式的文件:

$ trash-rm \*.o

注意:要用双引号圈住模式来避免 shell 拓展。

常见问题 #

如何创建顶级 .Trash 目录? #

步骤:

sudo mkdir --parent /.Trash
sudo chmod a+rw /.Trash
sudo chmod +t /.Trash

我能把 rm 的别名设置为 trash-put 吗? #

可以,但不应该这样做。以前我觉得这是个好主意,但现在我不觉得。

虽然 trash-put 的界面看起来与 rm 兼容,但它们有不同的语法,这些差异会导致一些问题。比如,用 rm 删除目录时需要 -Rtrash-put 则不需要。

但有时候我忘记用 trash-put 了,真的不能给 rm 设置别名吗? #

你可以给 rm 设置一个别名来提醒你不要使用它:

alias rm='echo "This is not the command you are looking for."; false'

如果你真的要用 rm,那就在 rm 前加上斜杠来取消别名:

\rm file-without-hope

注意,Bash 别名是有在交互式界面才有效,所以使用这个别名不会影响使用 rm 的脚本。

被移动到回收站的文件在哪? #

从 home 分区移动到回收站的文件在这:

~/.local/share/Trash/

安装 #

简单方法 #

要求:

  • Python 3 (Python 2.7 也可以)
  • pip (在 Debian 上用 apt-get install python-pip 来安装 pip)

安装命令:

pip install trash-cli

源码安装 #

为所有用户安装:

git clone https://github.com/andreafrancia/trash-cli.git
cd trash-cli
sudo pip install .

为当前用户安装:

git clone https://github.com/andreafrancia/trash-cli.git
cd trash-cli
pip install .

为当前用户安装后你可能需要把以下代码添加到 .bashrc:

export PATH=~/.local/bin:"$PATH"

卸载命令:

pip uninstall trash-cli

用包管理器安装 #

Debian/Ubuntu (apt):

sudo apt install trash-cli

反馈与 Bug 报告 #

如果你发现了 bug,请在这里报告:

https://github.com/andreafrancia/trash-cli/issues

你也可以给我发邮件 [email protected]。我的推特帐号是 @andreafrancia。

开发 #

环境设置:

virtualenv env --no-site-packages
source env/bin/activate
pip install -r requirements-dev.txt

运行测试:

nosetests unit_tests # 只运行单元测试
nosetests integration_tests # 运行所有集成测试
nosetests -A 'not stress_test' # 运行压力测试以外的测试
nosetests # 运行所有测试

发布前检测安装进程:

python check_release_installation.py

单元测试性能分析:

pip install gprof2dot
nosetests --with-profile --profile-stats-file stats.pf --profile-restrict=unit_tests unit_tests
gprof2dot -w -f pstats stats.pf | dot -Tsvg >| stats.svg
open stats.svg

免费使用知网

2020-12-10 00:00:00

市级和省级图书馆的网站都有知网资源,注册帐号之后登陆就可以免费使用。以广州图书馆为例,先登陆帐号,点击首页 -> 资源 -> 数字资源导航,点击打开中国知网就可以免费下载论文了。

广州图书馆数字资源导航截图
广州图书馆知网截图

吐槽一下中国知网,博硕士论文只有 CAJ 格式,英文版知网才能下载 PDF 格式的博硕士论文。中国知网宁愿方便外国人也不愿方便方便国人。看看 CAJViewer for Linux 的显示效果,模糊至极,简直是不堪入目。

CAJViewer for Linux 模糊的显示效果

既然如此,咱们就只好装作洋大人,用英文版知网下载清晰的 PDF 论文。以下面这篇论文为例,演示下载步骤。

一篇论文

首先在首页选择 ENGLISH:

切换语言版本

然后直接搜索中文标题:

英文知网

点击 Download the Full-text(PDF) 就可以下载了。


部分图书馆会限制外地人注册,比如注册广州图书馆需要广州身份证、广州社保卡或者长期居住证明材料,不在广州生活的人就很难注册。注册的时候要看清楚条款,免得白忙活一趟。最省事的方法就是用注册浙江图书馆,外地人也能注册,在支付宝直接授权就能注册。

操作步骤:在支付宝关注浙江图书馆生活号,浙江图书馆生活号 -> 服务 -> 服务大厅 -> 新用户注册 -> 确定授权


参考资料:在家里如何免费使用中国知网? - 知乎

增强计算机安全性的技巧

2020-12-08 00:00:00

关闭 BIOS 的 USB Boot 和 PXE Boot 选项 #

开启了 USB 启动后,用户可以绕过电脑里的操作系统,直接启动 U 盘里面的系统。PXE Boot 则是通过网络接口启动计算机。这样不需要知道原系统密码也可以读取硬盘里的文件了。我觉得这两个功能还挺危险的,平时我都会关闭它们,装系统的时候才用 USB Boot,用完就关闭。除此之外,还要设置 BIOS 密码,不然别人拿到你的电脑一样可以开启这两个选项。

开启 BIOS 的 Power on Password 选项 #

这个技巧是针对 Linux 的。GRUB 的 recovery mode 可以让用户不需要密码的情况下使用 root 账户。这个功能实在是太强力了,输入几条命令就可以修改用户密码。解决这个问题的办法就是:先设置 BIOS 密码,再设置启动电脑时询问 BIOS 密码。

使用全盘加密 #

设置了 BIOS 并不意味着可以高枕无忧。别人拿到你的电脑虽然不能直接在里面启动系统,但是可以把硬盘拆下来放到另一台电脑中读取文件。但是也不要担心,办法还是有的。使用 BitLocker 这样的全盘加密技术,别人拆下你的硬盘也无法读取你的文件。

保持良好习惯 #

除了前面几个技巧,保持良好习惯也很重要。比如:到官网下载软件、不要访问证书错误的网站。

延伸阅读 #

GitHub/jsDelivr 图床教程

2020-12-05 00:00:00

因为在博客的源代码仓库加图片(二进制文件)会导致仓库变大,所以我就把图片放到另一个仓库,直接使用链接来引用图片。

  1. 新建公共仓库1

    勾选 Add a README file,这样待会就可以直接使用了,点击 Create repository

  2. 克隆仓库

    1
    
    git clone [email protected]:your-username/repo-name.git
    
  3. 添加一些图片到仓库

  4. 上传图片

    1
    2
    3
    4
    
    #!/usr/bin/env sh
    git add --all
    git commit -m "update"
    git push
    
  5. 获取链接

    我们可以使用 GitHub 的链接或者 jsDelivr CDN,前者在中国被封禁,后者还能用。jsDelivr 会缓存文件,如果更新了文件,需要清除缓存

    • GitHub 链接规则:https://raw.githubusercontent.com/${用户名}/${仓库名}/${分支名}/${路径}
    • jsDelivr 链接规则:https://cdn.jsdelivr.net/gh/${用户名}/${仓库名}@${分支名}/${路径}
    • jsDelivr 清除缓存链接规则:https://purge.jsdelivr.net/gh/${用户名}/${仓库名}@${分支名}/${路径}

    举个例子,https://github.com/CyrusYip/blog-static/blob/main/images/2020-11-06_fcitx5-unicode.gif 这个文件对应的链接为:

    把链接放到 Markdown 文件里就可以使用了。比如:![](https://raw.githubusercontent.com/CyrusYip/blog-static/main/images/2020-11-06_fcitx5-unicode.gif)

  6. 删除文件请参考《完全删除 Git 仓库的文件》

自己手动拼凑链接太费劲,我写了个 Bash 程序来获取文件链接并将其写入 X11 剪贴板(使用 fzf 模糊搜索,找文件很快)。

搜索文件
获取链接
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env bash

# Get raw file links from current GitHub repository
# Dependencies: fzf and xclip
# This file should be placed at the root of a repository
# Guide (Chinese): https://cyrusyip.org/zh-cn/post/2020/12/05/host-images-on-github/

username="CyrusYip"
repo_name="blog-static"
branch_name="main"

get_url() {
 path=$(fzf)
 github="https://raw.githubusercontent.com/${username}/${repo_name}/${branch_name}/${path}"
 jsdelivr="https://cdn.jsdelivr.net/gh/${username}/${repo_name}@${branch_name}/${path}"
 jsdelivr_purge="https://purge.jsdelivr.net/gh/${username}/${repo_name}@${branch_name}/${path}"

 # copy all links so I can get them later
 copy_count=0
 echo "$github" | xclip -selection clipboard && copy_count=$((copy_count + 1))
 echo "$jsdelivr_purge" | xclip -selection clipboard && copy_count=$((copy_count + 1))
 echo "$jsdelivr" | xclip -selection clipboard && copy_count=$((copy_count + 1))
 [ $copy_count -eq 3 ] && echo "Links were successively copied."

 echo -e "\nLinks for the file:"
 echo " $github"
 echo " $jsdelivr"
 echo " $jsdelivr_purge"
 echo -e "\nPress Ctrl-C to exit, Enter to contine..."
}

while true; do
 get_url
 read -r
done

建议遵守 GitHub 和 jsDelivr 的使用条款,以免服务终止。


  1. 私人仓库需要令牌(token)访问图片,但是令牌会自动刷新,刷新后就无法使用之前的网址。想用私人仓库可以考虑使用 Cloudflare Pages 这样的 Jamstack 服务部署图片到子域名(例如 images.cyrusyip.org)。 ↩︎

在 Windows 下修复 U 盘错误

2020-12-01 00:00:00

有时在 Windows 插入 U 盘会提示有错误需要修复,我一直都是忽略的,接着用 U 盘。今天用 U 盘的时候没办法打开一个文件夹,于是就乖乖地修复了 U 盘,修复完就好了。原来 Windows 的 U 盘修复那么有用。

修复方法:在文件资源管理器右击 U 盘,点击 Properties -> Tools -> Error checking -> Check

操作截图

大学咸鱼指北

2020-11-28 00:00:00

工地大学 #

读高中的时候,老师常说:「现在加把劲,上了大学就轻松了。」上大学之后马上就发现老师说的都是屁话啊,上大学也挺累的啊。我就读的工地大学1居然有早自习,而且是要 7 点半到课室。为什么要那么早起床呢,学生不会自己安排生活吗,就差那半小时自习?经历了 12 年的「尊师教育」,刚上大学的时候还是比较乖的,学校怎么说我就怎么做。

上大学之后的第一件事就是军训。因为男生要剪寸头,然后我就和长发说再见了,真的是又生气又无奈。军训说是可以强身健体,我感觉完全是「驯服教育」,让大一新生好好听话。我那时候脚痛,在后勤部休息,也没躲过辅导员的驯服教育。那会的辅导员真该死,居然叫脚痛的我去搬桶装水。辅导员官威很重,看不得伤员休闲的样子,非要过来让我们站军姿和坐军姿。

经历完军训,开始正是上课之后又是一堆作业要做。还有每日打卡的学习任务,做完之后要在微信群报告,没做就一直催促。没多久就遇到了工地大学臭名昭著的素质培养计划。素培计划要求学生通过参加活动积累够 160 个学分,不然不能毕业。听一个讲座才 0.5 学分,只听讲座岂不是 听 320 个才行?好家伙,同学一听到不够素培学分不能毕业,都争先恐后地报名参加活动。每个学校举行的活动都坐无虚席,有不少是玩手机的。我猜真正想参加活动的同学估计还抢不到名额,去听讲座的反而是换个地方玩手机的,真是荒诞。

上课没意思,作业又多,杂事又多。工地大学的生活实在是太无聊了。于是我就打算做一条咸鱼,用尽量少的努力混到毕业。考试 60 分万岁,能不做的事情都不做,剩下的时候打打游戏,读读书。

咸鱼指北 #

既然对专业不感兴趣,那就做一条咸鱼吧。本文对咸鱼的定义是「尽量省事地完成学业混个毕业证,剩下的时间用于做自己喜欢的事情」。下面的内容是建立在一个前提之上的:上大学只拿毕业证,不做其他有求于学校的事情(比如申请奖学金、申请成为党员)。只要不有求于学校,就可以忽略它的要求了。

首先,做咸鱼呢,只要完成毕业要求就好了,其他的事情一概不管。最基本的就是学分要求,一定要保证每门课都修到学分。每门课的第一节课好好听考核要求,关注以下方面:考核组成、能否补考。平时上课可以摸下鱼,但是最后考试周最好加把劲复习一次搞定。如果挂科了也别慌,继续复习补考便是。不能补考直接重修的课就很麻烦了,所以要注意上的课能不能补考。虽然说 60 分万岁,但是不小心考到 59 就很悲惨了。

不要参加社团和部门,这样可以剩下很多时间。当然了,如果社团就是你的兴趣所在的话,参加也无妨。如果参加之后发现不适合就感觉退出。要是中途退出被要求写超长的退出申请的话,别怕,直接退群不参加活动便是。社团并不能拿你怎么着。

有时候做咸鱼是很艰难的,学校会逼着你去做一些没用的事情。大学想逼学生去做事,无非就是把一件事和毕业挂钩,通过对学生施加恐惧与焦虑,让学生去做事情。比如「不做志愿,没有志愿学时,就不能毕业」。这些话,有些是真的,有些是假的。获取学位证的要求应该参考学生手册,而不是辅导员的屁话。想要不被吓到,一定要保持批判性思维,敢于怀疑听到的话语。

比如前面提到的早自习。学校只要求必须去早读是没用的,一听就知道没什么后果对吧。所以工地大学的辅导员会恐吓学生,说 3 次不去早自习就等于 1 次旷课,还会被记名。部分新生听到可能就很慌,心想:我经常不去早自习 -> 会旷课好多次 -> 旷课多了就挂科了。一旦这么想就慌张了,人不喜欢焦虑的状态,于是就通过参加早自习来消除这份焦虑。

停下来好好看看辅导员的话:

3 次不去早自习就等于 1 次旷课。

假设它是真的,如果你真的有 3 次没去,会怎么样呢?会旷课 1 次,可是你有那么多门课,要给你记哪门课的旷课呢?是不是越想越奇怪,上面那句话完全没有可行性嘛。其实对于学校的安排,应该考虑它的真实性和后果,而不是被恐惧左右你的选择。就继续分析早自习的例子吧,辅导员说不去早自习就等于旷课。首先,辅导员没有这样的权利,其次它也不具备可行性,所以这句话是假的。

下面再分析一下前面提到的素质培养计划(简称素培):

学生必须积累 160 个素培学分,方可毕业。

往届的学生也是要搞这个的,就当素培是真的吧。获取素培学分最基本的方法就是听讲座,但是才 0.5 个学分。参加活动与组织活动分数更高,但机会有限,还费时费力。根据我的回忆,我们学院每学期大概有 20 场讲座,每场讲座需要 30 位观众,然后学生在学校待 7 个学期。讲座可以提供 20×30=4200 个学分。我们学院有 450 人。平分一下每个人才 4200/450≈9.3 个学分,完全不够用嘛。

很多同学也觉得这个素培计划过于苛刻,但又因为害怕毕不了业就争先恐后地报名活动,然后就换个地方玩手机。在这里先引入一条「公理」:

大学是营利机构,不会做损坏自己利益的事情。

苛刻的素培计划完全是违背公理的,如果让 99% 的学生都不能毕业,那谁还会报考工地大学呢,没人报考工地大学,工地大学不就倒闭了吗?但是根据往届学生的陈述,这又确实是毕业的要求之一。难不成素培计划是假的。我只能说它是半真半假,确实需要素培学分才能毕业,但是考虑到大多数人的状况,学校会在某个时候降低要求。

果不其然,在大四的时候,工地大学把素培学分要求降低到了 130。平时有些班级活动有比较高的素培学分,所以多数同学不用怎么参加素培活动都够分了。估计不少同学知道这个消息之后,内心骂了 100 遍工地大学。我也觉得工地大学真他妈奸诈,总是利用学生的单纯,通过恐吓来逼迫学生参加活动。像我这种老咸鱼,慌张了一会就会觉得素培计划很荒诞,然后该干嘛干嘛。可是那些最慌张的同学,就这样被骗了三四年,参加活动浪费那么多时间,真替他们感到不值。

工地大学辅导员惯用的恐吓词语还有「必须」和「后果自负」。识破辅导员诡计的方法就是想想不服从安排的后果。以下面的通知为例:

四级没考过的同学必须参加明天的四级备考讲座,否则后果自负!

这句话没有提及不参加的后果,所以「后果自负」是废话,不过有一定的恐吓效果。「必须」其实也是废话啊,辅导员又不能拿刀放到学生的脖子上逼着他去吧。

其实写这份指北,不是想鼓励大家做只是混毕业证的学生,而是给机会自己探索一下其他可能。

咸鱼生活 #

选择做咸鱼之后,会有比较多的空闲时间。空闲起来没事做,人会迷茫。买了 Nintendo Switch 打游戏,打游戏挺快乐的,但是玩多了又觉得空虚。想学计算机,犹豫了很久,终于在大三尝试了学 Python。先学了Python Crash Course, 2nd Edition,还发现了 Open Source Society University 的计算机科学课程,感觉自学计算机科学确实可行。学 Python 的时候会根据自己的需求写一些程序给自己用,那种创作的快乐真是让我着迷。

之后就是对什么感兴趣就学什么,学了杂七杂八的东西:Markdown、Git、Github、HTML、LaTeX、Rmarkdown。还给自己注册了个域名,用 Blogdown 写日志。学计算机之后,我再也没玩过 Switch,因为觉得学计算机技术太有意思了。Switch 应该都有 10 个月没开机的,居然毫不费力就不打游戏了,真不可思议。

学计算机的过程中,我发现有些简洁又强大的技术,比如 Markdown 和正则表达式。我很喜欢 Markdown 的简洁,内容与样式分离的写作方法也让我受益匪浅,治好了改格式的强迫症。用 Markdown 写作简直是种享受。Rmarkdown 就更强大了,可以用来写书、写幻灯片、写博客。Markdown 也不难学, CommonMark 的语法 10 分钟就能学完。我大学不是计算机专业的,学了这些技术,觉得十分有用。只有学计算机的人用这些技术太可惜啦,希望有一天可以在普通人中推广 Markdown、Rmarkdown和正则表达式。

我读的计算机资料大多是英文的,真的很感谢迷茫时努力的自己,不然我读英文肯定很吃力。我大学读的是英语专业,不过我实在是对文学和语言学不感兴趣,一度觉得上专业课十分痛苦。在那些迷茫的日子,虽然我经常不听专业课,但也在认真学英文,读了一些英文原版书,积累了不少词汇。或许迷茫的时候做好当下的事情也是一个不错的策略,如果我没有读那些原版书增加词汇量,就没机会读英文的资料了。感谢努力的自己。


  1. 我就读的大学会在开学期间搞装修搞建设,一点都不尊重学生。挖掘机地面挖得坑坑洼洼,有时候得绕路。感觉自己就在工地上大学,所以把这所大学称为「工地大学」。 ↩︎

正则表达式实例

2020-11-26 00:00:00

2024 年 3 月 31 日更新:像有序列表和无序列表互换这样比较简单的问题,现在我都用 Vim 处理,不想费脑筋想正则了。


本文记录了我使用过的正则表达式,多数用于处理 Markdown 文档。所有正则表达式均在 VSCode 测试。

选取多段文字 #

有时候我要选取很多段文字,比如某本书的一个章节。以前我选取多段文字都是这样的:

  1. 把光标定位到开头
  2. 按下 Shift
  3. 滚动文档到结尾
  4. 点击一下结尾

有时候这个招数不太好使,滚动文档的时候也不知道有没有超过了结尾。还是用正则表达式好,不用担心错过了结尾。

以下面的文档作例子,截取第一章的内容:

1
2
3
4
5
6
7
8
9
# 第一章

假设这里有好长的一段话。

假设这里又有好长的一段话。

# 第二章

又来一段话。

也就是:

1
2
3
假设这里有好长的一段话。

假设这里又有好长的一段话。

表达式:

1
(?<=# 第一章\n{2})(.|\n)+(?=\n{2}# 第二章)

(?<=) 为正后发断言(Positive Lookbehind),匹配它后面的内容。(?<=# 第一章\n{2}) 的意思是匹配第一章加两个换行之后的内容。

(?=) 后正先行断言(Positive Lookahead),匹配它前面的内容。(?=\n{2}# 第二章)的意思就是匹配两个换行加第二章

我们真正需要的文本包括了文字和换行,所以用 (.|\n)+ 来匹配。

合并与拆分文档 #

合并文档 #

假如我有两门课的笔记,需要把它们合并到一块。

把这两份笔记:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
---
title: 算术
---

# 加法

加法是基本算术运算。简单来说,加法将两个数字结合,成为一个数字,称之为“和”。

# 减法

减法是加法的逆运算。减法是求出两个数(被减数和减数)的差。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
title: 几何
---

# 点

在欧几里得几何中,点是空间中只有位置,没有大小的图形。

# 线

欧几里得把线形容成“在点之间均匀铺着”的“没有宽度的长度”。

# 面

二维,光滑且无限延展的平层构成了平面。

合并为(title: 数学 这一部分需要手动输入):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---
title: 数学
---

# 算术

## 加法

加法是基本算术运算。简单来说,加法将两个数字结合,成为一个数字,称之为“和”。

## 减法

减法是加法的逆运算。减法是求出两个数(被减数和减数)的差。

## 点

在欧几里得几何中,点是空间中只有位置,没有大小的图形。

# 几何

## 线

欧几里得把线形容成“在点之间均匀铺着”的“没有宽度的长度”。

## 面

二维,光滑且无限延展的平层构成了平面。

解决思路:

  1. 内容标题等级 + 1
  2. 把文档标题变成一级标题。

以算术笔记为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
---
title: 算术
---

# 加法

加法是基本算术运算。简单来说,加法将两个数字结合,成为一个数字,称之为“和”。

# 减法

减法是加法的逆运算。减法是求出两个数(被减数和减数)的差。
  1. 内容标题等级 + 1

    搜索 ^#,替换为 $0#,其实也就是给标题加多个 #

  2. 把文档标题变成一级标题

    搜索 ---\ntitle: (.+)\n---,替换为 # $1

处理完之后算术笔记就变成这样了:

1
2
3
4
5
6
7
8
9
# 算术

## 加法

加法是基本算术运算。简单来说,加法将两个数字结合,成为一个数字,称之为“和”。

## 减法

减法是加法的逆运算。减法是求出两个数(被减数和减数)的差。

用同样的方法处理几何笔记:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 几何

## 点

在欧几里得几何中,点是空间中只有位置,没有大小的图形。

## 线

欧几里得把线形容成“在点之间均匀铺着”的“没有宽度的长度”。

## 二维,光滑且无限延展的平层构成了平面。

最后新建一个文档,手动输入 title: 数学,再把两份处理好的文档复制进去就可以了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---
title: 数学
---

# 算术

## 加法

加法是基本算术运算。简单来说,加法将两个数字结合,成为一个数字,称之为“和”。

## 减法

减法是加法的逆运算。减法是求出两个数(被减数和减数)的差。

## 点

在欧几里得几何中,点是空间中只有位置,没有大小的图形。

# 几何

## 线

欧几里得把线形容成“在点之间均匀铺着”的“没有宽度的长度”。

## 面

二维,光滑且无限延展的平层构成了平面。

拆分文档 #

我们用上个教程合并的文档为例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---
title: 数学
---

# 算术

## 加法

加法是基本算术运算。简单来说,加法将两个数字结合,成为一个数字,称之为“和”。

## 减法

减法是加法的逆运算。减法是求出两个数(被减数和减数)的差。

## 点

在欧几里得几何中,点是空间中只有位置,没有大小的图形。

# 几何

## 线

欧几里得把线形容成“在点之间均匀铺着”的“没有宽度的长度”。

## 面

二维,光滑且无限延展的平层构成了平面。

将其拆分为两份文档:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
---
title: 算术
---

# 加法

加法是基本算术运算。简单来说,加法将两个数字结合,成为一个数字,称之为“和”。

# 减法

减法是加法的逆运算。减法是求出两个数(被减数和减数)的差。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
title: 几何
---

# 点

在欧几里得几何中,点是空间中只有位置,没有大小的图形。

# 线

欧几里得把线形容成“在点之间均匀铺着”的“没有宽度的长度”。

# 面

二维,光滑且无限延展的平层构成了平面。

解决思路:

  1. 删除文档标题
  2. 把一级标题变成文档标题
  3. 内容标题等级 - 1

具体步骤:

  1. 删除文档标题

    搜索 ---\ntitle:.+\n---\n\n,替换为 空字符串

  2. 把一级标题变成文档标题

    搜索 ^# (.+) ,替换为 ---\ntitle: $1\n---

  3. 内容标题 - 1

    搜索 ^#,替换为 空字符串

结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---
title: 算术
---

# 加法

加法是基本算术运算。简单来说,加法将两个数字结合,成为一个数字,称之为“和”。

# 减法

减法是加法的逆运算。减法是求出两个数(被减数和减数)的差。

# 点

在欧几里得几何中,点是空间中只有位置,没有大小的图形。

---
title: 几何
---

# 线

欧几里得把线形容成“在点之间均匀铺着”的“没有宽度的长度”。

# 面

二维,光滑且无限延展的平层构成了平面。

最后把内容复制到两个空文档就可以了。

有序列表与无序列表互换 #

虽然用正则表达式可以转换列表,其实我觉得 Typora 的列表转换功能更方便,点一下按钮就可以了。

有序列表转为无序列表 #

把下面的有序列表转换为无序列表:

1
2
3
4
5
水果:

1. 香蕉
2. 椰子
3. 葡萄

搜索 \d+\.(?= ),替换为 -

1
2
3
4
5
水果:

- 香蕉
- 椰子
- 葡萄

无序列表转为有序列表 #

有序列表的数字不需要递增,实际上是从第一个数字开始递增的。

比如下面这几个列表显示效果是一样的:

1
2
3
4
5
水果:

11. 香蕉
12. 椰子
13. 葡萄
1
2
3
4
5
水果:

11. 香蕉
1. 椰子
1. 葡萄
1
2
3
4
5
水果:

11. 香蕉
11. 椰子
11. 葡萄

都会显示为:

水果:

  1. 香蕉
  2. 椰子
  3. 葡萄

把下面的无序列表转换为从 2 数起的有序列表:

1
2
3
4
5
水果:

- 香蕉
- 椰子
- 葡萄

搜索 -(?= ),转换为 2.

1
2
3
4
5
水果:

2. 香蕉
2. 椰子
2. 葡萄

删除与增加空行 #

删除多余空行 #

在 Markdown 中使用多个空行不会增加段落间的空白,下面两份文档显示效果是一样的:

1
2
3
4
5
6
7
# 加法

加法是基本算术运算。

# 减法

减法是加法的逆运算。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 加法

加法是基本算术运算。



# 减法



减法是加法的逆运算。

因此,可以删去多余的空行。以上面的文档为例,演示删除多余的空行。

搜索 \n{2,},替换为 \n\n

增加空行 #

有些纯文本文档用一个换行符来分段,增加空行可以快速将其转换为 Markdown 文档。以下面的文档为例子:

1
2
3
4
加法
加法是基本算术运算。
减法
减法是加法的逆运算。

搜索 \n,替换为 \n\n

1
2
3
4
5
6
7
加法

加法是基本算术运算。

减法

减法是加法的逆运算。

删除知网导出文献的编号 #

一般写论文都是用 Word 自动编号的,知网文献的编号没什么用,所以就先把编号删掉。

1
2
[1]黄城烟.参考文献中标准著录格式的新规定及其影响——以GB/T7714―2015为例[J].中国科技期刊研究,2016,27(03):243-248.
[2]韩云波,蒋登科.参考文献国家标准GB/T7714—2015的修订特色与细则商榷[J].西南大学学报(社会科学版),2015,41(06):157-167.

在 LibreOffice Writer 按下 Ctrl H,搜索 \[\d+\],替换为空字符串。在 MS Word 里面应该搜索 \[[0-9]@\]。替换后之后文献前的数字就没有了:

1
2
黄城烟.参考文献中标准著录格式的新规定及其影响——以GB/T7714―2015为例[J].中国科技期刊研究,2016,27(03):243-248.
韩云波,蒋登科.参考文献国家标准GB/T7714—2015的修订特色与细则商榷[J].西南大学学报(社会科学版),2015,41(06):157-167.

在扫描版 PDF 中查找内容 #

在扫描版 PDF 中查找内容

中文没有斜体

2020-11-26 00:00:00

在西文中,意大利体(italic type)是手写风格的字体,常用作强调内容。西文中还有另一种倾斜的字体是伪斜体(oblique type),它的形状和常规字体一样,只是向右倾斜了。伪斜体和意大利体一样用作强调。下图为常规字体(罗马体)、意大利体与伪斜体(Garamond Roman):

罗马体(Roman type)和意大利体(Italic type)示例
伪斜体(oblique type)示例

中文里没有「斜体」(意大利体、伪斜体)的概念,所以千万不要照搬西文的习惯,用斜体来强调中文内容。斜体中文既错误又丑陋。可以用粗体或楷体来强调中文内容,正文内容用宋体或黑体。楷体看起来比较温和,粗体更为突出。

LaTeX 的 ctex 宏包对强调中文的处理挺不错的,用了 ctex 文档类或宏包之后,正文是宋体,\emph{中文} 是楷体,\textbf{中文} 是黑体。

1
2
3
4
5
6
\documentclass[fontset=windows]{ctexart}
\begin{document}
你好 \emph{你好} \textbf{你好} \textbf{\emph{你好}}

Hi \emph{Hi} \textbf{Hi} \textbf{\emph{Hi}}
\end{document}
ctex 中文示例

修复 Kubuntu 无音频设备的问题

2020-11-24 00:00:00

执行命令:

1
2
3
4
#!/bin/bash -x
sudo alsa force-reload
pulseaudio -k
start-pulseaudio-x11

进入 System Settings -> Hardware -> Audio -> Advanced -> Device Profiles,把 Profile 改成 Analog Stereo Duplex

把重置音频的命令保存为脚本文件,以后使用更方便:

  1. 把开头的脚本保存为 reset-sound

  2. 添加执行权限

    chmod +x reset-sound

  3. 把文件放到 $PATH1 目录中

这样以后出问题了就不用回来看那几行命令了,直接在终端执行 reset-sound 就行。


延伸阅读:


  1. 如果你没有建立目录存放自己用的脚本,那就使用 mkdir ~/bin 建立一个,然后把 export PATH="/home/hunter/bin:$PATH" 添加到 .bashrc.zshrc。 ↩︎

KDE Plasma 开机静音设置

2020-11-15 00:00:00

Kubuntu 休眠之后开机总会报错:

1
2
3
4
5
6
7
Message from syslogd@my-pc at Nov 15 09:31:33 ...
kernel:[ 319.150050] Do you have a strange power saving mode enabled?
Message from syslogd@my-pc at Nov 15 09:31:33 ...
kernel:[ 319.150050] Dazed and confused, but trying to continue 

Message from syslogd@my-pc at Nov 15 09:31:33 ...
kernel:[ 319.150049] Uhhuh. NMI received for unknown reason 2d on CPU 0. 

在 Stack Exchange 搜索了一下,没发现什么简单的解决方法,于是就一直忍着。但是在教室或者图书馆的时候,打开电脑,突然间它发出了响亮的「噔」警报声。旁人的目光让我的社交焦虑值飙升,巴不得找个洞钻进去。关注的眼神实在是太可怕了!我还是想办法解决这个问题吧。既然没办法阻止报错,那就只能开机和解锁的时候设置静音了。

Ask Ubuntu 找到了设置音量的命令:

1
2
3
/usr/bin/pactl set-sink-volume 0 100%
# or
/usr/bin/amixer -D pulse sset Master 100%

接下来就可以开始设置静音了。

设置开机静音 #

命令行设置 #

1
2
3
4
5
6
7
8
mkdir ~/.config/autostart-scripts

cd ~/.config/autostart-scripts

echo "#\!/bin/bash
/usr/bin/pactl set-sink-volume 0 0%" > 0-vol.sh

chmod +x 0-vol.sh

GUI 设置 #

点击 System Settings -> Workspace -> Startup and Shutdown -> Autostart -> Add Script,直接在设置搜索 Autostart 也行,添加的脚本内容如下:

1
2
#!/bin/bash
/usr/bin/pactl set-sink-volume 0 0%

休眠后再开机,发现没有静音。经过测试,发现这个方法只适用于关机之后开机。可能从休眠中启动只是把硬盘中保存的东西再重新放入内存,所以并不算开机吧。从休眠中启动完之后会进入解锁界面,所以要设置锁屏静音。

设置锁屏静音 #

这个设置我只找到了 GUI 的方法,同样是在系统设置。

  1. 进入设置

    System Settings -> Personalization -> Notifications -> Applications -> Screen Saver

  2. 选择 Screen locked

  3. Run command 选项添加前面的静音脚本

    ~/.config/autostart-scripts/0-vol.sh

终于可以不听到这烦人的提示音了!写到这里发现应该只设置锁屏静音就行了,不需要设置开机静音。懒得测试了,就这样吧。

在 Ubuntu 下配置 Fcitx5

2020-11-06 00:00:00

安装 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 安装 Fcitx5
sudo apt install fcitx5 \
fcitx5-pinyin \
fcitx5-chinese-addons \
fcitx5-frontend-gtk2 \
fcitx5-frontend-gtk3 \
fcitx5-frontend-qt5 \
fcitx5-module-ibus \
fcitx5-module-cloudpinyin \
fcitx5-material-color

# 把 Fcitx5 设置为默认输入法
im-config -n fcitx5

# 设置 Fcitx5 拼音输入法
fcitx5-configtool

# 开机启动
cp /usr/share/applications/fcitx5.desktop ~/.config/autostart/

配置 #

修改标点符号键位映射 #

Fcitx5 默认的标点符号键位为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ cat /usr/share/fcitx5/punctuation/punc.mb.zh_CN
. 。
, ,
? ?
" “ ”
: :
; ;
' ‘ ’
< 《
> 》
\ 、
! !
$ ¥
^ ……
* ×
_ ——
( (
) )
[ ·
] 「 」
~ ~

因为我常用 Markdown 和直角引号,所以把原本是弯引号的键位改成了直角引号,把方括号的键位改成了弯引号,取消了乘号和分隔符。如果要输入乘号和分隔符,就按下 Ctrl + Alt + Shift + u,输入multiplication signmiddle dot(用对应的 Unicode 码也行);使用 TabShift Tab 选择、 翻页,Enter 确定。

其他键盘上没有的字符也可以用这个方式输入,选中字符后按下 Ctrl + Alt + Shift + u 就可以获取字符名称。

动图演示 Ctrl + Alt + Shift + u

设置方法:

  1. 建立新的配置文件

    1
    2
    3
    
    mkdir ~/.local/share/fcitx5/punctuation/
    touch ~/.local/share/fcitx5/punctuation/punc.mb.zh_CN
    nano ~/.local/share/fcitx5/punctuation/punc.mb.zh_CN
    
  2. 把下面的配置内容复制进去:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    . 。
    , ,
    ? ?
    " 「 」
    : :
    ; ;
    ' 『 』
    < 《
    > 》
    \ 、
    ! !
    $ ¥
    ^ ……
    _ ——
    ( (
    ) )
    [ ‘ ’
    ] “ ”
    ~ ~
    
  3. 重启 Fcitx5

    1
    
    pkill fcitx5 && fcitx5 &
    

变化如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ icdiff punc.mb.zh_CN.old punc.mb.zh_CN.new -U 0
punc.mb.zh_CN.old punc.mb.zh_CN.new 
" “ ” " 「 」 
--- --- 
' ‘ ’ ' 『 』 
--- --- 
* × 
--- --- 
[ · [ ‘ ’ 
] 「 」 ] “ ”

使用中文维基词库 #

  1. 下载词库(这里很久没更新了,目前可以下载 Arch Linux 的 fcitx5-pinyin-zhwiki,解压后词典路径为fcitx5-pinyin-zhwiki-1_0.2.4.20240210-1-any.pkg/usr/share/fcitx5/pinyin/dictionaries/zhwiki.dict

  2. 添加词库

    1
    2
    
    sudo mkdir /usr/share/fcitx5/pinyin/dictionaries # 建立词库目录
    sudo cp zhwiki.dict /usr/share/fcitx5/pinyin/dictionaries # 添加词库
    
  3. 重启 Fcitx5

    1
    
    pkill fcitx5 && fcitx5 &
    

在扫描版 PDF 中查找内容

2020-11-05 00:00:00

扫描版的 PDF 都是图片,在里面查找内容是不可能的。所以「在扫描版 PDF 文档中查找内容」这个问题可以拆解为两部分:一是识别 PDF 中的文字,二是搜索里面的内容。

安装软件 #

开始教程之前,先安装好需要的软件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# OCRmyPDF
sudo apt install ocrmypdf

# 语言支持
sudo apt install tesseract-ocr-eng \
tesseract-ocr-chi-sim

# pdftotext
sudo apt install poppler-utils

# VS Code
# 这个得到官网下载:https://code.visualstudio.com/

识别文字 #

首先,我们要用 OCR1 技术来识别 PDF 中的文字。有很多 OCR 软件,我用的是 OCRmyPDF。接下来用余光中译的《老人与海》作为例子。

  1. 识别 PDF 文档中的文字

    1
    
    ocrmypdf -l eng+chi_sim 老人与海-余光中.pdf 老人与海-余光中-ocr.pdf
    
  2. 把识别后 PDF 转换成纯文本

    1
    
    pdftotext 老人与海-余光中-ocr.pdf
    

搜索文字 #

中文 #

《老人与海》第三段 除了眼睛,他身上处处都显得苍老。可是他的眼睛跟海水一样颜色,活泼而坚定老人与海-余光中-ocr.txt 里面是这样的:

1
2
3
4
除了 眼睛, 他 身上 处 处 都 显得 苍老.可 是他 的眼睛 跟 海 水一
样 颜色 , 活

涛 而 坚定 。

可见文字识别的效果实在不太行,不仅有错字,还有多余的空格和换行。直接搜索想找的内容肯定是不行的,但是正则表达式就可以。先观察一下上面那段文字,有文字、空格和换行符,共 58 个字符。所以我们要查找带有包含文字、空格和换行的组合。

使用 VS Code 查找:

1
[除了眼睛,他身上处处都显得苍老。可是他的眼睛跟海水一样颜色,活泼而坚定 \n]{10,}

结果:

1
2
3
一 样苍老 。
除了 眼睛, 他 身上 处 处 都 显得 苍老.可 是他 的眼睛 跟 海 水一
样 颜色 

把结果复制到 WPS PDF 搜索,就可以找到第三段文字了。注意,不推荐用 grep 搜索,grep 只能搜索一行字符。

英文 #

第三段的英文 Everything about him was old except his eyes and they were the same color as the sea and were cheerful and undefeated.老人与海-余光中-ocr.txt 里面是这样的:

1
2
Everything about him was old except his eyes and they were
the same color as the sea and were cheerful and undefeated.

看来识别效果挺好的,内容正确。单词间的都是空格或者换行符。使用 VS Code 查找:

1
Everything(\n| )*about(\n| )*him(\n| )*was(\n| )*old(\n| )*except(\n| )*his(\n| )*eyes(\n| )*and(\n| )*they(\n| )*were(\n| )*the(\n| )*same(\n| )*color(\n| )*as(\n| )*the(\n| )*sea(\n| )*and(\n| )*were(\n| )*cheerful(\n| )*and(\n| )*undefeated\.

(把 Everything about him was old except his eyes and they were the same color as the sea and were cheerful and undefeated. 的空格代替为 (\n| )*,再在句号前加上反斜杠,这样就得到上面的表达式了。)

结果:

1
2
Everything about him was old except his eyes and they were
the same color as the sea and were cheerful and undefeated.

  1. Optical Character Recognition,光学字符识别 ↩︎

在 RStudio 中使用 Fcitx5(Kubuntu 20.10)

2020-11-01 00:00:00

2022 年 7 月 16 日更新:Electron 版的 RStudio 可以正常使用 Fcitx5 了,位置也正常。

在 Kubuntu 20.10 使用 RStudio 的时候,发现它不支持 Fcitx。参考 RStudio 官方的方法,成功在 RStudio 里面用上了 Fcitx 5。里面部分内容已经过时,应该用下面这个命令才对:

1
sudo ln -s /usr/lib/$(dpkg-architecture -qDEB_BUILD_MULTIARCH)/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so /usr/lib/rstudio/plugins/platforminputcontexts

最近把 Kubuntu 20.04 升级到了 20.10,结果无法在 RStudio 中使用 Fcitx5 了。可恶,为什么 RStudio 不支持 Fcitx!官方不作为,那只好自己动手了。

首先尝试了编译 fcitx-qt5,失败。不行,我要死磕这个问题认怂了,用 Ibus 去了。把输入法设置成 Ibus 之后,用了一段时间;感觉真是难用,还没有云拼音,突然十分怀念 Fcitx5。于是我启动了 Fcitx5,发现它可以和 Ibus 共存,真是不可思议。又可以在 RStudio 里用 Fcitx5了。

下面是设置方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装 Ibus 输入法,将它设置为默认的输入法
sudo apt install ibus
im-config -n ibus

# 安装 Fctix5
sudo apt install \
fcitx5 \
fcitx5-pinyin \
fcitx5-chinese-addons \
fcitx5-frontend-gtk2 \
fcitx5-frontend-gtk3 \
fcitx5-frontend-qt5 \
kde-config-fcitx5 \
fcitx5-module-ibus

# 开机启动 Fcitx5
cp /usr/share/applications/fcitx5.desktop ~/.config/autostart/

# 设置 Fcitx5(如果已经设置过了就跳过这一步)
fcitx5-configtool

# 如果有缺失的包就先添加下面的 PPA:
# sudo add-apt-repository ppa:hosxy/fcitx5

这个方法有个坏处:Fcitx5 在某些软件中显示的位置不正确。

fcitx5-in-rstudio

俗话说,如果解决不了问题,就把提出问题的人解决掉。软件亦是如此,这个软件有问题,就换另一个咯。你可以用 RStudio Server,它是用浏览器访问的,Chrome 和 Firefox 都支持 Fcitx 5。如果你还是搞不定,那可以抛弃 RStudio,改用 Visual Studio Code。VS Code 也是支持 R 语言的。


延伸阅读

在 Ubuntu 编译 GoldenDict

2020-10-02 00:00:00

2024年8月14日更新 #

写这篇文章的初衷是 GoldenDict 没有停止开发但又很少发布新版,最近一次还是一年前(2023年5月31号)。现在推荐大家使用更新和发布频繁的 Goldendict-ng。Goldendict-ng 已被收录于 Debian、Ubuntu、Fedora、OpenSUSE 等 Linux 发行版,用 flatpak 安装也方便。

如果你点进来就是想编译 GoldenDict,请看 Goldendict 的文档,下面的内容可能已过时。

安装依赖 #

测试于 Ubuntu 21.04。

1
2
3
4
5
6
7
8
sudo apt-get update
sudo apt-get install git pkg-config build-essential qt5-qmake \
 libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev \
 qtdeclarative5-dev libxtst-dev liblzo2-dev libbz2-dev \
 libao-dev libavutil-dev libavformat-dev libtiff5-dev libeb16-dev \
 libqt5webkit5-dev libqt5svg5-dev libqt5x11extras5-dev qttools5-dev \
 qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins \
 libopencc-dev liblzma-dev libzstd-dev

编译 #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 卸载源里面的 GoldenDict
sudo apt remove goldendict
# 克隆源代码
git clone https://github.com/goldendict/goldendict.git
# 编译
cd goldendict
qmake "CONFIG+=chinese_conversion_support" "CONFIG+=zim_support"
make
# 安装
sudo make install
# 卸载
# sudo make uninstall

实际上不执行 make install 安装 GoldenDict 也可以用,运行编译好的 GoldenDict 就可以了。

1
2
chmod +x ./goldendict # 增加可执行权限
./goldendict # 运行