跳到主要内容

awk 命令基本使用

· 阅读需 4 分钟

awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。

默认分隔符是:空白键或者 tab 建,以 \n 为一行

$0:所有域
$n:第 n 个域

1、显示出最近登录成功的用户

使用默认分隔符

last -n 5 | awk '{print $1}'

2、查看用户

通过 -F ':' 指定分隔符

# 下面两个命令等效
cat /etc/passwd | awk -F ':' '{print $1}'
cat /etc/passwd | awk -F: '{print $1}'

3、查看用户的时候显示出 bash

通过双引号对 \t 转义

cat /etc/passwd | awk -F ':' '{print $1"\t"$7}'

4、BEGIN、END 使用

cat /etc/passwd | awk -F ':' 'BEGIN {"name,bash"} {print $1","$7} END {print "blue,/bin/nosh"}'

5、匹配含有关键字的行

'/pattern/action' 指定正则表达式和 action

awk -F ':' '/root/' /etc/passwd
awk -F ':' '/root/{print $7}' /etc/passwd

6、awk 内置变量

ARGC               命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FILENAME awk浏览的文件名
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行 -F选项
NF 浏览记录的域的个数
NR 已读的记录数
OFS 输出域分隔符
ORS 输出记录分隔符
RS 控制记录分隔符
awk -F ':' '{print $1 "\tfilename:" FILENAME "\t\tARGC:" ARGC "\tFNR:" FNR}' aa.txt 

7、print、printf

print函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。

printf函数,其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。

8、自定义变量

awk -F ':' 'BEGIN {count=0; print "[start] user count is "count}{ count++; print $1"\t" count} END {print "user count is " count}' /etc/passwd

统计某个文件夹下的文件占用的字节数

ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size}'

如果以M为单位显示:

ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size/1024/1024,"M"}'

9、显示账户

awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;}; END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd

查看拒绝访问的 IP

awk -F ':' '{print $2}' /etc/hosts.deny| sort | uniq -c | awk '{print $1"\t"$2}' | grep  '^[0-9]' 

MongoDB 常用查询语句/技巧

· 阅读需 1 分钟

转换为 UTC 时间字符串

// 转换为 UTC 时间字符串
db.collection.find({
timestamp: { $gte: new Date( new Date("2025-03-22T00:00:00+08:00").toISOString()) }
});

scoop

· 阅读需 2 分钟

ref

https://linux.do/t/topic/566873

Install

1. 调整执行策略

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

2. 安装

irm get.scoop.sh | iex

3. 检验是否安装成功

Initializing...
Downloading...
Creating shim...
Adding ~\scoop\shims to your path.
Scoop was installed successfully!
Type 'scoop help' for instructions.
scoop help

Usage

Bucket

buckets 是存储应用程序清单的仓库,每个 bucket 包含了一系列 JSON 文件,每个文件对应一个软件包,描述如何下载、安装以及依赖等信息。

需要有 git 环境

scoop install git
scoop bucket add extras
scoop bucket add java
scoop bucket add nerd-fonts
scoop bucket add versions
scoop bucket add nonportable

更新 scoop 和 buckets

scoop update

更新安装的软件

scoop update *

安装软件

scoop install 7zip
scoop install geekuninstaller
scoop install everything
scoop install fastcopy
scoop install apifox
scoop install localsend
scoop install screentogif
scoop install sumatrapdf

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

= 与 := 的区别

· 阅读需 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:
// 未知错误类型
}