Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

之前就比较好奇,命令行一般输出都是往后追加,好像不知道怎么做覆盖更新,但是很多比较厉害的shell脚本都有那种进度条,甚至整个重绘
所以就来看下,怎么在命令行里输出个会变的进度条,
比如我在命令行中输出两行

1
2
3
echo "111\n";
echo "222\n";
exit;

最简单就是这样子,那如果想把111改成222呢
我们发现”\n”是换行,那”\r”其实是原地移动到行首
可以再看下

1
2
3
4
echo "111\r";
usleep(1000*1000);
echo "222\n";
exit;

加了sleep是因为闪太快了看不到,这样就能看到这一行就重新刷新了
然后就可以看下进度条可以怎么输出了

1
2
3
4
5
6
for ($i = 0; $i <= 50; $i++) {
printf("progress: [%-50s] %d%%\r", str_repeat('#',$i), $i * 2);
usleep(1000 * 100);
}
echo "\n";
echo "Done.\n";

首先是解释下这段的含义

1
2
3
4
5
"progress: [%-50s] %d%%\r"
└──┬──┘ │ │ └─ 回到当前行开头
│ │ └──── 输出一个真正的 %
│ └────── 输出整数
└──────────── 输出宽度为 50 的字符串

感觉又变成了一个视觉游戏,以为是逐渐的在往后画
其实是一直在刷新这个进度条
首先是定义一个长度是50个字符的输出,用空格补全,然后会输出对应百分比,比如2%
就对应的是一个井号,然后用”\r”回到行首重绘
实际的就是第一次画一个#号,第二次画两个#号
这里最大的重点就是要让部分字符看着在变,部分看着不变,才能形成这种视觉假象
比如这里的50个字符长度,还有对应的”%”符号
否则就会变成一直在跳的了
对应的动的部分就是#号的增加,对应百分比数值也在往上涨
不过这里有点小问题,不知道有没有发现,它的数字没有定义位数
那么在达到100%的时候其实是有一点跳跃的

那如果想要改多行怎么办呢
就需要用ANSI 控制序列让光标向上移动若干行

