跳到主要内容

hacker

· 阅读需 19 分钟
  1. 动手实践,查看文档反复循环。动手实践可以把脑海中陌生知识转为熟悉技能。阅读文档可以补充新的学习素材。
  2. 学会一个 Linux 基本操作(man、grep、vim、输入输出重定向操作符),编辑器工具,要玩的炉火纯青,每个快捷键烂熟于心。
  3. Desk checking,在编译前检查代码。(code review
  4. 出现 bug 时,思考 2 个问题
    • 哪些设计或编程习惯导致了这个 bug ?
    • 哪些习惯能系统性的预防此类 bug ?
  5. 项目延期就要延个彻底,预留足够多的时间来处理未完成的事情
  6. 砍功能也要彻底,不要想着这个功能以后会用。(删除注释掉的代码同理)
  7. 做工程需要清醒的头脑,不要沉迷于长时间解决某个问题。(10:00 am and 2:00 pm 效率最高

原文

Merc Release 2.1   
Sunday 01 August 1993

Furey mec@shell.portal.com
Hatchet hatchet@uclink.berkeley.edu
Kahn michael@uclink.berkeley.edu

我开 MUD 服务器就是为了学 C 语言编程!

Yeah, right.

本文旨在记录我们的知识、经验和理念。无论您处于什么水平,我们都希望这份文档能帮助您成为更优秀的软件工程师。

请记住,工程实践才是根本,任何文档都无法替代您自己的思考、学习和动手实践。

学习之道

(1) 动手实践
(2) 查阅文档
(3) 继续实践
(4) 再读文档
(5) 深入实践
(6) 反复研读
(7) 持续实践
(8) 温故知新
(9) 明白了吗?

人脑在单次学习中能吸收的新知识量是有限的。动手实践虽然不会带来太多新内容,却能把脑海中的" 陌生知识"转化为"熟悉技能"。阅读文档虽无法让知识变熟悉,却能持续补充新的学习素材。

多数人即便看了文档也很少重温,达到基本够用就止步不前。但现代操作系统、编程语言、网络技术乃至应用软件, 都不可能一蹴而就掌握。必须反复经历"学习-实践"的循环才能真正精通。

环境配置

计算机:运行 Merc 程序的各类主机设备,无论体积大小。每台计算机都有其_制造商_和_型号_标识。以下是您可能会遇到的常见厂商及型号列表:

	Manufacturer	Model
------------ -----
Sun Sun-2
Sun Sun-3
Sun Sun-4
DEC Vax 5000
DEC Vax 5900
IBM RS/6000
NeXT NextCube
Sequent Symmetry
Sequent Balance

就硬件而言,Merc 可在任何 32 位硬件上运行。

操作系统:计算机上运行的最低层级程序。大多数常见计算机运行 Unix 或其变种版本,如 SunOSUltrixAIXMachDynix。注意这些变种名称多以 IX 结尾。

Unix 系统主要分为两大"家族":伯克利 Unix(由著名的加州大学伯克利分校开发)和 System 5 Unix(由 Unix 的鼻祖贝尔实验室开发)。

最常见的非 Unix 操作系统是 VMS(DEC 公司为其 VAX 计算机开发的专有系统)。在个人电脑领域,你会看到 IBM PC 及兼容机使用的 MS-DOS 和 OS/2 系统,以及苹果麦金塔电脑的 MacOS 系统。

先说清楚:VAX不是操作系统,而是 DEC 公司一系列计算机的名称。很多 VAX 机运行 VMS 系统,但运行伯克利 Unix 或 Ultrix 系统的 VAX 机更多。运行 Unix 的 VAX 机与其他运行 Unix 的机器共性更多,反而与运行 VMS 的同系列机型差异更大。

就操作系统兼容性而言,Merc 可在支持伯克利 Unix 版 TCP/IP 网络的 Unix 及其变体系统上运行。它也支持 MS-DOS 系统,但仅限于单用户模式。经过适当修改,Merc 可以移植到任何提供 telnet连接 TCP 服务的操作系统。

编程语言:Merc 采用 C 语言编写。美国国家标准协会(ANSI)制定了 C 语言规范,Merc 完全遵循 ANSI 标准 C 语言规范开发。

最流行的 ANSI 标准 C 编译器是自由软件基金会开发的 GNU gcc编译器,可通过 prep.ai.mit.edu 匿名 FTP 获取。Merc 在 Gcc 1.38 版本下编译良好,因此您可以直接使用 1.42版本,无需安装更庞大的 2.X 系列。

您并非必须使用 gcc 编译器。运行 AIX 操作系统的 IBM RS/6000 已预装 ANSI C 编译器,NeXT 工作站亦是如此(其标准cc编译器实际就是 GNU C 编译器)。任何符合 ANSI 标准的编译器都能正常使用。

遗憾的是,目前仍有大量设备未配备 ANSI 标准 C 编译器(Sun工作站在这方面最为典型)。您可以通过mktrad脚本尝试用非 ANSI(传统)C 编译器编译 Merc,具体操作请参阅 trad.txt 文档。

若不清楚电脑的品牌型号、操作系统以及 C 编译器是 Ansi 还是非 Ansi 标准,您需要先查明这些信息。

Unix 基础工具

man -- 提供在线手册页查询功能

grep -- 全称是"全局正则表达式打印"

viemacsjove -- 编辑器随你挑,但必须玩到炉火纯青,每个快捷键都要烂熟于心

ctags -- 为编辑器生成标签索引,让你能通过函数名直接跳转到源码任意位置

>>><| -- 输入输出重定向操作,要么找人演示,要么自己查man csh手册

这些都是程序员吃饭的家伙什。如果连这些基本功都不扎实,就像开车不会换挡一样寸步难行

调试方法论

调试是门科学。你需要建立假设,基于假设做出预测,运行程序并输入测试数据,观察其行为,最终验证或推翻假设。

优秀的假设能做出令人惊喜且最终应验的预测——这是其他假设无法做到的独特预见。

调试的首要原则是从源头避免制造 bug。这话听着简单,可惜人们总是置若罔闻。

编写程序时若出现任何错误或警告,务必先修正再继续。 C 语言的设计允许许多有缺陷的编码方式合法通过,但足够智能的编译器(比如开启-Wall选项的 gcc)会发出警告。花几分钟检查并修复这些警告代码,能为你省下数小时的排错时间。

1993 年时,"Desk checking"(代码审阅)这门手艺几乎失传了,实在可惜。你应当在编译前就进行代码自查,并定期复查以保持思路清晰、发现新问题。如果团队中有人专职审核他人代码,这个人发现的错误会比其他人加起来都多。

人工逐行检查代码每小时能覆盖数百行。顶尖程序员首轮编写的代码准确率约 99%,即每百行仍有一个漏洞。而你并非顶尖选手... 因此通过桌面检查每小时会发现多个漏洞。这堪称最高效的调试手段——比起在崩溃程序中耗费数小时才能揪出一个错误,效率简直天壤之别。

进阶技巧当属历史悠久的"打印语句调试法"——在代码中插入 print 语句追踪变量值。在 Merc 代码体系中,可调用 printffprintf 在关键节点输出关键数值。何时何处输出这些数值堪称调试艺术,唯有实践方能掌握其中精髓。

若尚未掌握操作系统中的输出重定向技巧,现在正是学习良机。Unix 系统下输入 man csh 命令,重点研读 > 操作符章节。同时必须厘清标准输出(如 printf 输出)与错误输出(如 fprintf 输出)的本质区别。

归根结底,如果不先理解程序的运行机制,就无法真正修复它。强大的调试工具能帮你收集数据,但它们既不能解读数据,也无法解决根本问题——这些只有你能做到。

发现 bug 时...你的第一反应往往是修改代码、消除表象就宣布修复完成。且慢!你看到的 bug 通常只是更深层问题的症状。应该追根究底,彻底参透这个 bug 的价值后,再让它"魂飞魄散"。

此外,发现 bug 时要问自己两个问题:"最初是哪些设计和编程习惯导致了这个问题?"以及:"哪些系统性习惯能预防此类 bug 的产生?"

调试工具篇

当 Unix 进程访问无效内存地址时,或(较罕见地)执行非法指令,或(更罕见地)发生其他异常时,Unix操作系统会接管控制权。该进程无法继续执行且必须被终止。但在终止前,系统会执行一项辅助操作:创建一个名为core的文件,并将该进程的完整数据空间写入其中。

因此,dumping core 并非问题的成因,甚至不是问题的直接表现。这是操作系统为帮助开发者定位导致进程崩溃的致命错误而提供的诊断机制。

开发者需使用调试器分析core文件。Unix 系统最常用的调试器是adbgdb ,偶尔也会遇到dbx。典型启动方式如:adb mercgdb merc core

调试时首先要做且往往唯一需要做的就是获取堆栈追踪。在adb中对应命令是$c,gdb 中则是backtrace

堆栈追踪能显示程序崩溃时所在的函数及其调用链,调试器还会列出这些函数的参数。要正确解析这些参数并运用更高级的调试功能,需要扎实的汇编语言编程功底。

如果你能使用名为Purify的程序...务必掌握其用法。

性能分析

程序性能分析步骤如下:

(1) Remove all the .o files and the merc executable:

rm *.o merc

(2) Edit your makefile, and change the PROF= line:

PROF = -p

(3) Remake merc:

make

(4) 照常运行 merc。当运行足够长时间获得良好的性能分析基准后,使用 shutdown 命令关闭游戏。如果游戏崩溃或从外部终止进程,将无法获取性能分析数据。

(5) 运行prof命令:

prof merc > prof.out

(6) 阅读 prof.out 文件。执行man prof命令了解输出格式说明。

要进行高级性能分析,可在步骤(2)中使用PROF = -pg参数,并在步骤 5 执行 gprof命令。gprof分析工具能生成详细报告,精确显示函数间的调用次数。 这些数据对调试和性能优化都极具价值。profgprof工具的可用性因系统而异。几乎所有 Unix 系统都内置 prof工具,但只有部分系统支持gprof

进度、功能与质量的三角博弈

现在简单谈谈项目管理

几乎所有项目最终都会面临进度、功能与质量的取舍困境。就像学生在截止前夜赶论文时,面临三个痛苦选择:要么迟交(牺牲进度),要么缩减内容(阉割功能),要么胡编乱造(降低质量)。

软件开发同样如此,团队常在三种方案间纠结:要么延期发布,要么砍掉功能,要么硬着头皮按时交付——而最后这种情况往往以失败告终。

这个决定最关键的是要意识到它确实是个决定。别指望会有奇迹发生来逃避。如果你不主动应对,外部环境就会替你做出选择。

假设你面临取舍选择了延期,那就别小打小闹——要延就延个痛快。如果想着"解决这个问题就尽快收尾",事后你准会后悔没多留点时间。但如果说"需要多干一天,干脆延期一周",到周末时完成质量反而更有保障。一次性大幅延期,总比重蹈覆辙地每天每小时拖延强。

如果要砍功能,同样要下狠手。别畏首畏尾地幻想"等有空再做"。这个功能已经彻底出局了,抓住需求简化的机会能省则省!

关于如何降低质量我给不出什么建议,这永远是我在项目中最后才会考虑的妥协方案。

睡眠之道

道理简单却深刻:做工程需要清醒的头脑

连续数小时埋头解决问题既轻松又诱人。人们会进入一种"心流"状态,满脑子都是问题,工作成果源源不断地涌现。许多作家描述过这种体验——仿佛亲眼目睹故事展开,只需将所见记录下来,就能一页接一页地创作。软件工程师们也常有类似感受,代码似乎自发地从指尖流淌而出。

我相信最有价值的工作往往诞生于这种状态。

但根据我的经验,"心流"状态往往在不知不觉中悄然消退。等我反应过来时,新工作已经不再行云流水般从手中产出,反而要花大量时间修正刚犯的错误。原本灵光闪现的头脑开始被各种疑虑和问题占据。

这时候很容易产生"再熬几小时"的冲动。"反正都在这儿了,刚才效率那么高,干脆通宵搞定算了?"千万别上当!这绝对是个陷阱!

我的建议是:回家吃饭、冲澡、睡觉,让自己满血复活。第二天再战。睡眠时大脑会自动处理问题,醒来时往往就有新思路。第二天上午10 点到下午 2 点的高效产出,绝对胜过熬夜到凌晨的混沌状态。

这个策略有个难点:如何重燃晨间斗志。如果是自主选择的项目还好说,但面对不得不做的任务时,就得权衡"早起没干劲"和"熬夜低效率"的利弊了。

程序员必读经典书单

在浩如烟海的编程书籍中,这三本堪称经典:

Kernighan and Plaugher, 《编程风格要素》

Kernighan and Ritchie, 《C程序设计语言》

Brooks, 《人月神话》

Go 内存分配机制

· 阅读需 2 分钟

问题引入

在阅读 New-API 源码的时候,发现在 struct 中定义一个 *string 类型的字段

type ClaudeMediaMessage struct {
Type string `json:"type,omitempty"`
Text *string `json:"text,omitempty"`
Model string `json:"model,omitempty"`
Source *ClaudeMessageSource `json:"source,omitempty"`
Usage *ClaudeUsage `json:"usage,omitempty"`
StopReason *string `json:"stop_reason,omitempty"`
PartialJson *string `json:"partial_json,omitempty"`
Role string `json:"role,omitempty"`
Thinking string `json:"thinking,omitempty"`
Signature string `json:"signature,omitempty"`
Delta string `json:"delta,omitempty"`
// tool_calls
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input any `json:"input,omitempty"`
Content json.RawMessage `json:"content,omitempty"`
ToolUseId string `json:"tool_use_id,omitempty"`
}

func (c *ClaudeMediaMessage) SetText(s string) {
c.Text = &s
}

struct 中定义一个 *string 类型的字段的目的

  1. 可以区分此字段是否被设置,或者被设置为 ""

    • 原理是:*string 是一个字符串指针,默认值是 nil, 而 string 的默认值是 ""
  2. 在序列换 JSON 的时候,如果字段是 *string 类型,且值为 nil,则在序列化时会被忽略。

  3. API 设计时,可能很好的表达出可选字段。

  4. 避免频繁出现空字符串。

    • var s string , s 会被初始化为 "",变量没有逃逸,则会被分配在栈上,底层实现是 StringHeader, 大小 16 字节(指向底层字节数组的指针(8 字节,64 位系统)、字符串长度(8 字节,64 位系统))
    • var sp *string , sp 默认值是 nil, 声明时立即分配内存(通常是在栈上),大小 8 字节(由操作系统决定,64位,8字节)

文章引用

TIMESTAMP 的时间范围

· 阅读需 2 分钟

执行 sql 报错。

UPDATE inspection_plans t SET t.plan_end_time = '2041-04-08 10:41:03' WHERE t.id = 1

错误信息:

Data truncation: Incorrect datetime value: '2041-04-08 10:41:03' for column 'plan_end_time' at row 1

问题原因:TIMESTAMP 的范围是 '1970-01-01 00:00:01' UTC '2038-01-19 03:14:07' UTC

解决方法:将 TIMESTAMP 改为 DATETIME 类型。

alter table inspection_plans modify plan_end_time datetime;

总结:

  • DATE 能够表示的时间范围:1000-01-019999-12-31 ,只记录年月日,不存储时分秒
  • TIME 能够表示的时间范围:'-838:59:59''838:59:59' ,只记录时分秒,不存储年月日
  • DATETIME 能够表示的时间范围:1000-01-01 00:00:009999-12-31 23:59:59 推荐
  • TIMESTAMP 能够表示的时间范围:1970-01-01 00:00:01 UTC2038-01-19 03:14:07 UTC
  • YEAR 能够表示的时间范围:19012155
  • TIMESTAMPDATETIME 的区别:
    • TIMESTAMP 是 UTC 时间,存储时会转换为 UTC 时间,取出时会转换为当前时区的时间
    • DATETIME 是本地时间,存储时不会转换为 UTC 时间,取出时也不会转换为 UTC 时间

nvm

· 阅读需 1 分钟

nvm 设置默认 node 版本

nvm alias default 18

.npmrc 文件的作用

配置说明文档: https://docs.npmjs.com/cli/v9/using-npm/config

npm 命令的预设配置文件,在执行 npm 命令时,会加载并根据这些配置来执行命令。

audit=false
loglevel=error
registry=http://mirrors.tencent.com/npm/
engine-strict=true

= 与 := 的区别

· 阅读需 3 分钟

基础用法

= 赋值操作符

  1. 仅用于赋值,不声明变量
  2. 可以在任何代码快中使用
var a int
a = 100

:= 短变量声明

  1. 声明变量的同时赋值
  2. 只能在函数内部使用
  3. 在给多个变量的时候赋值的时候,至少要定义一个新变量
a := 100
a,b := 200,300

使用案例

闭包

func closureDemo(){
for i:=0; i< 3;i++ {
go func(){
fmt.Print(i," ") // 闭包
}()
}
}

这段代码可能会输出 3 3 3,因为 i 是在闭包外部定义的变量,所有的 goroutine 都共享同一个 i 变量。

func closureDemo(){
for i:=0; i< 3;i++ {
val :=i
go func(){
fmt.Print(val," ")
}()
}
}

使用局部变量可以避免闭包带来的问题。每次循环都会创建一个新的变量。

i 变量定义在中,val 变量定义在中。

func closureDemo(){
for i:=0; i< 3;i++ {
go func(v int){
fmt.Print(v," ")
}(i)
}
}

通过参数将 i 的值传递给闭包函数,确保每个 goroutine 都有自己的 i 值。

并发下的安全赋值/原子操作

func main() {
var counter *int64 = new(int64)
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(v *int64) {
*v += 1
wg.Done()
}(counter)
}
wg.Wait()
fmt.Println("Counter:", *counter)
}

输出的值小于 1000

func concurrentAssignment() {
var counter int64
var wg sync.WaitGroup

// 使用原子操作保证并发安全
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
atomic.AddInt64(&counter, 1) // 使用=修改共享变量
wg.Done()
}()
}
wg.Wait()
fmt.Println("Counter:", counter)

