分类目录归档:技术笔记

对 tail -f 使用管道

来源: 对 tail -f 使用管道

最近发现 tail -f 时管道后面的程序都会被卡住,才发现 grep,sed,awk 不直接输出到 TTY 时都是带缓冲的。平时跟在 cat 后使用没问题是因为输入管道关闭触发了 flush。本文详细解释其中的坑,以及怎么让 sed, awk, grep 立即 flush。

TL;DR:grep 添加 --line-buffered,sed 添加 -u,awk 调 fflush()

管道和缓冲

管道 是 Linux/Unix 中进程间通信的一种方式,可以在命令间、进程间传递数据。比如下面的命令用来来打印所有文件不存在的异常。

cat log.txt | grep Error | grep ENOENT

由于 cat 命令会在读完文件后立即退出并关闭 STDOUT,grep 的缓冲会立即 flush,我们会在执行完上述命令后立即看到输出。但如果改成实时打印日志的 tail -f 则会看不到任何输出:

tail -f log.txt | grep Error | grep ENOENT

因为当 grep 的输出不是 TTY(终端) 时,会启用缓冲。输入关闭或缓冲区满时才输出。这个例子中第一个 grep 的输入 tail -f 一直没有关闭,因此缓冲一直不会输出,第二个 grep 也永远不会收到输入。 因此控制台不会有任何输出。

但如果反过来,grep 的输出是 TTY 时就不会缓冲。也就是说 tail -f log.txt | grep Error(注意少了一个 grep)会正常地持续地输出。

检查输出文件

那么 grep 会检查它输出到哪里?虽然理论上有悖于管道的设计,也不那么函数式。 难以想象我们有个函数,它的返回值竟然会取决于这个返回值下一步被用于做什么操作。 不仅是 grep,sed 也有类似的行为,这里不去更多地讨论设计,而是给几个有用的场景:

  1. 当输出到 TTY 时输出带颜色的字符,输出到文件时输出纯文本。
  2. 当输出到 TTY 时执行过程可以提示用户输入,输出到文件时则需要使用默认值或者报错。
  3. 以及 grep 的例子:输出到 TTY 时实时打印,输出到文件或其他程序时缓冲起来(因为尤其是写入磁盘文件时,没必要有输出就写)。

那么怎么判断标准输出的文件描述符呢?