1
2
3
任务 A:[########          ] 40%
任务 B:[############ ] 60%
总进度:[########## ] 50%

光标此时位于这三行下面。下一次刷新前,先让光标向上移动三行:

1
ESC [ 3 A

在php中可以写成

1
echo "\033[3A";

最简单的就是

1
2
3
4
5
6
echo "111\n";
echo "222\n";
echo "333\n";
echo "\033[3A";
usleep(1000*1000);
echo "444\n";

这样就会把前面的111,222,333给干掉,重新输出444
来个像样点的脚本就是

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
<?php

function progressBar(int $percent, int $width = 30): string
{
$percent = max(0, min(100, $percent));
$filled = (int) floor($percent * $width / 100);

return sprintf(
"[%-{$width}s] %3d%%",
str_repeat('#', $filled),
$percent
);
}

$lineCount = 3;

for ($i = 0; $i <= 100; $i++) {
$taskA = $i;
$taskB = min(100, $i * 2);
$total = (int) (($taskA + $taskB) / 2);

// 第一次输出前不需要移动光标
if ($i > 0) {
printf("\033[%dA", $lineCount);
}

$lines = [
"任务 A:" . progressBar($taskA),
"任务 B:" . progressBar($taskB),
"总进度:" . progressBar($total),
];

foreach ($lines as $line) {
// 回到行首并清除当前整行
echo "\r\033[2K";
echo $line;
echo PHP_EOL;
}

fflush(STDOUT);
usleep(100000);
}

这里的符号代表的含义

控制序列作用
\r回到当前行行首
\033[2K清除当前整行
\033[3A光标向上移动三行
\033[1A光标向上移动一行
\033[1B光标向下移动一行
\033 表示 ESC 字符,所以:
1
echo "\033[3A";

发送给终端的实际含义是:

1
ESC + [3A

也就是“光标向上移动三行”。
还是比较神奇的东西
以前一直没搞明白,刚好这次想到了
现在很多想claude code,包括opencode,pi等都是搞成了命令行的
这个大概就是这些命令行工具的基础
像opencode是使用的

1
2
3
@opentui/core
@opentui/solid
solid-js

pi则是使用

1
2
@earendil-works/pi-tui
其他的框架还有
语言开源库定位
TypeScriptOpenTUIOpenCode 使用的完整 TUI
TypeScriptpi-tuiPi 使用的轻量差量渲染
JavaScriptInk使用 React 编写 CLI
JavaScriptcli-progress单个或多个进度条
GoBubble TeaElm 风格 TUI 框架
RustRatatuiRust 生态常用 TUI
PHPSymfony Console进度条、表格和多区域刷新
PHP 里最省事的是 Symfony Console 的 section()
1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\Console\Output\ConsoleOutput;

$output = new ConsoleOutput();
$section = $output->section();

$section->writeln("任务 A:10%\n任务 B:20%\n任务 C:30%");

sleep(1);

$section->overwrite(
"任务 A:40%\n任务 B:50%\n任务 C:60%"
);

最近给一台联想 G400 重装 Windows 7。本来以为只是做个启动盘、装好系统就结束了,实际操作时却遇到了驱动缺失、旧版 IE 无法打开部分网站、Windows Update 报错,以及显卡驱动依赖 .NET Framework 等一连串问题。这里把完整过程和用到的资料整理一下,方便以后再遇到类似的老电脑时查阅。

先提醒一句:Windows 7 已于 2020 年 1 月 14 日结束支持,不再获得常规安全更新;Chrome 109 也早已停止更新。因此,这套方案更适合需要兼容老软件或老硬件的场景,不建议把它作为长期联网、登录重要账号或进行支付操作的主力系统。

一、安装前的准备

1. 下载 Windows 7 SP1 镜像

我使用的是 Windows 7 SP1 原版镜像,可以从 MSDN,我告诉你 查找。

需要注意的是,这个网站不是微软官方网站,而是第三方原版软件信息收录站。下载时应选择与授权和硬件相符的版本,并核对镜像的 SHA-1 等校验值,避免使用被二次修改、捆绑软件或来源不明的镜像。

联想 G400 通常可以安装 64 位系统。如果机器内存很小,或者有必须使用 32 位系统的老软件,再根据实际情况选择 x86 版本。后面下载补丁和驱动时,系统架构也必须保持一致。

2. 提前下载驱动

Windows 7 对很多硬件并不能做到安装后自动识别,最麻烦的情况是系统装好了,却因为有线网卡和无线网卡都没有驱动而无法联网。因此,重装前最好先从 联想 G400 驱动下载页面 下载对应版本的驱动,放到另一个 U 盘中。

建议至少准备这些驱动:

  • 芯片组驱动;
  • 有线网卡和无线网卡驱动;
  • 核芯显卡和独立显卡驱动;
  • 声卡、触控板、读卡器等驱动。

G400 有不同的硬件配置,最好根据机器背面的具体型号或主机编号查找,不要只看系列名称。网卡驱动尤其可以多准备一个可能的版本,以免装完系统后完全无法联网。

3. 提前下载补丁和常用软件

为了避免装好系统后才发现网站打不开,建议把下面这些内容也提前放进 U 盘:

  • .NET Framework 4 独立安装包;
  • IE11 安装包;
  • 本文后面提到的 Windows 7 补丁;
  • Chrome 109 离线安装包;
  • 已下载好的联想官方驱动。

二、制作启动 U 盘并安装系统

我使用 UltraISO 将 Windows 7 SP1 镜像写入 U 盘。操作前一定要确认目标盘符,因为写入过程会清空 U 盘中的原有数据。

制作完成后,从联想 G400 的启动菜单中选择 U 盘,按照安装向导完成系统安装即可。如果进入安装界面后出现键盘、鼠标或 U 盘突然无法使用的情况,可以先换到 USB 2.0 接口再试;Windows 7 安装环境对 USB 3.0 的原生支持并不完善。

分区或格式化系统盘前,务必确认重要文件已经备份。安装完成后也不要急着联网,先把基础驱动和补丁装好。

三、安装驱动和 .NET Framework 4

驱动建议按下面的顺序安装:

  1. 芯片组驱动;
  2. 有线网卡或无线网卡驱动;
  3. 核芯显卡和独立显卡驱动;
  4. 声卡、触控板、读卡器等其他驱动。

这次安装显卡驱动时,安装程序提示缺少 .NET Framework 4。可以从微软官方下载 .NET Framework 4 独立安装程序,安装并重启后,再继续安装显卡驱动。

驱动全部安装完成后,可以打开“设备管理器”,检查是否还存在带黄色感叹号的设备。

四、先安装 Windows 7 汇总补丁

刚装好的 Windows 7 SP1 与现在的网络环境相差太远,直接使用 Windows Update 往往会失败。可以先手动安装微软的便利汇总补丁 KB3125574,它包含了 Windows 7 SP1 发布之后到 2016 年 4 月之间的大部分更新。

这里有一个容易遗漏的前置条件:安装 KB3125574 之前,必须先安装 2015 年 4 月的服务堆栈更新 KB3020369

推荐顺序如下:

  1. 在 Microsoft Update Catalog 中下载并安装 KB3020369,然后重启;
  2. 下载并安装 KB3125574,然后再次重启。

下载时要认准 Windows 7,并根据当前系统选择 x86 或 x64 包,不要误下成 Windows Server 2008 R2 版本。安装 KB3125574 还需要给系统盘预留至少 4 GB 可用空间。

如果提示“此更新不适用于你的计算机”,优先检查以下几项:

  • 当前系统是否已经是 Windows 7 SP1;
  • 下载的补丁架构是否与系统一致;
  • 前置补丁是否已经安装;
  • 该补丁是否已经安装,或被更新的补丁取代。

五、安装 IE11

系统补丁安装完成后,可以安装微软提供的 Windows 7 版 IE11(64 位)。IE11 本身也已经停止支持,这里安装它主要是为了改善老系统的网页兼容性,并补齐部分旧软件依赖,不建议把它当作日常浏览器。

如果刚装好 Windows 7 时打不开补丁目录或下载页面,可以在另一台正常联网的电脑上先把安装包下载好,再通过 U 盘复制过去。不要为了打开网站而关闭证书校验或安装来源不明的所谓“证书修复包”。

六、解决 Windows Update 的 80072EFE 错误

如果运行 Windows Update 时出现 80072EFE,可以参考这篇 Reddit 讨论,手动安装下面三个补丁:

  1. KB4490628:2019 年 3 月服务堆栈更新;
  2. KB4474419:SHA-2 代码签名支持更新;
  3. KB3138612:Windows Update Client 更新。

建议按上面的顺序逐个安装。遇到提示需要重启时就先重启,再继续安装下一个;三个补丁安装完毕后,再次重启并运行 Windows Update。

如果 Windows Update 仍然失败,可以先确认系统时间、时区是否正确,并检查有线或无线网络是否稳定。由于 Windows 7 已结束支持,即使更新功能恢复,也不能把它理解为系统重新获得了完整、持续的安全支持。

七、浏览器选择

Windows 7 能使用的最后一个 Chrome 大版本是 Chrome 109,后续版本已经不再支持 Windows 7。如果确实需要,可以安装 109 版本,但它已经不能获得新的功能和安全修复。

因此,Chrome 109 只适合临时访问普通网页或兼容老系统,不要用它登录网银、邮箱等重要账号。离线安装包也应尽量从可信渠道取得,并核对数字签名,不建议随便从软件站下载所谓的“绿色版”或“特别版”。

八、完整安装顺序

最后把这次的操作顺序汇总一下:

  1. 备份原系统中的重要数据;
  2. 下载 Windows 7 SP1 镜像并用 UltraISO 制作启动 U 盘;
  3. 提前准备联想 G400 驱动、系统补丁和离线安装软件;
  4. 安装 Windows 7 SP1;
  5. 安装芯片组和网卡驱动;
  6. 安装 .NET Framework 4,再安装显卡及其他驱动;
  7. 依次安装 KB3020369KB3125574
  8. 安装 IE11;
  9. 遇到 80072EFE 时,依次安装 KB4490628KB4474419KB3138612
  10. 根据需要安装 Chrome 109,并尽量减少这台机器的联网用途。

这次最深的感受是,给老电脑装 Windows 7,真正花时间的并不是系统安装本身,而是提前准备驱动、补齐系统组件和解决更新问题。把驱动和补丁都提前放进 U 盘,整个过程会省心很多。

参考资料

我的博客的推送一份是在github
作为github page,另一份是在自建的gitea上面
上周突然就出现推送失败了
在gpt5.5老师的指导下一顿查gitea服务器的日志也没查出来原因
一开始说是可以尝试从https切换到ssh形式也没起作用
服务端日志也没有用的
然后这次升级了gpt5.6老师以后,就再问了下

1
2
3
4
send-pack: unexpected disconnect while reading sideband packet
Writing objects: 100% (6455/6455), 2.95 MiB | 1.53 MiB/s, done.
Total 6455 (delta 2752), reused 6281 (delta 2580), pack-reused 0
fatal: the remote end hung up unexpectedly

这次的日志比较有价值可能
另外还有个信息是之前git gc因为仓库的小文件太多了可能
然后这次gpt老师给出的方法是

1
2
http.postBuffer = 16777216
http.version = HTTP/1.1

把postBuffer给调大了,然后直接在仓库里执行

1
git -C .deploy_git push --verbose

发现直接可以了
具体原因分析下来是: 推送产生的请求体约为 2.95 MiB,超过了 Git 默认 1 MiB 的 http.postBuffer。

1
2
3
4
5
6
7
8
9
10
11
推送数据 2.95 MiB
↓ 超过默认缓冲区 1 MiB
Git 改用 chunked 分块传输

Traefik / Gitea 链路没有正确处理该请求

返回 HTTP 200,但响应体为 0

Git 收不到 receive-pack 的 sideband 结果

unexpected disconnect

因为默认的postBuffer不够大,然后使用了chunked传输,是指一块块传,直到最后一块大小为0
然后因为我的gitea可能经过了traefik和gitea中间有不支持的
关于postBuffer在git官方文档也有解释

http.postBuffer

1
2
3
Maximum size in bytes of the buffer used by smart HTTP transports when POSTing data to the remote system. For requests larger than this buffer size, HTTP/1.1 and Transfer-Encoding: chunked is used to avoid creating a massive pack file locally. Default is 1 MiB, which is sufficient for most requests.

Note that raising this limit is only effective for disabling chunked transfer encoding and therefore should be used only where the remote server or a proxy only supports HTTP/1.0 or is noncompliant with the HTTP standard. Raising this is not, in general, an effective solution for most push problems, but can increase memory consumption significantly since the entire buffer is allocated even for small pushes.

翻译下

1
2
3
4
5
Git 使用智能 HTTP 传输协议向远程服务器发送 POST 请求时,所使用缓冲区的最大大小,单位为字节。
当请求大小超过这个缓冲区时,Git 会使用 HTTP/1.1 的 Transfer-Encoding: chunked,也就是分块传输编码,以避免在本地创建一个非常大的 pack 文件。
默认值是 1 MiB,对于大多数请求来说已经足够。
需要注意的是,增大这个值的作用仅仅是停用分块传输编码。因此,只有在远程服务器或中间代理仅支持 HTTP/1.0,或者没有正确遵循 HTTP 标准时,才应该提高这个限制。
一般来说,增大这个值并不能解决大多数 Git 推送问题。同时它还可能显著增加内存占用,因为即使实际推送的数据很小,Git 也会分配完整大小的缓冲区。

对于我这样的hexo是在.deploy_git目录里的.git/config文件
普通git仓库如果要配置就是在.git/config里进行配置

1
2
3
[http]
postBuffer = 16777216
version = HTTP/1.1

可以直接改这个文件,也可以用git配置命令

1
2
git config http.postBuffer 16777216
git config http.version HTTP/1.1

这样都可以,在现在这个时代
发现这样一个小技巧或者小问题的答案
我还是想记录下
因为很多的东西感觉已经被GPT这样的颠覆掉了
有这些小的问题被解决
感觉有种久违的愉悦感
排查问题,找到原因是个很有意思的过程
并把它解决也是很有意思
这个可能就算是初心了吧
就能把解决问题的思路方法分享下
也是一种比较开始的事情

最近因为看了下 Chrome 里面置入的小模型,顺手又看了下 Chrome 扩展。

之前其实也接触过一些浏览器扩展,但是一直没有特别系统地看过它的结构,感觉好像就是写一个 html,然后再配几个 js 文件就可以了。

但是真正写起来以后,会发现 Chrome 扩展里面有几个不同的运行环境,尤其是 popupcontent script,如果没有分清楚,后面调用网页内容或者操作页面 DOM 的时候就容易有点混乱。

这次先做一个比较简单的例子,点击扩展按钮以后,给当前页面加一个背景色,再在右下角显示一个提示。

阅读全文 »

之前听说chrome在你的电脑里塞了一个小模型,相比于说这个比较容易引起公愤的,可能是因为模型有4g大小,但是感觉也提供了挺大的想象空间
比如在浏览器内就可以直接调用模型进行对话处理,最简单的比如做个翻译啥的,
首先也有具体的硬件条件,

  1. 系统:Windows 10/11、macOS 13+、Linux、ChromeOS
  2. 显存:4GB 以上 VRAM,或者 16GB 内存加 4 核 CPU
  3. Chrome 版本:138 以上

然后可以用这个地址看下怎么来开启这个内置模型
https://prompt-api.com/

1
2
3
4
5
Open chrome://flags/#optimization-guide-on-device-model and set to Enabled BypassPerfRequirement
Open chrome://flags/#prompt-api-for-gemini-nano and set to Enabled
Open chrome://flags/#prompt-api-for-gemini-nano-multimodal-input and set to Enabled (for image/audio input)
Click Relaunch to restart Chrome
After relaunch, visit chrome://on-device-internals to check model status

主要是这些配置项,开启之后可以通过 chrome://on-device-internals当前的状态,比如硬件条件是否符合,我一开始是本地的磁盘空间不够,
默认需要有大概22g空间以上才可以下载使用,

PropertyValue
device capabletrue
disk space available18671 MiB available, 20480 MiB required
enabled by enterprise policytrue
enabled by featuretrue
enabled by user settingtrue
is already installingtrue
on device feature recently usedtrue
out of retentionfalse
Detected VRAM (MiB)12288
Minimum VRAM required (MiB)0

符合之后可以在console查看当前的状态,

1
2
3
4
await LanguageModel.availability({
expectedInputs: [{ type: 'text', languages: ['en'] }],
expectedOutputs: [{ type: 'text', languages: ['en'] }]
})

比如正在下载会显示
downloading,可以使用的话就是'available'
如果想对话尝试下可以在上面的 https://prompt-api.com/ 里进行对话体验
还有就是这个模型能用来干嘛呢
chrome其实提供了一系列的api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Translator API
Translator API 可在 Chrome 138 稳定版中使用。根据要求翻译用户生成的内容和动态内容。

使用场景
用户可以使用自己的第一语言输入请求,您可以使用 Language Detector API 识别该语言。然后,使用 Translator API 将请求转换为您的业务运营语言,并将其发送给支持代理。
在社交网络应用中,当用户的时间轴上显示他们不熟悉的语言的帖子时,他们可以按需请求翻译。
Language Detector API
Language Detector API 自 Chrome 138 稳定版开始提供。您可以使用此 API 检测输入文本的语言。 这是翻译流程的关键部分,因为您可能并不总是知道翻译的输入语言。

使用场景
语言检测有多种应用场景:

确定未知源语言,以便将内容翻译为已知目标语言,这样用户就不必同时指定这两种语言。
标记文本,例如,为了改进在线社交网站中的屏幕阅读器发音。

比如翻译的,语言检测的等等
那比如我们可以自己做个翻译的扩展小工具,既不用联网,也不用科学上网,还是很方便的
感觉端侧的小模型还是挺有前景的,随着模型能力越来越强
顶尖的模型都很贵,但是很多活其实可以拆分着来
最复杂核心的架构分析重构设计需要最好的模型
但是常规的一些简单的重复性的工作,一般的小模型也能完成
这样就chrome本身这么大的一个入口,带上这么个模型
还是非常有想象力的
当然这个也是我作为一个软件开发从业者的角度
而不是商业思维上来看
因为这些看起来都是没啥商业空间
毕竟免费的好用的都已经一大堆了,没有啥必要再付费或者订阅啥的
只是对于自己使用来说,有时候还是比较方便的
就比如这个翻译的场景,能够有个不需要联网,不用科学上网的
不过还一点是它现在好像不支持中文
所以还可以在期待下后续的发展
一个是模型能力的再提升
另一个是支持的范围,可以由中文支持这种

0%