// 使用通道安全传递值
ch := make(chan int)
go func() {
val := <-ch // 使用:=接收新值
fmt.Println("Received:", val)
}()
ch <- 42 // 使用=发送值
}

使用 atomic.AddInt64 来实现原子操作,确保在并发环境下对 counter 的安全修改。

类型断言

· 阅读需 2 分钟

类型断言用于检查接口值的动态类型是否符合某个特定类型。

value, ok := interfaceVar.(ConcreteType)

  • interfaceVar 是一个接口变量
  • ConcreteType 是你想要断言的具体类型
  • value 是断言成功后转换的值
  • ok 是一个布尔值,表示断言是否成功

案例1 双返回值

var writer io.Writer = os.Stdout
if closer, ok := writer.(io.Closer); ok {
closer.Close() // 安全调用接口扩展方法
}

案例2 单返回值

// 直接转换(失败时触发panic)
str := anyVar.(string)

案例3 类型匹配

var v interface{} = 42

// 基础类型匹配
_, ok1 := v.(int) // ok1 = true
_, ok2 := v.(string) // ok2 = false

// 接口类型匹配
_, ok3 := v.(fmt.Stringer) // 检查是否实现特定接口

案例4 与 switch 关键字搭配使用

.(type) 必须与 switch 一起使用。