注意 [ 是一个命令,-t 是它的参数,可以 man [ 查看详情。

缓冲区满

既然 tail -f 日志看不到输出是因为缓冲区没有 flush,那么缓冲区什么时候会被 flush 呢?有两种情况:

  1. 写入已经结束(类似 JavaScript 中的 Stream.prototype.end() 调用)。但是 tail -f 的输出流永远不会结束,因为 -f 会永远 follow 文件 append。作为对比,cat 命令的输出流会在读到文件尾时结束。比如执行 cat log.txt | grep Error 会立即 flush 并退出。
  2. 缓冲区满。既然叫做 Buffer 一定是有大小的,tail 写入足够多的内容后,grep 的缓冲区就会满,这时也会发生 flush。

那么 grep 的缓冲区是多大呢?既然 tail 的输出不足以填满缓冲区,我们用输出足够多的 yes 命令:

yes Error ENOENT | grep Error | grep ENOENT

yes 命令用来不断地循环(死循环,直到被 Ctrl-C)输出它的参数,因此缓冲很快会满。果然上面的命令我们可以看到大量的输出。

避免缓冲

grep 提供了 --line-buffered 来按照行缓冲,也就是每写满一行 flush 一次:

--line-buffered
     Force output to be line buffered.  By default, output is line buffered when standard output is
     a terminal and block buffered otherwise.

sed 可以用 --unbuffered 来禁用缓冲:

-u, --unbuffered
     load minimal amounts of data from the input files and flush the output buffers more often

awk 作为一门完整的编程语言,需要调用 fflush() 方法来清空缓冲:

The built-in function fflush(expr) flushes any buffered output for the file or pipe expr.

因此前面的例子中给 grep 添加 --line-buffered 即可让它持续地输出:

tail -f log.txt | grep --line-buffered Error | grep ENOENT

注意第二个 grep 不需要添加 --line-buffered,因为它的标准输出是 TTY,默认不会启用缓冲区。 下面是一个更完整的例子,从 log.txt 文件实时读日志,过滤包含 Error 的行,把 harttle 标记去掉,打印出第一列,再过滤得到 ENOENT 的行:

tail -f log.txt | grep --line-buffered Error | sed -u 's/harttle//' | awk '${print $1; fflush()}' | grep ENOENT

References

Vim 寄存器完全手册

转载自 Vim 寄存器完全手册

常见文本编辑器都会提供剪切板来支持复制粘贴,Vim 也不例外。 不同的是 Vim 提供了 10 类共 48 个寄存器,提供无与伦比的寄存功能。 最常用的 y 操作将会拷贝到默认的匿名寄存器中,我们也可以指定具体拷贝到哪个寄存器中。

一般来讲,可以用 "{register}y 来拷贝到 {register} 中, 用 "{register}p 来粘贴 {register} 中的内容。例如: "ayy 可以拷贝当前行到寄存器 a 中,而 "ap 则可以粘贴寄存器 a 中的内容。

除了 a-z 26 个命名寄存器,Vim 还提供了很多特殊寄存器。合理地使用可以极大地提高效率。例如:

  • "+p 可以粘贴剪切板的内容,
  • ":p 可以粘贴上一个 Vim 命令(比如你刚刚费力拼写的正则表达式),
  • "/p 可以粘贴上一次搜索关键词(你猜的没错,正是 normal 模式下的 /foo 搜索命令)。

在 Vim 中可通过 :reg 来查看每个寄存器当前的值。

寄存器分类

Vim 提供了 10 类寄存器,可在 Vim 中通过 :help registers 查看帮助。

  1. 匿名寄存器 ""
  2. 编号寄存器 "0"9
  3. 小删除寄存器 "-
  4. 26 个命名寄存器 "a"z
  5. 3 个只读寄存器 ":, "., "%
  6. Buffer 交替文件寄存器 "#
  7. 表达式寄存器 "=
  8. 选区和拖放寄存器 "*, "+, "~
  9. 黑洞寄存器 "_
  10. 搜索模式寄存器 "/

1. 匿名寄存器

使用 d, c, s, x 等会删除字符的命令时,被删除字符会进入匿名寄存器 ""。 你可以认为 "" 寄存器是一个指针,指向刚才被存到的寄存器。

如何用 Vim 搭建 IDE ? 一文中提到,Mac 下可通过下列设置来让 Vim 共享系统剪切板, 就是这个原理:所有删除和拷贝操作默认都会到匿名寄存器。

set clipboard=unnamed

使用 y 命令未指定寄存器会存到 "0 寄存器中,同时 "" 会与该寄存器保有同样的值。 这意味着你使用 p"p 总会得到同样的结果。

2. 编号寄存器

编号寄存器从 "0"9 共 10 个,其中 "0 保存着拷贝来的字符串,"1"9 保存着删除掉的字符串。 删除操作符包括 s, c, d, x。 删除掉的字符串会被存到 "1 中,上次删除的则会被存到 "2 中。以此类推,Vim 会保存你最近的 9 次删除。

  • 只有整行整行的删除,和通过段落级别的移动指令(包括 %,(,),/,`,?,n,N,{,}) 的删除才会被放到 "1 中。
  • 当用户指定拷贝操作的寄存器时(如 "ap),"0 不会被写入;但删除操作一定会被写入到 "1 中。

"0 寄存器很有用,比如我们 copy 了一段文本然后用它替换另一段文本。 这时默认寄存器 "" 中的值就变成了被替换文本,如果还需要用 copy 的文本继续替换的话就需要 "0p 了。

3. 小删除寄存器

不足一行的小删除则会被放到小删除寄存器中("-),起作用的删除操作符也包括 s, c, d, x。 例如:

dw    # 删除一个词
d9l   # 删除9个字符
cb    # 向前更改一个词

"0 寄存器类似,当用户指定寄存器并进行删除时,"- 不会被写入。

4. 命名寄存器

命名寄存器有 "a"z 共 26 个,这些寄存器只有当我们指定时才会被使用。 其实我们在录制宏时,所有键盘操作会以字符串的形式存到寄存器中。 例如录制一个宏存到 "a 寄存器中,内容为更改当前行 cc,改为 foo 字符串:

qaccfoo

然后执行 :reg 来查看寄存器,可以发现 a 寄存器的值是 ccfoo

小技巧:当使用小写字母进行操作时会覆盖当前寄存器内容,当使用大写字母进行操作时,会追加当前寄存器内容。

5. 只读寄存器

只读寄存器共 3 个,它们的值是由 Vim 提供的,不允许改变:

  • ".:上次 insert 模式中插入的字符串。还记得吗? . 命令可以重复上次操作,而 ". 存储了上次插入。
  • "%:当前文件名,不是全路径,也不是纯文件名,而是从当前 Vim 的工作目录到该文件的路径。例如此时 Harttle 的 Vim 中,"%p 的结果为 _drafts/vim-registers.md
  • "::上次命令模式下键入的命令。正如 @a 可以执行 "a 寄存器中的宏一样,@: 可以执行上次命令。

6. 交替文件寄存器

交替文件寄存器 "# 存储着当前 Vim 窗口(Window)的交替文件。交替文件(alternate file)是指 Buffer 中的上一个文件,可通过 Ctrl+^ 来切换交替文件与当前文件。

Window 和 Buffer 有什么区别?参见 Vim 多文件编辑:窗口 一文。

7. 表达式寄存器

表达式寄存器 "= 主要用于计算 Vim 脚本的返回值,并插入到文本中。 当我们键入 "= 后光标会移动到命令行,此时我们可以输入任何 Vim 脚本的表达式。 例如 3+2,按下回车并且 p 则会得到 5

这在我们调试 Vim 脚本时非常有用,比如调用一个函数看它是否有正确的返回值。

8. 选择和拖放寄存器

选择和拖放寄存器包括 "*, "+, 和 "~,这三个寄存器的行为是和 GUI 相关的。

"*"+ 在 Mac 和 Windows 中,都是指系统剪切板(clipboard),例如 "*yy 即可复制当前行到系统剪切板。 其他程序中复制的内容也会被存储到这两个寄存器中。 在 X11 系统中(绝大多数带有桌面环境的 Linux 发行版),二者是有区别的:

  • "* 指 X11 中的 PRIMARY 选区,即鼠标选中区域。在桌面系统中可按鼠标中键粘贴。
  • "+ 指 X11 中的 CLIPBOARD 选区,即系统剪切板。在桌面系统中可按 Ctrl+V 粘贴。

上文所述的 Mac 下 set clipboard=unnamed 会使得系统剪切板寄存器 "* 和 Vim 默认的匿名寄存器 "" 始终保有同样的值,即 Vim 和系统共用剪切板。

有文本拖拽到 Vim 时,被拖拽的文本被存储在 "~ 中。Vim 默认的行为是将 "~ 中内容插入到光标所在位置。 当然你可以给 `

` 做键盘映射。 ## 9. 黑洞寄存器 黑洞寄存器 `”_`,所有删除或拷贝到黑洞寄存器的文本将会消失。 这是为了在删除文本的同时不影响任何寄存器的值,`”_` 通常用于 Vim 脚本中。 ## 10. 搜索寄存器 搜索寄存器 `”/` 用于存储上一次搜索的关键词。Vim 中如何进行搜索呢? 在 normal 模式下按下 `/` 即进入 search 模式,输入关键字并按下回车即可。 该寄存器是可写的,例如 `:let @/ = “harttle”` 将会把 `”harttle”` 写入该寄存器。 下次搜索时不输入搜索词直接回车便会搜索 `”harttle”`。 ## 命令行模式拷贝 值得一提的时,任何寄存器中的值都是可以拷贝到命令模式下的。 比如对于寄存器 `”a` 中的值,在 normal 模式下可以通过 `”ap` 来粘贴;在 command-line 模式下通过 `a` 来粘贴。这一操作存在风险,因为寄存器中的值可能是从网页中拷贝来的。 如果寄存器中的字符串存在 ` ` 字符或 “ 字符,则会时 Vim 回到 normal 模式, 并继续执行寄存器中的命令。为了防范 _剪切板劫持_,可以添加下列的 Vim 配置: “` inoremap + u”+gP “` > 该命令的解释请移步:[http://vim.wikia.com/wiki/Pasting_registers](http://vim.wikia.com/wiki/Pasting_registers) ## 扩展阅读 – 剪切板与 X11 选区:[http://stackoverflow.com/questions/11489428/how-to-make-vim-paste-from-and-copy-to-systems-clipboard](http://stackoverflow.com/questions/11489428/how-to-make-vim-paste-from-and-copy-to-systems-clipboard) – Vikia-Pasting Registers: [http://vim.wikia.com/wiki/Pasting_registers](http://vim.wikia.com/wiki/Pasting_registers) – Vim Help: `:help registers`, `:help quotestar`, `:help quoteplus` # References – [Vim 寄存器完全手册](https://harttle.land/2016/07/25/vim-registers.html)

Vim 宏录制和重放

TL;DR

  1. qa: 录制到寄存器 a 中,可使用 a-z 26 个命名寄存器
  2. 开始操作,做的所有操作都会录制下来,重放时自动操作;
  3. q: 结束录制
  4. @a 重放一次;
  5. 100@a 重放100次;

例子

在编辑某个文件的时候,可能会出现需要对某种特定的操作进行许多次的情况,以编辑下面的文件为例:

;=====================================================================================;This is a sample configuration file when upgrading XXX using InstallShield.;Author:        ini_always;Date:          8/24/2011;Last modified: 9/20/2011;Note: Install script does NOT verify whether the configuration file is in a "WELL";format, a WRONG format may lead to installation failure.;If more information is needed, please check the document for details.;=====================================================================================

这是一个 ini 类型的配置文件,可以看到每一行的最前面有一个逗号,现在如果需要将每行前面的逗号去掉,怎么办?在第一行行首按x,然后按j,然后按x…这样重复下去?确实,我最开始也是这样的,但如果这个文件有100行要这样修改呢?或者1000行?

好吧,少废话,进入正题。所谓宏,在vim里面是指某种特定顺序的一系列操作,我们可以录制自己的操作序列,然后重复这个序列多次,以简化某种重复的操作。vim宏有录制和播放的过程,录制就是你教给vim该怎么操作,播放就是vim照着你教的进行自动操作。因此,对于上面的文件处理,首先要进行宏录制:

1.把光标定位在第一行;

2.在normal模式下输入qa(当然也可以输入qb, qc, etc,这里的a, b, c是指寄存器名称,vim会把录制好的宏放在这个寄存器中)(PS:如果不知道什么是vim的寄存器,请自行放狗搜之);

3.正常情况下,vim的命令行会显示“开始录制”的字样,这时候,把光标定位到第一个字符(按或者|),再按x删除,按j跳到下一行;

4.normal模式下输入q,结束宏录制。

好了,经过以上步骤,我们定义了一个存储在寄存器a中的宏,它的操作序列是:0->x->j,也就是跳到行首,删除,跳到下一行。

现在,第一行已经删除了行首的逗号,而且光标也已经在第二行,现在,在normal模式下输入@a,以播放我们刚录制好的存在寄存器a中的宏。于是,第二行行首的逗号也被删除,光标停在了第三行。

这也不简单啊?你肯定会这样想,要删除100行,我还得输入100个@a,我还不如手动删除呢。呵呵,vim早就想到了,输入7@a,好了,剩下的7行全部搞定了。(PS:在命令前面加数字,就是代表要执行这个命令多少次)

当然,这个例子很简单,但也很典型。利用好vim的宏,可以使一些原本很无聊的工作要简单很多。

References

ArchLinux 快速本地部署 DeepSeek

在 ArchLinux 下借助 Ollama 可以快速运行一个小参数体量的 DeepSeek 本地模型,在我的环境上 纯 CPU 推理也可以获得很快的体验。

安装环境

一行命令即可装好 ollama

$ sudo pacman -S ollama

模型

ollama 的 DeepSeek-R1模型页面 可以看到可拉取的模型,包括全量的 671b 模型,以及精炼的小模型表现同样出色。:

  • DeepSeek-R1
  • DeepSeek-R1-Distill-Qwen-1.5B
  • DeepSeek-R1-Distill-Qwen-7B
  • DeepSeek-R1-Distill-Llama-8B
  • DeepSeek-R1-Distill-Qwen-14B
  • DeepSeek-R1-Distill-Qwen-32B
  • DeepSeek-R1-Distill-Llama-70B

模型能力统计

运行

首先打开一个 终端,运行 ollama 服务端

$ ollama serve  
2025/02/18 14:57:36 routes.go:1187: INFO server config env="map[CUDA_VISIBLE_DEVICES: GPU_DEVICE_ORDINAL: HIP_VISIBLE_DEVICES: HSA_OVERRIDE_GFX_VERSION: HTTPS_PROXY: HTTP_PROXY: NO_PROXY: OLLAMA_DEBUG:false OLLAMA_FLASH_ATTENTION:false OLLAMA_GPU_OVERHEAD:0  
OLLAMA_HOST:http://127.0.0.1:11434 OLLAMA_INTEL_GPU:false OLLAMA_KEEP_ALIVE:5m0s OLLAMA_KV_CACHE_TYPE: OLLAMA_LLM_LIBRARY: OLLAMA_LOAD_TIMEOUT:5m0s OLLAMA_MAX_LOADED_MODELS:0 OLLAMA_MAX_QUEUE:512 OLLAMA_MODELS:/home/songtianlun/.ollama/models OLLAMA_MULTIU  
SER_CACHE:false OLLAMA_NOHISTORY:false OLLAMA_NOPRUNE:false OLLAMA_NUM_PARALLEL:0 OLLAMA_ORIGINS:[http://localhost https://localhost http://localhost:* https://localhost:* http://127.0.0.1 https://127.0.0.1 http://127.0.0.1:* https://127.0.0.1:* http://0.0.  
0.0 https://0.0.0.0 http://0.0.0.0:* https://0.0.0.0:* app://* file://* tauri://* vscode-webview://*] OLLAMA_SCHED_SPREAD:false ROCR_VISIBLE_DEVICES: http_proxy: https_proxy: no_proxy:]"  
time=2025-02-18T14:57:36.900+08:00 level=INFO source=images.go:432 msg="total blobs: 11"  
time=2025-02-18T14:57:36.900+08:00 level=INFO source=images.go:439 msg="total unused blobs removed: 0"  
time=2025-02-18T14:57:36.900+08:00 level=INFO source=routes.go:1238 msg="Listening on 127.0.0.1:11434 (version 0.5.7)"  
time=2025-02-18T14:57:36.901+08:00 level=INFO source=routes.go:1267 msg="Dynamic LLM libraries" runners="[cpu cpu_avx cpu_avx2]"  
time=2025-02-18T14:57:36.901+08:00 level=INFO source=gpu.go:226 msg="looking for compatible GPUs"  
time=2025-02-18T14:57:36.944+08:00 level=INFO source=gpu.go:392 msg="no compatible GPUs were discovered"  
time=2025-02-18T14:57:36.944+08:00 level=INFO source=types.go:131 msg="inference compute" id=0 library=cpu variant=avx2 compute="" driver=0.0 name="" total="30.7 GiB" available="7.2 GiB"

不要关闭窗口,在另外一个 shell 窗口运行以下命令启动并使用 deepseek-r1:1.5b 模型:

$ ➜  ~ ollama run deepseek-r1:1.5b  
>>>    
Use Ctrl + d or /bye to exit.  
>>>    
Use Ctrl + d or /bye to exit.  
>>> hello  

<think>  

</think>  

Hello! How can I assist you today? 😊e  

>>>

也可以用 cherry-studio 之类的工具对接使用:

CherryStudio 配置截图

CherryStudio 运行截图

实测 1.5B 模型运行非常流畅,可根据实际情况同样的方法尝试其他更多模型。

其他操作

ollama 其他常用操作如下:

# 在命令行中运行模型
ollama run <模型名称>

# 列出可用模型
ollama list

# 查看模型状态
ollama ps

# 删除模型
ollama rm <模型名称>

# 启动 API 服务
ollama serve

更多详情参见各官网。

References

Docker 官方脚本一键安装

使用官方脚本安装

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

curl -fsSL https://res.frytea.com/d/Dev/Docker/get_docker.sh | bash -s docker --mirror Aliyun

curl -fsSL https://res.frytea.com/d/Dev/Docker/get_docker.sh | DOWNLOAD_URL=http://192.168.25.10/docker  bash -s docker

# ustc
curl -fsSL https://get.docker.com -o get-docker.sh
sudo DOWNLOAD_URL=https://mirrors.ustc.edu.cn/docker-ce sh get-docker.sh

# 也可以自己在内网做一个镜像,将脚本和 docker 软件源全部镜像,可以实现全内网高速安装
curl -fsSL http://192.168.25.9/raw/general/docker/install-docker.sh -o get-docker.sh
sudo DOWNLOAD_URL=http://192.168.25.10/docker sh get-docker.sh

curl -fsSL http://192.168.25.9/raw/general/docker/install-docker.sh |  DOWNLOAD_URL=http://192.168.25.10/docker bash -s

安装完成后使能 docker 服务:

systemctl enable docker
syste sart docker
sudo usermod -aG docker $USER

参考文献

#Docker

s3cmd 常见用法

常见用法

  • 列举所有 Buckets
    $ s3cmd ls
  • 创建 bucket,且 bucket 名称是唯一的,不能重复。
    s3cmd mb s3://my-bucket-name
  • 删除空 bucket
    s3cmd rb s3://my-bucket-name
  • 列举 Bucket 中的内容
    s3cmd ls s3://my-bucket-name
  • 上传 file.txt 到某个 bucket,
    s3cmd put file.txt s3://my-bucket-name/file.txt
  • 上传并将权限设置为所有人可读
    s3cmd put --acl-public file.txt s3://my-bucket-name/file.txt
  • 批量上传文件
    s3cmd put ./* s3://my-bucket-name/
  • 下载文件
    s3cmd get s3://my-bucket-name/file.txt file.txt
  • 批量下载
    s3cmd get s3://my-bucket-name/* ./
  • 删除文件
    s3cmd del s3://my-bucket-name/file.txt
  • 来获得对应的bucket所占用的空间大小
    s3cmd du -H s3://my-bucket-name
  • 设置S3 bucket的Public权限
    s3cmd setacl s3://myexamplebucket.calvium.com/ --acl-public --recursive

临时链接

s3cmd signurl s3://example-bucket/image.jpg 1657457538
  • example-bucket — the name of the bucket that hosts the file,
  • image.jpg — the file you want to share,
  • 1657457538 — link expiry time in the Timestamp format, use the converter to convert time formats.

References

Rails 7 已有项目引入 Bootstrap 3

若是新项目,在 Rails 7 之后直接使用 rails new test -j esbuild --css bootstrap 即可,若是已有项目,使用下面步骤引入:

主要步骤

安装 cssbundling-rails 管理 CSS

bundle add cssbundling-rails
./bin/rails css:install:bootstrap

做了以下几件事情:

  • 它会创建 builds 文件夹,并在清单文件中链接它
  • 它会删除 application.css 文件,因为它会生成自己的文件。
  • 它添加了一个 package.json 文件来存储 Javascript 依赖项。
  • 它会安装 foreman gem,并为其生成一个配置文件。
  • 它添加了一个 bin/dev 脚本来启动 rails 服务器,并监视 CSS 文件的任何更改。
  • 它会创建特定于 Bootstrap 的 scss 文件,该文件将被捆绑到一个 application.css 文件中。
  • 它将安装 package.json 文件中列出的所有 Javascript 依赖项。
  • 它将 Bootstrap 字体路径附加到 assets 路径。
  • 它将 Bootstrap Javascript 导入添加到 application.js 文件中。
  • 最后,它配置 build:css 命令并运行它来构建 application.css 文件。

安装 jsbundling-rails 处理 JS 绑定

bundle add jsbundling-rails
./bin/rails javascript:install:esbuild

上述脚本做了这些事情:

  • 它会检查 builds 文件夹,但因为我们已经有了它,所以它不会做任何事情。
  • 它将 javascript_include_tag 添加到应用程序布局文件中。
  • 它在 foreman 配置文件中添加一个任务,以监视任何 Javascript 更改。
  • 它安装 esbuild 打包器,并尝试构建 Javascript 代码。

疑难问题

$ esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets
✘ [ERROR] Could not resolve "controllers"

    app/javascript/application.js:3:7:
      3 │ import "controllers"
        │        ~~~~~~~~~~~~~
        ╵        "./controllers"

  Use the relative path "./controllers" to reference the file "app/javascript/controllers/index.js".
  Without the leading "./", the path "controllers" is being interpreted as a package path instead.

1 error
node:child_process:866
    throw err;
    ^

Error: Command failed: /Users/cezar/Work/ror/bootstrap/node_modules/esbuild-darwin-arm64/bin/esbuild app/javascript/application.js --bundle --sourcemap --outdir=app/assets/builds --public-path=assets
    at checkExecSyncError (node:child_process:828:11)
    at Object.execFileSync (node:child_process:863:15)
    at Object.
<anonymous> (/Users/cezar/Work/ror/bootstrap/node_modules/esbuild/bin/esbuild:209:28)
    at Module._compile (node:internal/modules/cjs/loader:1099:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47 {
  status: 1,
  signal: null,
  output: [ null, null, null ],
  pid: 94418,
  stdout: null,
  stderr: null
}

Node.js v17.8.0
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

因为我们有一些 import maps 留下的代码,这与 jsbundling-rails gem 的工作方式相冲突。

因此,让我们解决这些问题。

修复安装

首先要做的是安装 turbo-railsstimulus 包。

yarn add @hotwired/turbo-rails
yarn add @hotwired/stimulus

然后,我将调整 application.js 文件中的导入路径,并删除旧的刺激导入。

--- a/app/javascript/application.js
+++ b/app/javascript/application.js
-import "controllers"
+import "./controllers";

--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
-import { application } from "controllers/application"
-
-// Eager load all controllers defined in the import map under controllers/**/*_controller
-import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
-eagerLoadControllersFrom("controllers", application)
-
-// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
-// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
-// lazyLoadControllersFrom("controllers", application)
+import { application } from "./application";

在应用程序布局文件中,我将删除 javascript_importmap_tags 帮助程序,因为它不再需要。

--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
     <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
-    <%= javascript_importmap_tags %>
     <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
   </head>

最后,我将取消链接其他 javascript 文件夹,只在清单中留下 builds 文件夹和 images 文件夹。

--- a/app/assets/config/manifest.js
+++ b/app/assets/config/manifest.js
 //= link_tree ../images
-//= link_tree ../../javascript .js
-//= link_tree ../../../vendor/javascript .js
 //= link_tree ../builds

安装 Bootstrap3

以上命令默认会安装最新的 Bootstrap, 如果像我一样需要安装旧版本,则按照以下步骤:

首先安装 Bootstrap 3jquery

yarn add bootstrap@3
yarn add jquery

之后创建一个 js 文件 app/javascript/add_jquery.js :

import jquery from 'jquery'
window.jQuery = jquery
window.$ = jquery

最后在 app/javascript/application.js 中引入即可:

...
import './add_jquery'  

import "jquery/dist/jquery"  
import "bootstrap/dist/js/bootstrap"
...

css 则是在 app/assets/stylesheets/application.bootstrap.scss 中写入:

+ @import 'bootstrap/dist/css/bootstrap';
@import 'bootstrap-icons/font/bootstrap-icons';

链接 debugger

以上操作后就可以使用 bin/dev 启动项目,会自动起来三个服务,这时如果使用 debugger 启动调试,就可以使用以下命令链接:

rdbg --attach

References

Ruby 多版本管理工具 rbenv

rbenv (Simple Ruby Version Management) 允许您在 Ruby 的多个版本之间轻松切换。它简单、不引人注目,并遵循 UNIX 的传统,即只做一件事的单一用途工具。

安装方法

安装 rbenv

# Archlinux
sudo pacman -S rbenv
# macOS
brew install rbenv
# Debian/Ubuntu
sudo apt install rbenv
# Fedora/RHEL
sudo dnf install rbenv

安装 ruby-build

使用 rbenv 安装 ruby 版本需要用到 install 命令,但是该 rbenv install 命令不是开箱即用的 rbenv 提供的,而是由 ruby-build 插件提供的,下面一同安装:

# Archlinux
sudo pacman -S ruby-build
# macOS
brew install ruby-build

使用方法

# 首先初始化
rbenv init

# 查看可安装版本
rbenv install -l # list latest stable versions
rbenv install -L # list all local versions

# 安装某个版本
rbenv install 3.1.2

# 使用某个版本
rbenv global 3.1.2 # set the default Ruby version for this machine
rbenv local 3.1.2  # set the Ruby version for this directory

References

使用 GCOP AI 助力提升 git commit 效率

最近在 阮老师博客 看到一款这个 GCOP,一个基于 LLM 的 Git 提交消息生成和 Git 工作流程优化工具,试用了一下确实不错。接入 LLM 即可自动分析 git 修改生成 commit 消息,比我之前做的 git-commit 生成器好用多了。

以下是一些简单介绍:

简介

GCOP (Git Copilot) 是一个 Git 智能助手,它使用 AI 来自动生成 Git 提交消息,帮助开发者优化 Git 工作流程。

主要功能

  1. 智能提交消息:自动分析代码变更,生成高质量、定制化的提交消息,并提供多种细粒度配置,确保团队提交标准的一致性
  2. 灵活的 AI 集成:支持配置各种 LLM 模型(如 OpenAI GPT、Claude、Deepseek 等)
  3. 简化的 Git 命令:内置 20+ 直观的 Git 快捷操作和命令,提供直观的命令别名,如 git c 用于 AI 辅助提交,优化 Git 工作流程
  4. 无缝工作流集成:增强现有 Git 工作流程,无需改变使用习惯

优点

🎨 高度可定制化体验

  • 提供全局配置和项目级配置,配置项目特定设置,确保团队标准的一致性
  • 设计专属提交模板以匹配项目需求
  • 通过自定义提示优化 git message 的风格

⚡ 流畅的开发者体验

  • 内置 20+ 直观的 Git 快捷操作和命令,智能别名让复杂的 Git 操作轻而易举
  • 支持各种 LLM 模型(OpenAI、Anthropic、Deepseek 等)
  • 零配置集成至现有的 Git 工作流

📚 智能学习能力

  • 自动学习您的代码库提交历史
  • 随时间适应团队的提交习惯
  • 通过持续学习提升信息质量

项目完全开源,使用 MIT 协议,基于 Python 开发。

Github: https://github.com/Undertone0809/gcop
官网: https://gcop.zeeland.top/

使用方法

安装配置参考官网 Quick Start Guide 即可,mac 下可以结合 conda 安装,之后大概按照这样配置:

model:
  model_name: openai/gpt-4o-mini
  api_key: sk-xxxx
  api_base: https://api.xxx.io/v1

即可使用。

效果

(base) songtianlun@songtianlundeMacBook-Air sample_rails_app % git ac
[Code diff] 
diff --git a/.gitignore b/.gitignore
index afd85fd..daa7023 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,5 @@
 !/app/assets/builds/.keep

 /node_modules
+
+.idea
[On Ready] Generating commit message...
[Thought] The changes involve the removal of several .idea configuration files and the 
addition of .idea to the .gitignore file. This indicates a cleanup of IDE-specific files 
that are not necessary for version control, which can help reduce clutter in the repository.
[Generated commit message]
chore: remove IDE configuration files

- Deleted .idea/misc.xml, .idea/modules.xml, and .idea/sample_rails_app.iml files
- Added .idea to .gitignore to prevent future IDE files from being tracked

This commit cleans up the repository by removing IDE-specific configuration files that are 
not needed for the project. It also ensures that any future IDE files will be ignored, 
keeping the repository clean and focused on the actual project code.
? Do you want to commit the changes with this message? yes
[main d86005c] chore: remove IDE configuration files
 6 files changed, 2 insertions(+), 373 deletions(-)
 delete mode 100644 .idea/.gitignore
 delete mode 100644 .idea/misc.xml
 delete mode 100644 .idea/modules.xml
 delete mode 100644 .idea/sample_rails_app.iml
 delete mode 100644 .idea/vcs.xml
(base) songtianlun@songtianlundeMacBook-Air sample_rails_app % git push

常用命令

$ git ghelp                
/opt/homebrew/Caskroom/miniconda/base/lib/python3.12/site-packages/pydantic/_internal/_config.py:345: UserWarning: Valid config keys have changed in V2:
* 'fields' has been removed
  warnings.warn(message, UserWarning)

gcop is your local git command copilot
Version: 1.7.3
GitHub: https://github.com/Undertone0809/gcop

Usage: gcop [OPTIONS] COMMAND

Commands:
  git p          Push the changes to the remote repository
  git pf         Push the changes to the remote repository with force
  git undo       Undo the last commit but keep the file changes
  git ghelp      Add command into git config
  git gconfig    Open the config file in the default editor
  git gcommit    Generate a git commit message based on the staged changes and commit the 
changes
  git c          The same as `git gcommit` command
  git ac         The same as `git add . && git gcommit` command
  git acp        The same as `git add . && git gcommit && git push` command
  git cp         The same as `git gcommit && git push` command
  git amend      Amend the last commit, allowing you to modify the commit message or add 
changes to the previous commit
  git info       Display basic information about the current git repository

改好代码之后 git ac && git push 两行命令搞定标准格式提交,很方便。

References

Linux buffer-cache 占用过高性能调整

什么是 buff/cache?

Linux 2.4 的内存管理中,buffer 指 Linux 内存的:Buffer cachecache 指 Linux 内存中的:Page cache。一般呢,是这么解释两者的。

  • A buffer is someting that has yet to be ‘written’ to disk.
  • A cache is someting that has been ‘read’ from the disk and stored for later use.

翻译过来就是说:

  1. buffer (buff) 是用来缓存尚未 “写入” 磁盘的内容。
  2. cache 是用来缓存从磁盘 “读取” 出来的东西。

所以 buffer 被用来当成对 io 设备写的缓存。而 cache 被用来当作对 io 设备的读缓存。这里的 io 设备,主要指的是块设备文件和文件系统上的普通文件。

但是在 Linux 2.6 以后,它们的意义不一样了。

Linux 2.6 之后 Linux 将他们统一合并到了 Page cache 作为文件层的缓存。而 buffer 则被用作 block 层的缓存。
block 层的缓存是什么意思呢,你可以认为一个 buffer 是一个 physical disk block 在内存的代表,用来将内存中的 pages 映射为 disk blocks,这部分被使用的内存被叫做 buffer

buffer 里面的 pages,指的是 Page cache 中的 pages,所以,buffer 也可以被认为 Page cache 的一部分。

或者简单来说,buffer 负责裸设备相关的缓存,cache 负责文件系统的缓存。

Buffer 的具体职责

在当前的系统实现里,buffer 主要是设计用来在系统对块设备进行读写时作为缓存来使用。这意味着对块的操作会使用 buffer 进行缓存,比如我们在格式化文件系统的时候。

但是一般情况下两个缓存系统是一起配合使用的,比如当我们对一个文件进行写操作的时候,cache 的内容会被改变,而 buffer 则用来将 cachepage 标记为不同的缓冲区,并记录是哪一个缓冲区被修改了。

这样,内核在后续执行脏数据的回写(writeback)时,就不用将整个 page 写回,而只需要写回修改的部分即可。

Cache 的具体职责

cache 主要用来作为文件系统上的文件数据的缓存来用,当进程对文件有 read/write 操作的时候。包括将文件映射到内存的系统调用 mmap,就会用到 cache

因为 cache 被作为文件类型的缓存来用,所以事实上也负责了大部分的块设备文件的缓存工作。

怎么回收 buff/cache?

Linux 内核会在内存将要耗尽的时候,自动触发内存回收的工作,以便释放出内存给急需内存的进程使用。

但是这种回收的工作也并不是没有成本。

理解 cache 是干什么的就知道,cache 中存在着一部分 write 操作的数据。所以必须保证 cache 中的数据跟对应文件中的数据一致,才能对 cache 进行释放。

于是伴随着 cache 清除的行为的,一般都是系统 IO 飙高。这是因为内核要将 cache 中缓存的 write 数据进行回写。

我们可以使用下面这个文件来人工触发缓存清除的操作,Linux 提供了三种清空方式:

  1. echo 1 > /proc/sys/vm/drop_caches # 仅清除页面缓存
  2. echo 2 > /proc/sys/vm/drop_caches # 清除目录项和 inode
  3. echo 3 > /proc/sys/vm/drop_caches # 清除页面缓存、目录项以及 inode

但是这种放时只能在执行的当时起作用,过一段时间之后又会发现内存被占满,怎么办呢?

实际上内核提供了 vm.vfs_cache_pressure 参数用来控制缓冲区的回收频率,我们可以调整它。

这个参数是用来控制内核回收 VFS 缓存的频率。修改这个值会提高或者降低回收 VFS 缓存的频率。值可以设置为 0-200 中的任意值。越大回收频率越快,可以把 vm.vfs_cache_pressure 赋值为 200 来获得最快的回收频率。这个值默认值一般为 100

另外也可以使用 slabtop 分析内存使用情况。一般情况下,dentry*_inode_cache 值越高回收的效果越好。

为什么是 dentry*_inode_cache 呢,这是因为当读写文件时内核会为该文件对象建立一个 dentry,并将其缓存起来,方便下一次读写时直接从内存中取出提高效率。

一些措施

内核参数

可以通过配置 vm.vfs_cache_pressure 参数,控制系统回收 directory entries 和 inode 缓存的倾向程度

Linux Kernel 文档描述如下:

此百分比值控制内核回收用于缓存 directoryinode 对象的内存的趋势。 在默认值 vfs_cache_pressure=100 时,内核将尝试以 pagecacheswapcache 回收的“公平”速率回收 dentryinode。减小 vfs_cache_pressure 会导致内核更倾向于保留 dentryinode 缓存。当 vfs_cache_pressure=0 时,由于内存压力,内核永远不会回收 dentryinode,这很容易导致内存不足的情况。将 vfs_cache_pressure 增加到 100 以上会导致内核更喜欢回收 dentryinode。 将 vfs_cache_pressure 显著增加到 100 以上可能会对性能产生负面影响。回收代码需要使用各种锁来查找可释放的目录和 inode 对象。当 vfs_cache_pressure=1000 时,它将查找比可用对象多 10 倍的可用对象。

可尝试将该值调整为 200 使得比默认值更积极地回收缓存,要持久化配置 vm.vfs_cache_pressure 为 200你可以通过以下几种方法:

  1. 通过 /etc/sysctl.conf 文件(推荐方法):
# 使用文本编辑器打开 /etc/sysctl.conf
sudo vim /etc/sysctl.conf

# 添加或修改以下行
vm.vfs_cache_pressure = 200

# 使配置生效
sudo sysctl -p
  1. 通过创建 /etc/sysctl.d/ 目录下的配置文件:
# 创建新的配置文件
sudo vim /etc/sysctl.d/99-vfs-cache-pressure.conf

# 添加以下行
vm.vfs_cache_pressure = 200

# 使配置生效
sudo sysctl --system

手动清理

$ sync
$ echo 1 > /proc/sys/vm/drop_caches
$ echo 2 > /proc/sys/vm/drop_caches
$ echo 3 > /proc/sys/vm/drop_caches

自动定时清理

不推荐这么做

1、创建脚本cleanCache.sh

#!/bin/bash#每两小时清除一次缓存
echo "开始清除缓存"
sync;sync;sync #写入硬盘,防止数据丢失
sleep 10#延迟10秒
echo 1 > /proc/sys/vm/drop_caches
echo 2 > /proc/sys/vm/drop_caches
echo 3 > /proc/sys/vm/drop_caches

2、创建定时任务

crontab -e #弹出配置文件

3、添加定时任务执行频率

#分  时  日  月  周  命令
0 */2 * * * /usr/local/bin/cleanCache.sh

4、设置crond启动以及开机自启

systemctl start crond.service
systemctl enable crond.service

5、查看定时任务是否被执行

cat /var/log/cron | grep cleanCache

References