switch v := err.(type) {
case *AppError:
// 处理自定义错误类型
case *os.PathError:
// 处理系统路径错误
case nil:
// 没有错误的情况
default:
// 未知错误类型
}

go:embed 指令

· 阅读需 3 分钟

[GoLang官方提案]https://go.googlesource.com/proposal/+/master/design/draft-embed.md

//go:embed 支持嵌入静态文件。

  1. 对于单个文件,可以嵌入为字符串和 byte slice
  2. 多个文件,支持嵌入为新的文件系统 FS
  3. 只支持嵌入为string, byte sliceembed.FS三种类型,这三种类型的别名(alias)和命名类型(如type S string)都不可以

嵌入字符串

//go:embed hello.txt
var s string

嵌入为 byte slice

//go:embed hello.txt
var b []byte

嵌入为fs.FS

基本使用

//go:embed hello.txt
var f embed.FS

支持同一变量上嵌入多个文件

//go:embed hello1.txt
//go:embed hello2.txt
var f embed.FS

func main() {
data, _ := f.ReadFile("hello.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("hello2.txt")
fmt.Println(string(data))
}

匹配模式

go:embed dirpath 次文件夹下的除了 ._ 开头的文件/文件夹都会被嵌入(递归)。

如果要嵌入._开头的文件/文件夹,需要使用 go:embed dirpath/*

注意:这个只能嵌入 dirpath 目录下的 ._开头的文件/文件夹。子文件夹下的 ._ 开头的文件/文件夹是不会嵌入。

使用

将前端静态文件打包进 go 程序

嵌入静态文件

//go:embed frontend/out/*
var buildFS embed.FS

输出日志,检查文件是否被正确嵌入

// 打印嵌入的文件系统内容,用于调试
fs.WalkDir(buildFS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
log.Printf("嵌入文件系统中有: %s", path)
}
return nil
})

静态文件服务

// 创建一个子文件系统,将嵌入的文件系统 buildFS 中的 frontend/out 目录作为新文件系统的根目录
contentFS, err := fs.Sub(buildFS, "frontend/out")
if err != nil {
log.Fatalf("无法获取嵌入的前端文件: %v", err)
}

// 创建一个文件服务处理器
fileServer := http.FileServer(http.FS(contentFS))

// 为所有路径提供服务
r.PathPrefix("/").Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 打印请求路径,帮助调试
log.Printf("请求文件: %s", r.URL.Path)
fileServer.ServeHTTP(w, r)
}))

Linux Shell 常用命令

· 阅读需 9 分钟

du

1、查看指定文件夹的大小

-h 以K,M,G为单位,提高信息的可读性。
-s 仅显示总计。

du -sh dir_path

2. 查询当前文件夹总大小

du -sh .

3. 查看当前文件夹所有子项的大小

du -sh *

df

df (disk filesystem)用于显示文件系统的磁盘空间使用情况.

查看 block 使用情况

df -h

查看 inode 使用情况

df -i

alias

alias gg='gg'
alias cls='clear'
alias ls='ls -alFh --ignore=. --ignore=.. --color=auto --group-directories-first'
alias acme.sh=~/.acme.sh/acme.sh

ln

ln -s source_file target_file

nginx

ln -s /wangzhy/cloud-conf/nginx/etc/nginx/conf /etc/nginx/conf
ln -s /wangzhy/.config/nginx_ip_conf /etc/nginx/ip-conf

把程序添加到 /usr/bin 下面

ln -s /source_path/source_name /usr/bin/target_name 

find

find 语法

find [path] [expression]

path:是要查找的目录路径,可以是一个目录或文件名,也可以是多个路径,多个路径之间用空格分隔,如果未指定路径,则默认为当前目录。

expression:是可选参数,用于指定查找的条件,可以是文件名、文件类型、文件大小等等。

-name pattern:按文件名查找,支持使用通配符 * 和 ?。

pattern 用引号括起来,例如

# 查找所有的 pdf 文件
find / -name '*.pdf*'

-type type:按文件类型查找,可以是 f(普通文件)、d(目录)、l(符号链接)等。

查看大于 100M 的文件

find / -type f -size +100M

-size [+-]size[cwbkMG]:按文件大小查找,支持使用 + 或 - 表示大于或小于指定大小,单位可以是 c(字节)、w(字数)、b(块数)、k(KB)、M(MB)或 G(GB)。

-mtime days:按修改时间查找,支持使用 + 或 - 表示在指定天数前或后,days 是一个整数表示天数。

-user username:按文件所有者查找。

-group groupname:按文件所属组查找。

find 命令中用于时间的参数如下:

-amin n:查找在 n 分钟内被访问过的文件。

-atime n:查找在 n*24 小时内被访问过的文件。

-cmin n:查找在 n 分钟内状态发生变化的文件(例如权限)。

-ctime n:查找在 n*24 小时内状态发生变化的文件(例如权限)。

-mmin n:查找在 n 分钟内被修改过的文件。

-mtime n:查找在 n*24 小时内被修改过的文件。

在这些参数中,n 可以是一个正数、负数或零。正数表示在指定的时间内修改或访问过的文件,负数表示在指定的时间之前修改或访问过的文件,零表示在当前时间点上修改或访问过的文件。

+n : n 天之前修改的文件 -n : n 天内修改的文件 n : n 天前修改的文件(第 n 天前的当天)

find 使用案例

  1. 在脚本中使用

find /opt/oracle/oradata/ORCLCDB/oracle_exp_dir -mtime +1 -name "WIKI_USER*" - rm -f exec {} \;

  1. 在 shell 使用

find /opt/oracle/oradata/ORCLCDB/oracle_exp_dir -mtime +1 -name "WIKI_USER*" - rm -f exec {} +

  1. 查找多种后缀的文件
find ~ \( -name "*.zip" -o -name "*.log" -o -name "*.txt" -o -name "*.jar" -o -name "*.json" -o -name "*.xls" -o -name "*.xlsx" -o -name "*.doc" -o -name "*.docx" \)
  1. 查找大于 10M 的 zip 文件
find /path/to/search -type f -name "*.zip" -size +10M -exec du -h {} + > ~/Desktop/a.txt

watch

每隔 1s 查询一下本目录下的,以 sql 结尾的文件的大小,单位是 MB。

watch -n 1 -d ls -lh --block-size=M *.sql

vim

1、vim 显示隐藏行号

# 显示行号
set number
set nu
# 隐藏行号
set nonumber
set nonu

2、vim 删除以 XX 开头的行

# 按 esc 进入命令输入模式
# 删除以 XX 开头的行
:g/^XX/d

tee

tee 的主要功能是从标准输入读取数据,然后将数据写入标准输出(即输出到终端上面)和文件。

echo 'hello world' | tee -a xxx.txt

tee>> 的区别?

tee 可以一次写入多个文件,并且可以在终端显示写入的内容。 >> 是静默的写入。

tar 压缩/解压文件

# 压缩
tar -zcvf test.tar.gz test
# 解压
tar -zxvf file.tar.gz

五选一

-c 压缩 -x 解压 -t 查看内容 -r 追加文件 -u 更新原压缩包的文件

可选

-z gzip -j bz2 -Z compress -v 显示过程 -O 标准输出

必选

-f 使用档案名字,这个参数是最后一个参数,后面只能接档案名。

systemd

Ubuntu 16.04 不再支持 rc.local 了。开机自启只能通过 systemd 来实现。

# 查看systemd 版本
systemctl --version
# 查看服务是否启用
systemctl is-enabled SERVICE_NAME

scp

[root@VM-16-10-centos ~]# scp
usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]
[-l limit] [-o ssh_option] [-P port] [-S program]
[[user@]host1:]file1 ... [[user@]host2:]file2

scp 命令传输文件

# 复制远程机器上的 xxx.zip 到本地
scp cloud:/wangzhy/xxx.zip ~/backups/
# 复制远程机器上的文件夹到本地
# -r 表示递归复制整个目录
scp -r cloud:/wangzhy ~/backup/
  • -C:开启压缩传输

passwd

1、设置简单的密码

# 方法一
echo '123456' | passwd --stdin username
# 方法二
echo username:password | chpasswd

2、删除用户的登录密码

passwd -d root

3、清除 last、lastb 的信息

# 清除登录记录
echo > /var/log/wtmp # last
echo > /var/log/btmp # lastb

查看 linux 版本信息

# 查看架构信息
uname -a
# 查看发行版本信息
cat /etc/os-release
# 输出机器的体系结构
arch

ntp

同步操作系统的时间

# 安装 ntp
yum -y install ntp
# 同步时间,这个只是修改操作系统的时间,重启之后就会失效
ntpdate -u cn.pool.ntp.org
# 查看当前硬件时间
clock -r
# 把当前操作系统的时间写入硬件中
clock -w

curl

curl -L 参数的作用是让 curl 在遇到 HTTP 3xx 状态码(重定向)时,自动跟随新的位置(URL)

curl -L www.sina.com

使用 -k--insecure 参数让 curl 忽略 SSL 证书验证

curl -k https://wangzhy.com 

下载文件

-o 需要指定文件名

-O 使用 URL 中的文件名

brew

安装 xcode 才能升级 brew

xcode-select --install

brew 升级

brew update
brew upgrade

brew 查看安装的软件

brew list

brew 卸载软件

brew uninstall software_name

查看磁盘读写总量

brew install smartmontools

smartctl -a /dev/disk2

安装 svn

brew install subversion

svn help

grep

egrep 是 grep 的增强版本,支持更多的正则表达式

-v 告诉 egrep ,不现实匹配的行,即反向匹配。

egrep -v "^*#|^$" filebeat.yml

wget

-i 下载本地或外部文件中的 URL,如: wget -i file.txt

-O 将输出写入指定文件。 sh -c "$(wget https://xxx.sh -O -)", 末尾的 - 表示标准输出(stdout) 这句命令的作用是,通过 sh 执行 https://xxx.sh 脚本。

-b 后台下载。

-d 调试模式。

基于Docker和Nginx搭建HTTPS Git服务器

· 阅读需 3 分钟

使用到的工具:

  1. Docker Compose
  2. Nginx
  3. Git
  4. fcgiwrap

Docker Compose 配置

services:
nginx:
restart: always
container_name: nginx
user: root
image: nginx
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- 80:80
- 443:443
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/mine.types:/etc/nginx/mine.types
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/html:/etc/nginx/html
- ./nginx/screen:/etc/nginx/screen
- ./nginx/logs:/etc/nginx/logs
# 重要:
- /run/fcgiwrap.socket:/var/run/fcgiwrap.socket
# 这里不需要映射进去,因为 fcgiwrap 是运行在宿主机里面的
# - "/usr/lib/git-core/:/usr/libexec/git-core/:ro"
environment:
- NGINX_PORT=80
- TZ=Asia/Shanghai
privileged: true

Nginx 配置

# 443 端口
server {
listen 443 ssl;
server_name git.wangzhy.com;
ssl_certificate /etc/nginx/ssl/wangzhy.com_ecc/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/wangzhy.com_ecc/wangzhy.com.key;

ssl_session_cache shared:git_ssl_cache:10m;
ssl_session_timeout 10m;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
charset utf-8;
default_type text/plain;

include /etc/nginx/ip-conf/whitelist.conf;
deny all;

root /etc/nginx/html;

# 通过 https 请求 git
location ~ (/.*)$ {
# 使用 Basic 认证
# auth_basic "Restricted";
# auth_basic_user_file /etc/nginx/passwd;

# FastCGI 参数
include fastcgi_params;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
# 因为 fastcgi 是通过 docker 将宿主机的程序映射过来的,fastcgi 实际是运行在宿主机的,所以在这里要是有宿主机的地址。
fastcgi_param SCRIPT_FILENAME "/usr/lib/git-core/git-http-backend";
fastcgi_param GIT_HTTP_EXPORT_ALL "";
# git 库在服务器上的根目录
fastcgi_param GIT_PROJECT_ROOT /wangzhy/gitrepo;
fastcgi_param PATH_INFO $1;
# 将认证用户信息传递给 fastcgi 程序
# fastcgi_param REMOTE_USER $remote_user;
# 将允许客户端 post 的最大值调整为 100 兆
}

error_page 400 402 403 404 500 502 503 504 /50x.html;
location = /50x.html {
}
}

Git 配置

允许远程访问 git 仓库:

git config --system http.receivepack true
git config --system http.uploadpack true

fcgiwrap 配置

安装

apt update && apt install fcgiwrap

启动

systemctl start fcgiwrap
systemctl enable fcgiwrap

问题处理

权限问题

  1. error: remote unpack failed: unable to create temporary object directory

检查下面文件、文件夹的权限

  • /run/fcgiwrap.socket
  • /usr/lib/git-core/git-http-backend
  • ps aux | grep fcgiwrap
  • Docker Compose Nginx 的主线线的用户
  • xxx.git 文件夹的权限,一般要求是 chmod -R 755 xxx.git
  1. fatal: unable to access 'https://xxxx/.git/': The requested URL returned error: 403

检查 xxx.git/config 文件,查看是否配置了 http.receivepack true

修改运行 fcgiwrap 的用户

系统默认是 www-data,如果需要修改成其他用户,比如 nginx,可以使用下面命令:

systemctl edit --full fcgiwrap.service

Nginx 反向代理 WebSocket 服务

· 阅读需 1 分钟
# 反向代理
server {
server_name domain.com;
listen 443 ssl;

# 通过 acme.sh 进行签署,具体见:https://blog.wangzhy.com/ssl#acmesh-%E7%AD%BE%E7%BD%B2%E8%AF%81%E4%B9%A6
ssl_certificate /etc/nginx/ssl/xxxx/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/xxxx/domain.com.key;

# SSL 增强配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;


location / {
proxy_pass http://host.docker.internal:9001;
# 基础代理头设置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# 连接数限制
proxy_http_version 1.1; # 明确指定HTTP1.1
proxy_buffers 8 16k; # 缓冲区优化
proxy_buffer_size 32k;

# WebSocket 专用设置
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

# 调整超时时间(秒)
proxy_read_timeout 86400; # 保持长连接
proxy_send_timeout 86400;
}
}