阻止 bilibili 网页自动关注

B 站非常逆天,只要你使用网页端看视频,点开首页推荐的视频,观看一定时长,就有概率会触发自动关注该 UP 主。 之前也有怀疑是某个浏览器插件的锅,但更换浏览器依然能够触发这个问题。 网上发现不少类似的问题,但都没有解决方案,迫于无奈只能使用自定义广告过滤规则了,之后没有再出现问题。 这里使用 uBlock origin, 其他去广告插件也类似, 进入插件设置 -> 自定义静态规则,添加如下规则。 在视频页面禁止关注请求,不影响点开 UP 主主页进行关注。 1 www.bilibili.com##+js(no-fetch-if, /api\.bilibili\.com\/x\/relation\/modify/, location.pathname.startsWith('/video/'))

2026/1/2
articleCard.readMore

Hexo 版本更新与技术债务

昨天花了些时间将 Hexo + next 的博客版本更新了下,发现不少问题。 博客已经很多年了,一直用的是最开始的Hexo版本,nodejs 也是 v12,next 主题也是早年源码安装的,还在上面做了修改。 也是早就明白需要更新了,但是觉得麻烦,就一直没动,最后变得相当麻烦。 依赖更新 首先是版本升级,先将 nodejs 切换到最新 lts(v22)。 再尝试使用npm将 package.json 中的库自动更新,但由于版本差距太大以及很多库不维护了,更新不了。 最后只能使用 hexo init 新建个博客项目,将里面的依赖项复制到老博客里,删除对应的老依赖。 剩下的依赖,大部分是安装的插件,只能一个个搜索用途,看看有没有替代或者还要不要用。 package.json 中的其他参数,参考最新的配置文件,将无用(过时)的设置项删除。 使用 npm 安装 next 主题,将原 themes/next 中的配置文件复制到 _config.next.yml。 对比 hexo 和 next 官方 _config 文件,相应修改 _config.yml 和 _config.next.yml。 删除 themes 中重复的主题目录。 使用 npm install 安装依赖,保证能够正常生成博客。 删除 node_modules,改用 pnpm 管理依赖,更轻量快速。 使用 pnpm 时可能会报错,如某个库的 index.node 找不到,是因为 pnpm 默认不执行库的 post install 脚本。 需要将这些库添加到 pnpm.onlyBuiltDependencies 中。 1 2 3 4 5 6 "pnpm": { "onlyBuiltDependencies": [ "hexo-word-counter", "hexo-util" ] } ps: 总体来说 pnpm 还是存在一些兼容性问题,但大部分都不是 pnpm 本身的问题,基本上都是库的影子依赖。 大模型在这种疑难杂症上帮助很大 额外设置 hexo 修改 scaffolds 文章模板文件 老版本 hexo 的 scaffolds/page.md 等文件格式与新版不同,会导致一些工具不能识别 front-matter, 需要更新。 老版本 front-matter 前面少了开头的 ---,导致 vscode 的 hexo-util 插件不能识别 tag 等信息,调试了很久。 1 2 3 4 5 6 7 8 9 10 # 老版本 title: {{ title }} date: {{ date }} --- # 新版本 --- title: {{ title }} date: {{ date }} --- next 自定义样式和脚本,之前是通过之间修改主题源码实现,现在使用 custom_file_path 注入自定义内容。 需要修改 _config.next.yml 文件 1 2 3 4 custom_file_path: head: source/_data/head.njk bodyEnd: source/_data/body-end.njk style: source/_data/styles.styl 自定义样式 next 主题的 Muse scheme 整体比较符合我的口味,但部分样式还需要微调。 source/_data/styles.styl 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 // 自动给文章的 toc 和正文标题编号, 不对首页编号 body {counter-reset: h1} .post-block .post-body h2 {counter-reset: h2} .post-block .post-body h3 {counter-reset: h3} .post-block .post-body h4 {counter-reset: h4} .post-block .post-body h5 {counter-reset: h5} .post-block .post-body h6 {counter-reset: h6} .post-block .post-body h2:before { counter-increment: h1; content: counter(h1) ". " } .post-block .post-body h3:before { counter-increment: h2; content: counter(h1) "." counter(h2) " " } .post-block .post-body h4:before { counter-increment: h3; content: counter(h1) "." counter(h2) "." counter(h3) " " } .post-block .post-body h5:before { counter-increment: h4; content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) " " } .post-block .post-body h6:before { counter-increment: h5; content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) " " } .post-block .post-body h1.nocount:before, .post-block.post .post-body h2.nocount:before, .post-block.post .post-body h3.nocount:before, .post-block.post .post-body h4.nocount:before, .post-block.post .post-body h5.nocount:before, .post-block.post .post-body h6.nocount:before { content: ""; counter-increment: none } // 隐藏文章 toc 中文章目录 tab 中的友链信息,站点概览中的保留 .sidebar .sidebar-toc-active + .sidebar-blogroll { display: none } 只在文章页显示侧边栏 next 主题的侧边栏逻辑有点问题, 就算设置 sidebar.display 为 post,当从文章跳到其他页面时,侧边栏并不会自动关闭,所以只能通过脚本来绕过。 source/_data/body-end.njk 1 2 3 4 5 6 7 8 9 10 11 12 13 <script> document.addEventListener('pjax:success', () => { if (CONFIG.sidebar.display !== 'remove') { const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)'); // 当没有 TOC 且侧边栏正在显示时,隐藏侧边栏 if (!hasTOC && document.body.classList.contains('sidebar-active')) { window.dispatchEvent(new Event('sidebar:hide')); } } }); </script> 自定义重定向 hexo 的文章不支持多个入口,比如我想要分类中英文都可以访问,默认情况是做不到的。 在设置了 category_map 的情况下,hexo 只会生成 programming 等英文链接,无法同时保留中文url作为入口。 1 2 3 4 5 category_map: 编程: programming 随笔: writing 阅读: reading 工作生活: life 所以最终通过在 404 页面添加自定义的 js 实现重定向逻辑。 source/_data/head.njk 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 {% if page.title === '404 Page Not Found' or page.type === '404' or page.layout === '404' %} <script> (function() { // 从 Hexo 配置中获取 category_map var categoryMap = {{ config.category_map | dump | safe }}; var currentPath = decodeURIComponent(window.location.pathname); // 检查路径中是否包含分类映射的key for (var key in categoryMap) { if (currentPath.includes(key)) { var targetCategory = categoryMap[key]; var targetPath = currentPath.replace(key, targetCategory); // 执行重定向 if (currentPath !== targetPath) { window.location.replace(targetPath); return; } } } })(); </script> {% endif %} 更换 gitalk 为 utterances 原先一直使用 gitalk 作为文章的评论系统。 gitalk 每个新文章都需要作者登陆触发新建评论 issue,不太方便,所以现在换成 utterances。 utterances 评论插件 next 主题已内置,修改 _config.next.yml 可以开启。 1 2 3 4 5 6 # 注意先关闭 gitalk utterances: enable: true repo: ruanimal/ruanimal.github.io # Github repository owner and name # Available values: pathname | url | title | og:title issue_term: title 还需要在 github pages 仓库安装 utterances 应用。 自定义的域名可能不被 utterances 支持,无法登陆跳转 需要新建 source/utterances.json 1 2 3 4 5 6 { "origins": [ "https://blog.ponder.work", "https://ruanimal.github.io" ] } 如果你和我一样,博客有多个域名,副域名登陆跳转会失效,可以通过注入js修正 utterances 的登陆跳转是根据 canonical link,我们只要让它和当前url保持一致就行了 在 source/_data/head.njk 文件添加以下内容 1 2 3 4 5 6 7 8 9 10 11 12 13 <script> (function() { var canonical = document.querySelector('head > link[rel="canonical"]'); if (canonical && canonical.href) { try { var oldUrl = new URL(canonical.href); canonical.href = window.location.origin + oldUrl.pathname + oldUrl.search + oldUrl.hash; } catch (e) { console.warn('Failed to update canonical link:', e); } } })(); </script> 更换编辑器 之前一直是用 mac 平台是 mweb 作为 hexo 的文章编辑器,实话说也非常好用。 但是我把主力操作系统换成 linux 之后,linux 上一直没找到与 mweb 相当的应用。 最终经过一番折腾,最终使用 vscode + Hexo Utils 插件作为平替。 同时对该插件进行来二开,最终体验算是追平了 mweb。 感受 所谓技术债务,项目正常迭代的过程中是不断积累的,如果平时不跟进解决,最终在某个阶段就会爆炸,造成巨大影响。 平时迭代时,如果时间安排的过紧,我们就会倾向于采取保守的决策(能跑就不要动),最终不可避免的技术债务堆积。 当然,生活中其实也是这样吧,很多不紧急的问题,一直拖着,最终大概率也会无可挽回。

2026/1/2
articleCard.readMore

配置 Linux 作为主力操作系统

这些年日常操作系统一直是 windows 和 macOS 交替使用,Linux 一般只作为服务器的操作系统。 然而,咖喱味的 Windows 11 (LTSC)用起来实在难受(平时只玩游戏,下载) arm 的 macbook 虽然能效惊人,但是内存金子价格,软件也封闭(点名finder)和傲慢,实在受不了。 最后,还是转向 Linux,毕竟现在 wayland 基本堪用,国产软件也随着信创逐渐丰富了。 发行版选择 这里选用 KDE Neon user edition,基于 Ubuntu LTS, 有以下优点 Ubuntu 用户基数大,有问题容易解决 可以安装星火应用商店,方便使用国产软件 可以用到最新的 KDE 桌面环境,wayland 支持更好 KDE 远程桌面好用(服务端和客户端) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 OS: KDE neon User Edition x86_64 Host: MS-7D99 (3.0) Kernel: Linux 6.14.0-33-generic Uptime: 1 day, 19 hours, 23 mins Packages: 2831 (dpkg) Shell: bash 5.2.21 Display (E2434I-T): 1920x1080 @ 60 Hz in 24" [External] Display (KOS2718): 3840x2160 @ 60 Hz (as 1920x1080) in 27" [External] * DE: KDE Plasma 6.4.5 WM: KWin (Wayland) WM Theme: Breeze Theme: Breeze (Light) [Qt], Breeze [GTK2/3] Icons: Breeze [Qt], breeze [GTK2/3/4] Font: 微软雅黑 (10pt) [Qt], 微软雅黑 (10pt) [GTK2/3/4] Terminal: /dev/pts/3 CPU: 13th Gen Intel(R) Core(TM) i5-13500 (20) @ 4.80 GHz GPU 1: NVIDIA GeForce RTX 4060 Ti [Discrete] GPU 2: Intel AlderLake-S GT1 @ 1.55 GHz [Integrated] Memory: 25.07 GiB / 46.67 GiB (54%) Swap: 392.00 KiB / 8.00 GiB (0%) 硬件要求 NVIDIA 显卡在 Linux 下对 Wayland 支持不好,容易出现卡死或者重启的情况,建议显示器连到核显 有些主板 Linux 兼容性不行,建议使用御三家主板 系统设置 Nvidia 显卡驱动 显示器插到核显上,只使用 Nvidia 做计算,玩游戏也能调用到显卡。 使用开源服务器驱动,解决 Wayland 兼容性问题,防止玩游戏崩溃 安装驱动 1 sudo ubuntu-drivers install 580-server-open 启用睡眠支持参考 将显存内容保存,否则唤醒后程序会报错(如CUDA程序) 1 2 3 4 5 # /etc/modprobe.d/nvidia-power-management.conf options nvidia NVreg_PreserveVideoMemoryAllocations=1 NVreg_TemporaryFilePath=/tmp sudo update-initramfs docker 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 for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done # Add Docker's official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo usermod -aG docker $USER newgrp docker # 立即生效(或注销重新登录) # docker 中调用显卡 https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html ipv6 启用 EUI-64 可以让 ipv6 的ip后缀根据mac生成,可用于配置防火墙规则 使用形如 ::<后缀>/::ffff:ffff:ffff:ffff 的掩码 1 nmcli con modify <WIFI NAME> ipv6.addr-gen-mode eui64 启用休眠文件 1 2 3 4 5 6 7 8 sudo su cd / truncate -s 0 swapfile # chattr +C swapfile # disable COW on btrfs fallocate -l 8G swapfile chmod 0600 swapfile mkswap swapfile swapon swapfile 使用英文用户文件夹 用户个人文件夹(文档、下载等),默认是跟随系统语言。 可是中文在终端下输入不太方便,建议使用英文用户文件夹名称。 方法步骤: 语言修改成英文,注销重新登录 打开文件管理器,确认修改为英文 语言修改回中文 打开文件管理器,保留原名称 无线网卡调优 WIFI 启用 WOL(网络唤醒) 1 2 3 4 # 查看 WOL 支持 iw phy0 wowlan show # 开启 WOL 支持 sudo iw phy0 wowlan enable magic-packet 关闭节能模式,解决无线网口入站延迟较大 1 2 3 4 5 6 7 8 cat <<EOF > /etc/NetworkManager/conf.d/wifi-powersave-off.conf [connection] wifi.powersave = 2 EOF # 或者启动时设置 sudo iw dev wlo1 set power_save off 睡眠调整 睡眠到内存(suspend)耗电量很低,够用了,没必要开启混合休眠 1 2 3 4 5 6 7 8 9 10 11 12 # 只睡眠到内存 # 修改 /etc/systemd/sleep.conf AllowSuspend=yes AllowHibernation=no AllowSuspendThenHibernate=no AllowHybridSleep=no SuspendState=mem standby # 禁用休眠(可选) sudo systemctl mask hibernate.target hybrid-sleep.target # nvida intel 核显性能优化 10代以上 intel 核显 1 2 3 4 # cat /etc/modprobe.d/i915.conf options i915 enable_fbc=1 enable_guc=2 enable_dc=0 sudo update-initramfs -u RDP 远程桌面 服务端: 使用 KDE 默认的远程桌面 (可惜稳定性一般) 客户端: apt install krdc 其他配置 1 2 3 4 5 6 7 # 双屏只显示其中一个屏幕 # 修改 ~/.config/systemd/user/plasma-krdp_server.service ExecStart=/usr/bin/krdpserver --monitor 0 # 重启服务 systemctl --user daemon-reload systemctl --user restart app-org.kde.krdpserver.service 使用新内核(可选) 发行版默认内核版本可能比较老,如有必要可以更新内核 注意:mainline 的内核与 ubuntu-drivers 的 nvidia 显卡驱动不兼容(所以一般不推荐) 1 2 3 sudo add-apt-repository ppa:cappelikan/ppa sudo apt update sudo apt install mainline 软件使用 常见应用替代方案 qq: 使用官方linux版本的flatpak打包 flatpak install com.qq.QQ 任务管理器 flatpak install io.missioncenter.MissionCenter 词典:使用欧陆词典 flatpak install net.eudic.dict 微信 使用星火应用商店打包的官方linux版本 下载管理器: motrix:普通http下载 qbittorrent: bt下载 虚拟机 KVM:性能更好 VMware:易用性更好 游戏 steam:大部分游戏都可以直接运行,性能损耗也不大 视频播放 haruna:使用 flatpak 版本 KVM 虚拟机 安装 1 2 3 sudo apt install qemu-kvm libvirt-daemon-system virt-manager sudo usermod -aG libvirt,kvm $USER sudo systemctl enable --now libvirtd 硬盘直通(scsi), 相比下面的方法性能更好 1 2 3 4 5 <disk type='block' device='disk'> <driver name='qemu' type='raw' cache='none' io='native'/> <source dev='/dev/disk/by-id/ata-TOSHIBA' index='2'/> <target dev='sdg' bus='scsi'/> </disk> 硬盘直通(virtio),samba分享时可能卡顿 1 2 3 4 5 <disk type="block" device="disk"> <driver name="qemu" type="raw" cache="none"/> <source dev="/dev/disk/by-id/ata-ST3000DM008"/> <target dev="sdc" bus="virtio"/> </disk> 无线网口不支持桥接,使用路由绕过 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 # 修改 /etc/sysctl.conf; sudo sysctl -p 生效 net.ipv4.ip_forward = 1 net.ipv4.conf.wlo1.proxy_arp=1 net.ipv4.conf.br-routed.proxy_arp=1 # 创建网桥和路由 #/etc/systemd/system/bridge-routed.service [Unit] Description=Create routed bridge br-routed After=network.target Wants=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStartPre=/usr/bin/ip link add br-routed type bridge ExecStart=/usr/bin/ip link set br-routed up ExecStartPost=/usr/bin/ip route add 192.168.1.41/32 dev br-routed ExecStop=/usr/bin/ip link delete br-routed [Install] WantedBy=multi-user.target # 修改 kvm 配置 <interface type="bridge"> <mac address="00:00:00:00:00:00"/> <source bridge="br-routed"/> <target dev="vnet0"/> <model type="virtio"/> <alias name="net0"/> <address type="pci" domain="0x0000" bus="0x0a" slot="0x00" function="0x0"/> </interface> # 虚拟机使用静态ip IPv4 地址 . . . . . . . . . . . . : 192.168.1.41(首选) 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.1.1 DNS 服务器 . . . . . . . . . . . : 192.168.1.1 dolphin 右键菜单自定义快捷方式 支持文件和文件夹 chmod + ~/.local/share/kio/servicemenus/mklink.desktop 1 2 3 4 5 6 7 8 9 10 [Desktop Entry] Type=Service ServiceTypes=KonqPopupMenu/Plugin MimeType=inode/directory;application/octet-stream; Actions=RunMyScript [Desktop Action RunMyScript] Name=制作符号链接 Icon=emblem-symbolic-link Exec=/home/rlj/scripts/mklink.sh "%F" mklink.sh 1 2 3 4 #!/usr/bin/env bash NAME=$(basename "$1") ln -s "$NAME" "$NAME.lnk" haruna 视频播放器 haruna 是 mpv 播放器的前端,功能比较齐全 存在的问题: 视频播放器首次最大化窗口,总是跳到另一个屏幕 关闭另一个屏幕,关闭应用,再打开,再最大化,再重新打开屏幕 无法重命名文件 设置 flatpak 目录权限 flatpak 使用 1 2 3 4 5 # 换源 flatpak remote-modify flathub --url=https://mirrors.ustc.edu.cn/flathub # 目录授权,解决应用无法编辑文件 flatpak override --user org.kde.haruna --filesystem=/share 挂载 SMB 共享支持 windows 符号链接 支持原生符号链接(目录连接),双向操作都能同步 步骤 windows 允许符号链接 计算机配置 → 管理模板 → 系统 → 文件系统 → 启用 Win32 长路径 计算机配置 → windwows设置 → 安全设置 → 本地策略 → 用户权限分配 → 创建符号链接 samba 挂载选项 symlink=native 链接时使用相对路径(两个系统绝对路径不一样) 遇到的问题 故障排查方法 查看系统日志 journalctl -k -b0 查看用户日志,比如应用崩溃 journalctl –user -b0 用 AI 搜索相关问题,如果无法解决再尝试 google 搜索 edge 无法启动 edge 异常退出后,有概率无法启动 报错 1 2 Microsoft Edge 进程似乎正在使用此用户配置。 Microsoft Edge 已锁定此用户配置以防止损坏。如果你确定没有其他进程正在使用此用户配置,可以将其解锁并重新启动 Microsoft Edge。 处理方法,其他 chromium 内核浏览器可能也类似 1 rm ~/.config/microsoft-edge/SingletonLock steam 玩游戏卡住 dmesg 信息 1 2 3 4 7月 27 10:48:59 neon kernel: x86/split lock detection: #AC: CPU 0/KVM/88279 took a split_lock trap at address: 0xfffff80104613be8 7月 27 10:51:00 neon kernel: x86/split lock detection: #AC: CPU 2/KVM/88281 took a split_lock trap at address: 0xfffff8010465701c 7月 27 10:52:51 neon kernel: x86/split lock detection: #AC: CHTTPClientThre/90929 took a split_lock trap at address: 0xf098fc4f 7月 27 10:53:07 neon kernel: x86/split lock detection: #AC: CPU 1/KVM/88280 took a split_lock trap at address: 0xfffff8010465701c 解决方法 1 2 3 4 # 编辑 /etc/default/grub GRUB_CMDLINE_LINUX_DEFAULT='quiet splash split_lock_detect=off' sudo update-grub 迁移系统&修复引导 进入 livecd,使用 Gparted 复制系统到新硬盘 修改新硬盘分区的 /etc/fstab 的挂载点 uuid 重建 grub 引导 1 2 3 4 5 6 7 mount /dev/sdaX /mnt/ mount /dev/sda0 /mnt/boot/efi for i in {proc,dev/sys}; do mount --bind /${i} /mnt/${i} done chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi 睡眠唤醒后很卡 桌面很卡,cpu 频率上不来,只有 800mhz。 映泰主板兼容性问题,换主板解决 终端删除文件到回收站 1 2 sudo apt install trash-cli alias rm=trash electorn 应用无法启动 报错 chrome sandbox 相关 1 2 3 4 5 # 编辑 /etc/sysctl.conf 添加 kernel.apparmor_restrict_unprivileged_userns=0 # 生效 sudo sysctl -p 登录界面隐藏无关用户 比如某些用于文件共享的用户 1 2 3 4 5 6 7 # 修改 /etc/sddm.conf [Autologin] Session=plasma [Users] HideUsers=data,data-r,data-s HideShells=/usr/sbin/nologin,/bin/false 偶发硬件设备工作不正常 强制重启后或者系统崩溃后,如果设备不正常,可以尝试重启到 windows 再切回 linux 应用在启动器里找不到 1 2 rm -f ~/.config/menus/ kbuildsycoca6 --noincremental 修复睡眠唤醒问题 睡眠之后可能出现莫名奇妙的唤醒 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 查看当前唤醒触发器 cat /proc/acpi/wakeup | grep enable # 查看触发器的设备路径 for p in /sys/class/wakeup/*/device/power/wakeup; do dev=$(dirname $(dirname $p)); dev_path=$(realpath $dev) echo ${dev_path} done # 直接修改是 /proc/acpi/wakeup 是不生效的 # 设置具体设备的方式禁用 rtc 和 AWAC, 位置格式 ${dev_path}/power/wakeup # 可以将禁用命令配置到 rc.local echo disabled | tee /sys/devices/platform/rtc_cmos/power/wakeup # rtc echo disabled | tee /sys/devices/platform/rtc_cmos/rtc/rtc0/alarmtimer.0.auto/power/wakeup # rtc echo disabled | tee /sys/devices/platform/ACPI000E:00/power/wakeup # AWAC # 检查是否生效 cat /proc/acpi/wakeup | grep enable apt 设置代理 部分 apt ppa 源需要代理,但是国内的镜像源却不需要。 所以需要给每一个源单独设置代理的功能 有两种设置方式 白名单模式(只给需要的配置) 黑名单模式(先设置默认代理, 不需要的设置直连) 设置语法 设置代理服务器:Acquire::http::Proxy <proxy-server> 设置某个域名的代理:Acquire::http::proxy::<domain> <proxy-server|DIRECT> 示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # /etc/apt/apt.conf.d/99proxy # 给每种协议的源设置代理,一般设置 https::Proxy 就行了 #Acquire::http::Proxy "http://yourproxyaddress:proxyport"; #Acquire::https::Proxy "http://yourproxyaddress:proxyport"; #Acquire::ftp::Proxy "http://yourproxyaddress:proxyport"; #Acquire::socks::Proxy "http://yourproxyaddress:proxyport"; # 设置具体域名是否走代理 #Acquire::http::proxy::local.mirror.address "DIRECT"; #Acquire::http::proxy::HOST_NAME_TO_BE_PROXIED "http://yourproxyaddress:proxyport" # 黑名单模式示例 Acquire::https::Proxy "http://localhost:10808"; Acquire::https::Proxy::"mirrors.aliyun.com" "DIRECT";

2025/12/21
articleCard.readMore

在 Linux 虚拟机中使用 PyAutoGUI 做自动化

PyAutoGUI 是 GUI 功能强大自动化方案,但 UI 程序的运行环境选择与配置也是一大难题。 系统选择 为了让环境可迁移,免维护,资源消耗少,必须使用虚拟化的方案。 虽然 PyAutoGUI 支持 Windows/macOS/Linux 三个平台。但是各有各的弊端。 Windows 的系统臃肿且开发环境不友好 macOS 的权限管理过于严格且虚拟化难度大,高dpi的屏幕也不适合自动化 Linux 就是最好的选择了,但执行的应用可能不支持 Linux,必须引入 Wine 综合以上情况考虑,系统环境选择如下 pve: 宿主机系统,也可以选择别的环境 debian: 客户机系统,目前是 debian 12, 方便使用 deepin 的 wine 程序 mate: 桌面环境,比较轻量,实测兼容性比 xfce 好 星火应用商店: 方便安装各种国内软件和 Wine 程序 统信 Windows 应用兼容引擎: 在星火应用商店中安装,可以用于安装和打包商店中没有的程序 虚拟机配置 步骤 创建好 pve 虚拟机, 安装 debian,选择 mate 桌面 设置分辨率:控制中心 -> 显示器 -> 设置为 1920x1080 关闭自动睡眠:控制中心 -> 电源管理 -> 动作和显示修改为从不 关闭屏幕保护:控制中心 -> 屏幕保护程序 -> 取消勾选所有选项 安装gnome-screenshot: 用于 PyAutoGUI 内部调用截图 安装星火应用商店(可选) 安装统信 Windows 应用兼容引擎(可选):在星火应用商店中安装 火焰截图(可选):在星火应用商店中安装,更方便对应用的按钮和文字截图。 其他说明: 如果不关闭自动睡眠和屏幕保护,PyAutoGUI 就无法截取到应用的图像,也就无法操作了。 图形显示协议默认选 x11,wayland 对显卡有要求,虚拟机不方便处理。 睡眠后时间不对 虚拟机睡眠唤醒后时间没有更新,不知道为什么没有触发时间更新。 目前只能通过强行同步来解决 在脚本里执行同步命令 1 2 # 和阿里云ntp服务器同步 sudo ntpdig -S ntp.aliyun.com 或者,理论上可以通过虚拟机中的 systemd-suspend hook,或者 pve hookscript 来解决(没试成功) PyAutoGUI 使用技巧 远程登录调试 pve 默认图片控制台不太方便,不能复制粘贴,所以使用远程登录调试 由于系统使用 x11 环境,这里使用 xrdp 作为远程服务端。 如果以后更新到 wayland,可以使用 gnome-remote-desktop。 1 2 sudo apt update sudo apt install xorgxrdp xrdp 远程登录时找不到显示器 xrdp 或者 ssh 远程登录执行脚本时,窗口相关命令可能会提示找不到显示器。 可以在自动化脚本中设置以下环境变量 1 2 os.environ['DISPLAY'] = ':0' os.environ['XAUTHORITY'] = '/home/<your-name>/.Xauthority' 过滤没必要的截图日志 gnome-screenshot 截图时会产生一些无用日志 1 2 ** Message: 17:36:31.888: Unable to use GNOME Shell's builtin screenshot interface, resorting to fallback X11. 新建一个 gnome-screenshot 文件,赋予执行权限,放到 PATH 环境变量中比 /usr/bin 靠前的路径 1 2 #!/bin/bash exec /usr/bin/gnome-screenshot "$@" >> /tmp/gnome-screenshot.log 2>&1 验证码识别 pytesseract 常见的方案是 pytesseract, 但是效果不好,识别率比较一般。 安装 1 2 sudo apt install tesseract-ocr pip install pytesseract 使用 1 pytesseract.image_to_string(image, config='--psm 8 -c tessedit_char_whitelist=0123456789') ddddocr ddddocr 是基于机器学习的验证码识别库,识别效果比较好。 这里使用 docker 安装 fastapi接口 1 docker run -d -p 8000:8000 oozzbb/ddddocr-fastapi:latest 使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def ocr_image(file='region.png'): url = 'http://your-address:8000/ocr' with open(file, 'rb') as fp: files = { 'file': fp, } data = { 'probability': 'false', 'png_fix': 'false', 'charsets': '0123456789' # 验证码可能的字符列表 } response = requests.post(url, files=files, data=data) assert response.ok return response.json()['data'] 高效执行自动化脚本 可以在宿主机远程调用自动化脚本,并且脚本执行完成后挂起虚拟机 1 2 3 4 5 6 7 8 9 10 HOST=your-name@vm-ip-address VMID=109 # pve 虚拟机id qm resume $VMID # 唤醒虚拟机 sleep 2 ssh $HOST "SOME_VAR=foobar python your-script.py" # 执行脚本 sleep 1 qm suspend $VMID # 关闭虚拟机 其他有用的函数 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 # 切换窗口到前台 def wm_to_front(title: str): if sys.platform == 'linux': return subprocess.check_call(['wmctrl', '-a', title]) raise NotImplementedError # 点击或者移到到图片位置 def click_image(image, confidence=0.9, grayscale=True, delay=1.5, duration=0.6, offset=(1, 0), abort=True, click=True): try: loc = pyautogui.locateOnScreen(image, confidence=confidence, grayscale=grayscale) except pyautogui.ImageNotFoundException: loc = None if loc: x, y = pyautogui.center(loc) if click: pyautogui.click(x+offset[0], y+offset[1], duration=duration) else: pyautogui.moveTo(x+offset[0], y+offset[1], duration=duration) time.sleep(delay) return loc else: if abort: raise RuntimeError(f"can not find {image}!") else: return None # 等待某个图片出现 def wait_button(image, timeout, interval=10): for _ in range(timeout//interval): time.sleep(interval) loc = click_image(image, abort=False) if loc: break else: raise RuntimeError(f'wait {image} failed') time.sleep(interval/2) 使用感受 PyAutoGUI 是基于图像而不是图型控件来识别目标,没有确认反馈。 相比于网页自动化的 Selenium 就感觉落后些了。 当然,Windows 平台有 pywinauto 支持控件识别,但我不咋熟悉。 总之,PyAutoGUI 就像手枪,虽然比较简陋,但还是很直观的,执行简单的任务也够用了。 注意事项 自动化代码中不要编码用户名密码相关信息,建议用环境变量传入 不要轻易调整系统的 dpi(建议设为1.0) 和分辨率,可能导致图片无法识别 xfce 对 wine 应用的兼容性似乎不好,wine 文件保存对话框被可能被反复触发,原因不明。

2025/5/27
articleCard.readMore

语言的力量

语言的力量远比想象的强大,某种程度上是有虚空造物的能力。 你可以凭空创造一个概念,并植入你的听众脑子,不论好还是坏。诈骗分子、PUA 大师、营销大师无不精通此等技艺。 起因是一个综艺节目,有个导演对哈妮克孜说了句话,让我惊为天人,让我惊叹“你他娘真是个人才”。 它在线下拍戏的时候见到了哈妮克孜,哈当时戴了副大镜框眼镜,于是它说道“我当时看到你的时候,感觉好失望,你和我银幕上看到的哈妮克孜完全不一样”。 它凭空创造了一个观念(事实),哈妮克孜颜值不行。怎么创造的呢?和哈妮克孜荧幕形象相比。这显然是不客观的,但很难瞬间就察觉到。 这样,好与坏、美与丑一切观念都是可以被重新定义的,只要你选择合适的目标和对照标准,甚至是你脑海里的标准。 你是丑的和你漂亮的时候比,你是蠢的和聪明的时候比,我对你是失望的和对你的期望相比。 PUA 大师就是用这种手法实现打压,新闻学就是这么颠倒黑白,当然也可以反向操作,那么你就成为知心姐姐鼓励大师。 所以,神话中的言出法随也不难理解,毕竟人是观念的动物,你改变/植入了某人的某个观念,那么你就改变了他眼中的世界。 这也就是《乔布斯传》中乔布斯的扭曲现实力场。

2025/5/27
articleCard.readMore

联想笔记本 BIOS 跳过检测强制降级

联想某些版本的 bios 似乎会禁止降级,即使打开 bios 设置里的允许降级选项,依然会提示 “this platform does not support IHISI interface” 的错误,导致降级失败。 降级方法 经过一些尝试,找到了方法绕过,以 ThinkBook 14 G3 ACL 机型为例 打开驱动下载页面 打开 BIOS 页面, 下载以下安装文件 当前版本 BIOS 的安装包(NEW),版本 GQCN41WW_HFCN36WW 以及想要降级的 BIOS (OLD),版本 GQCN36WW_HFCN31WW 运行 NEW,选择仅解压。找到解压目录,应该只有一个 exe 程序,使用 7-zip 或者 bandizip 打开并提取内部文件。 对 OLD 也执行如上操作,如果没有解压选项,可以尝试直接提取。 将 OLD 提取目录中的 .bin 格式固件(GLV3A036.bin、GLV4D031.bin)复制到 NEW 提取目录中。 修改 NEW 提取目录中的 platform.ini 文件,找到如下内容。将其中的 .bin 文件名,修改为 OLD 中的 .bin 文件名,保存。有两个 .bin 文件,文件名是和版本中的数字对应的,不要修改错误。 1 2 3 4 5 6 7 8 9 10 11 ; 修改前 [MULTI_FD] Flag=1 FD#01=ID,GLV4D,GLV4D036.bin FD#02=PCI,0,1,1,0,FFFFFFFF,FFFFFFFF,GLV3A041.bin ; 修改后 [MULTI_FD] Flag=1 FD#01=ID,GLV4D,GLV4D031.bin FD#02=PCI,0,1,1,0,FFFFFFFF,FFFFFFFF,GLV3A036.bin 运行 NEW 提取目录中的 H2OFFT-Wx64.exe,等待降级完成。 注意:有风险,请谨慎操作。不要跨太多版本,可能有不可预料的问题 参考 https://www.geektech.co.nz/lenovo-y700-how-to-enable-bios-downgrade

2025/5/15
articleCard.readMore

redroid “设备未获得play保护机制认证” 问题

使用 redroid 等安卓虚拟环境,可能会发现 google play 用不了的问题。 虽然系统集成了 gapps,但系统提示 “设备未获得play保护机制认证”,无法登录 play 商店。 可能的原因比较多,这里大概是因为虚拟机的型号没在google的数据库里。 解决方案就是,获取 GSF ID 注册到 Google。 步骤 安装 device id 打开应用,复制 GSF id,假设为ffffffff 终端运行printf "%d\n" 0xffffffff,转换换成10进制数字。 打开 https://www.google.com/android/uncertified/ 输入转换后的结果并提交

2025/5/7
articleCard.readMore

在 iOS 上访问安卓应用

有些应用在安卓上是独占的,iOS 上又没有比较好的替代品,而且 iOS 上没有能用安卓模拟器。 如果使用多个设备,维护的心智成本又高,被这个问题困扰了许久。 最近碰巧了解了 scrcpy, 用于远程控制安卓,终于解决了这个问题。 需要的工具 安卓环境:虚拟环境或者物理设备均可,这里使用虚拟环境。以下是可选的虚拟环境 PVE 的 PCT容器 (Proxmox Container Toolkit),需要打补丁 docker,基于Redroid 需要调整内核参数 WSA (Windows Subsystem for Android),与 scrcpy 配合有点问题,窗口有黑边。 VMware、VirtualBox等虚拟化平台,需要解决虚拟显卡。 使用 scrcpy-mobile 远程控制安卓环境, 当然 scrcpy 也支持 Windows、Linux、macOS PCT 安卓容器 由于已有PVE环境,这里选用PCT,主要步骤 根据 PCT-patches 文档,给 PCT 打好补丁 根据 lineageOS 模板,新建安卓容器,注意去除Unprivileged container的勾选。 修改 lxc.init.cmd 选项,在后面增加以下参数,调整分辨率和iPad一致。 1 androidboot.redroid_width=1668 androidboot.redroid_height=2388 androidboot.redroid_fps=60 ps: 安卓容器对宿主机性能似乎有一定要求,J4125 只是勉强够用,流畅度一般。 macOS 中使用 scrcpy 安装 scrcpy ,连接命令 1 2 3 4 5 6 scrcpy --audio-codec=aac \ --video-codec=h264 \ --video-bit-rate=16M \ --max-fps=60 \ --tcpip=192.168.10.181:5555 \ --start-app=io.legado.app.release 参数解释 --audio-codec=aac同步声音,使用 AAC 编码,默认参数可能导致没有声音 --video-codec=h264使用 H.264 视频压缩编码 --video-bit-rate=16M16Mbps 码率,高画质 --max-fps=60最大帧率 60FPS,画面流畅 --tcpip=192.168.10.181:5555Wi-Fi adb 连接设备 --start-app=io.legado.app.release (可选)连接后直接启动 Legado App,应用列表可使用adb shell pm list packages -3查看 iOS 中使用 scrcpy-mobile appstore中安装 scrcpy-mobile 由于该应用的用户界面易用性比较差,表单也不支持有些 scrcpy 参数,这里直接使用快捷指令打开应用。 设置打开 scrcpy-mobile 后,开启引导式访问,防止误触。同时可以在 iOS 设置中开启引导式访问的面容id,防止频繁输入密码。 scrcpy-mobile 的 url schema 1 scrcpy2://192.168.10.181:5555?enable-audio=true&audio-codec=aac&video-bit-rate=16M&video-codec=h264&max-fps=60 最后,这也未尝不算一种 NTR 安卓环境设置(可选) 关闭导航栏:系统 > 手势 > 系统导航 > 手势导航 加快或者关闭系统动画:开发者选项 > 窗口动画缩放、过渡动画缩放、Animator 时长比例 > 关闭或者0.5x

2025/4/26
articleCard.readMore

在 VSCode 中用 Rust 刷LeetCode

本文介绍在 VSCode 中配置和使用插件来高效地解决 LeetCode 问题,并使用 Rust 语言编写和测试代码。 vscode 插件 LeetCode.vscode-leetcode pucelle.run-on-save rust-lang.rust-analyzer 项目结构 cargo new vscode-leetcode-rust 1 2 3 4 5 6 7 8 9 10 // tree -I target --dirsfirst . ├── Cargo.lock ├── Cargo.toml └── src ├── lib.rs ├── main.rs └── solutions ├── 1_two_sum.rs ... vscode 全局设置 1 2 3 4 5 6 7 8 "leetcode.useEndpointTranslation": false, // for english filename "leetcode.workspaceFolder": "/Users/<your_name>/projects/vscode-leetcode-cn-rust", "leetcode.filePath": { "default": { "folder": "src/solutions", "filename": "${id}_${snake_case_name}.${ext}" } }, 用 automod 宏添加新回答到模块 cargo add automod 1 2 3 4 5 6 7 // src/lib.rs const CURRENT: &str = "sdfsdfsd.rs"; // trigger rust-analyser recheck pub mod solutions { automod::dir!("src/solutions"); } 触发 rust-analyzer 用 run-on-save 插件,保存回答时更新 lib.rs,触发 rust-analyzer 重新分析项目,开启新回答的代码补全。 vscode 项目配置 1 2 3 4 5 6 7 8 "runOnSave.commands": [ { // use gnu sed update lib.rs when add solutions (macOS) "command": "sh onsave.sh ${fileBasename}", "runIn": "backend", "finishStatusMessage": "touched ${workspaceFolderBasename}" }, ] onsave.sh脚本,macOS使用 gnused,linux 使用默认 sed 就好。 通过切换模块是否为pub来触发 rust-analyzer 识别新回答 1 2 3 4 5 6 7 8 9 10 11 12 #!/bin/bash FILE=$1 if [[ `grep "$FILE" src/lib.rs | wc -l` -eq 0 ]]; then gsed -i -E "/CURRENT/c\const CURRENT: &str = \"$FILE\";" src/lib.rs if [[ `grep '::dir!(pub' src/lib.rs | wc -l` -eq 1 ]]; then gsed -i "s|::dir!(pub |::dir!(|" src/lib.rs else gsed -i "s|::dir!(|::dir!(pub |" src/lib.rs fi fi 编写本地测试用例 把测试代码写在 "// @lc code=end" 后面,需要定义 Solution 结构体,可能还需要定义参数的结构体。 1 2 3 4 5 6 7 8 // @lc code=end struct Solution; #[test] fn test_a() { // let res = Solution::is_valid(String::from("()[]{}")); // println!("RESUTL\t{:?}", res); }

2025/3/8
articleCard.readMore

跨域的那些事

什么是跨域? 就是当前域访问了非本域的资源。对于http来说,url代表资源,也就是访问了非本域的url。 域的定义是啥? 这里的域,也就是同源策略(Same-origin policy)中的源。 如果两个 URL 的协议、端口和主机都相同的话,则这两个 URL 是同源的。 当前域:当前网页的域;目标域:访问的资源所在的域 为什么要限制跨域请求?信息泄露 js直接读取浏览器的目标域的sessionid、cookie,伪造请求。 比如,当前站点是恶意站点,它用js请求银行站点,盗取信息。 CSRF,如img标签的src会被访问,利用受害者已认证的身份,在不知情的情况下向受害者认证的站点发起恶意请求。 通过在src里构造url,恶意请求目标域 1 2 <img src="https://bank.com/transfer?amount=1000&to=attackerAccount" style="display:none;"> XSS跨站脚本(Cross-site scripting)注入,导致用户在信息泄露 一般是接受了用户构造的输入,输入里包含恶意脚本内容,内容未被转义,又输出在页面上,从而被执行 其他用户查看了该页面,注入的脚本被执行 怎么限制跨域?浏览器的同源策略 禁止的 DOM 同源策略: 不同源的dom之间不能相互操作,多个iframe的情况 XMLHttpRequest 同源策略: 禁止请求不同源url Cookie、LocalStorage、IndexedDB 等存储性内容同源策略 允许的 页面中的链接,重定向以及表单提交 <script>、<img>、<link>这些包含 src 属性的标签可以加载跨域资源。(只能GET) 限制跨域导致哪些不便? 前后端分离开发时,localhost不能正常访问后端资源 一些公共的api不能被访问 https的页面的http的静态资源,不能加载 如何绕过同源策略? 浏览器启动参数(在用户端操作) 反向代理(在当前域操作) JSONP(在目标域操作) 利用<script>允许跨域的特点,设置标签的src为目标域,动态生成需要的javascript内容 跨源资源共享(CORS)(目标域操作) 设置相应的reponse header 1 2 3 4 Access-Control-Allow-Origin: https://foo.example // 所允许的来源域 Access-Control-Allow-Methods: POST, GET, OPTIONS // 所允许的请求方法 Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 所允许的请求header Access-Control-Max-Age: 86400 参考 https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS https://developer.mozilla.org/zh-CN/docs/Glossary/CSRF https://juejin.cn/post/6879360544323665928 https://juejin.cn/post/6867096987804794888

2023/8/15
articleCard.readMore

HomeBrew 与无 root 权限 Linux 环境包管理

一些公用的 Linux 服务器,处于维护以及安全考虑,一般只会提供普通权限用户给使用者。 普通用户的权限满足日常使用是够了,但是难以配置自己的开发环境,安装一些自己需要的包。 如果都从源码编译安装软件,依赖的维护过于复杂,初始编译工具链的版本可能也不满足需求,如 gcc 版本过低。 如果申请 sudo 权限或者请求更新系统或安装 docker,后期责任难以界定,运维和管理员一般也不会同意。 所以,最优方案还是有需求的用户在个人目录维护自己的工具链和环境。下文方案为围绕 HomeBrew 构建。 安装 miniconda 解决前置依赖 如果你的系统比较新,可以直接尝试安装 HomeBrew。 基于上面讨论的内容,公用服务器一般存在系统版本低的问题,是 centos7 或者 centos6 也毫不稀奇,而且如 glibc 等库的版本也非常低。 安装 HomeBrew 有两个强依赖,git 及 curl,而且依赖的版本都比较高,centos7 的版本也不能满足。 另外,由于 Brew 不少软件都需要从源码编译,gcc 和良好的网络环境也不可缺少。 幸好 miniconda 能够解决以上几点问题。miniconda 只是提供 HomeBrew 安装的依赖,后续可以删除。 配置 conda 源(可选): 新建 .condarc,包含以下内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 pytorch-lts: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud 下载及安装 miniconda 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 下载 # 如果服务器的内置证书已过期, 增加 --no-check-certificate 条件跳过证书验证 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && chmod +x Miniconda3-latest-Linux-x86_64.sh # 安装 ./Miniconda3-latest-Linux-x86_64.sh -b -p ~/miniconda3 source ~/miniconda3/etc/profile.d/conda.sh # 安装所需包 conda install -y gcc_linux-64 gxx_linux-64 curl git # 链接 gcc,等 HomeBrew 安装完成这些链接可以删掉 cd ~/miniconda3/bin/ ln -s x86_64-conda_cos6-linux-gnu-gcc gcc ln -s x86_64-conda_cos6-linux-gnu-cpp c++ ln -s gcc cc 配置安装环境变量 这些环境变量也可以配置到 bashrc 等文件,使之永久生效 1 2 3 4 5 6 7 8 9 10 11 # 设置 curl 和 git,可选 export HOMEBREW_CURL_PATH=~/miniconda3/bin/curl export HOMEBREW_GIT_PATH=~/miniconda3/bin/git # 设置安装源为清华源,如果网络畅通可忽略,清华源也可能403 export HOMEBREW_INSTALL_FROM_API=1 export HOMEBREW_API_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api" export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles" export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git" export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git" 安装 HomeBrew 由于没有 root 权限,HomeBrew 需要手动安装。 由于是手动安装,位置与默认安装位置不同,很多预编译的包就不能用了,都得从源码编译,所以网络和机器性能以及耐心很重要。 1 2 3 4 5 6 7 8 9 10 # 下载,也可使用清华源 https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git git clone https://github.com/Homebrew/brew ~/.homebrew # 安装 eval "$(~/.homebrew/bin/brew shellenv)" brew update --force --quiet chmod -R go-w "$(brew --prefix)/share/zsh" # 自动加载 brew echo 'eval "$(~/.homebrew/bin/brew shellenv)"' >> ~/.bashrc 参考 https://docs.brew.sh/Installation https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/ https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/ https://github.com/tuna/issues/issues/1353

2023/5/28
articleCard.readMore

给 macOS 词典增加生词本功能

macOS 系统的自带词典应用非常强大,与其他应用整合很好,快捷取词很方便(command+control+d)。 但是美中不足的是缺少生词本功能,查了单词又很容易忘记,对语言学习者来说就有些不便了。 经过本强迫症的探索,终于找到基于 Karabiner-Elements + Automator + Logseq 的完美生词本方案。 最后的效果是,快捷键取词的同时记录单词卡片到Logseq对应的笔记。 词典词库扩充 参考知乎文章安装好《朗道英汉字典5.0》 这是为了有个释义简洁的词典,方便后续生成生词本词条 编写workflow 使用 macOS 自带应用 Automator(自动操作)编写workflow,将当前鼠标所在位置的文本提取并保存制卡。 首先打开 Automator.app 新建一个 Quick Aciont(快速操作) 然后依次拖入“获得词语定义”,“运行Shell脚本”等步骤,并调整如下几个位置的选项。 修改脚本里的代码为如下内容,生词本路径相应替换,并相应位置新建好生词本文件。 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 # -*- coding:utf-8 -*- from __future__ import unicode_literals, print_function import sys, os, io, subprocess FILE=os.path.expanduser("~/weiyun_sync/!sync/logseq-note/pages/生词本.md") output = [] text = sys.argv[1].decode('utf8') if sys.version_info.major == 2 else sys.argv[1] lines = [i.strip() for i in text.splitlines() if i.strip()] if len(lines) < 2: exit(0) word = lines[0] if lines[1][0] == '*': output.append('- {}\t{} [[card]]'.format(word, lines[1])) lines = lines[2:] else: output.append('- {}\t [[card]]'.format(word)) lines = lines[1:] output.append('\t- {}'.format(lines[0])) for line in lines[1:]: output.append('\t ' + line) old_words = set() with io.open(FILE, 'r', encoding='utf8') as fp: for line in fp: parts = line.split() if line.startswith('-') and len(parts) > 1: old_words.add(parts[1]) if word not in old_words: with io.open(FILE, 'a', encoding='utf8') as fp: fp.write('\n') fp.write('\n'.join(output)) fp.write('\n') subprocess.check_call(['osascript', '-e', u'display notification "添加 {}" with title "生词本"'.format(word)]) else: subprocess.check_call(['osascript', '-e', u'display notification "跳过 {}" with title "生词本"'.format(word)]) 选择路径保存好 workflow,然后在 键盘 - 快捷键 - 服务 中能看到新建的workflow。 为它设置快捷键 command + shift + alt + 1 Karabiner-Elements Karabiner-Elements 是 macOS 平台的一个重新映射快捷键的软件。 这里我们使用它将“查询单词”和“触发workflow”整合在一起,当然它还支持很多用途,这里就不赘述了。 注意确保Karabiner相关权限,并且设置中下图相关设备是勾选状态 安装好Karabiner-Elements后,打开它的配置文件 路径在 /Users/<用户名>/.config/karabiner/karabiner.json 在 profiles -> complex_modifications -> rules 列表中增加一项配置,内容如下。 然后保存,Karabiner会自动加载新的配置。 这里是将鼠标的侧键(靠前的)映射为查单词的快捷键,实现一键查词。 也可以根据需要更改按键,通过EventViewer可以查看按键代码,配置文件格式可参考官方文档 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 { "description": "Mouse", "manipulators": [ { "from": { "pointing_button": "button5" }, "to": [ { "pointing_button": "button1" }, { "pointing_button": "button1" }, { "key_code": "d", "modifiers": [ "left_command", "left_control" ] }, { "key_code": "1", "modifiers": [ "left_option", "left_shift", "left_command" ] } ], "type": "basic" } ] } 效果 可以看到 Logseq 中卡片生成的效果 参考 https://karabiner-elements.pqrs.org/docs/ https://zhuanlan.zhihu.com/p/433646737 https://hectorguo.com/zh/save-words-in-dictionary/ https://github.com/jjgod/mac-dictionary-kit https://lightcss.com/mac-dictionary/

2022/11/12
articleCard.readMore

关闭子进程打开的文件描述符

我们在测试代码时,由于需要经常重启服务,经常会发现服务端口被占用。 一般kill掉后台进程就ok了,但是如果服务有启动一些常驻的后台程序,可能也会导致端口不能释放。 在类UNIX系统中,一切被打开的文件、端口被抽象为文件描述符(file descriptor) 从python3.4开始,文件描述符默认是non-inheritable,也就是子进程不会共享文件描述符。 问题 一般为了实现多进程、多线程的webserver,服务端口fd必须设置为继承(set_inheritable),这样才能多进程监听一个端口(配合SO_REUSEPORT) 典型的是使用flask的测试服务器的场景,这里我们写一段代码模拟。 1 2 3 4 5 6 import socket, os server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 22222)) server.set_inheritable(True) os.system("python -c 'import time;time.sleep(1000)' ") 我们通过lsof -p {pid}可以看到这两个进程的所有文件描述符 server进程, 可以看到服务端口的fd是4 1 2 3 4 5 6 7 8 9 10 11 COMMAND PID FD TYPE DEVICE SIZE/OFF NODE NAME ptpython 6214 cwd DIR 253,0 4096 872946898 / ... ptpython 6214 0u CHR 136,13 0t0 16 /dev/pts/13 ptpython 6214 1u CHR 136,13 0t0 16 /dev/pts/13 ptpython 6214 2u CHR 136,13 0t0 16 /dev/pts/13 ptpython 6214 3r CHR 1,9 0t0 2057 /dev/urandom ptpython 6214 4u sock 0,7 0t0 58345077 protocol: TCP ptpython 6214 5u a_inode 0,10 0 8627 [eventpoll] ptpython 6214 6u unix 0x0000000000000000 0t0 58368029 socket ptpython 6214 7u unix 0x0000000000000000 0t0 58368030 socket sleep子进程,也拥有fd=4的文件描述符 1 2 3 4 5 6 7 COMMAND PID FD TYPE DEVICE SIZE/OFF NODE NAME python 18022 cwd DIR 253,0 4096 872946898 / ... python 18022 0u CHR 136,13 0t0 16 /dev/pts/13 python 18022 1u CHR 136,13 0t0 16 /dev/pts/13 python 18022 2u CHR 136,13 0t0 16 /dev/pts/13 python 18022 4u sock 0,7 0t0 58345077 protocol: TCP 如果server进程退出时,sleep进程没有退出,fd=4对应的端口就被占用了,服务也就不能正常启动了。 解决方法 手动清理 1 2 3 4 5 6 7 8 import os import time os.system(f'lsof -p {os.getpid()}') os.closerange(3, 100) # 这里假定打开文件描述符不会超过100 time.sleep(5) os.system(f'lsof -p {os.getpid()}') # 后面执行需要的业务代码 使用close_fds 使用subprocess库而不是os来启动子程序, 通过close_fds参数关闭多余的文件描述符 1 2 import subprocess subprocess.call("python -c 'import time;time.sleep(1000)'", shell=True, close_fds=True) 参考 https://docs.python.org/3/library/os.html#inheritance-of-file-descriptors https://docs.python.org/3/library/subprocess.html#subprocess.Popen https://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python#answer-25069136

2022/8/30
articleCard.readMore

容器内进程优雅退出

在使用 docker 时,常常会碰到进程退出时资源清理的问题,比如保证当前请求处理完成,再退出程序。 当执行 docker stop xxx 时,docker会向主进程(pid=1)发送 SIGTERM 信号 如果在一定时间(默认为10s)内进程没有退出,会进一步发送 SIGKILL 直接杀死程序,该信号既不能被捕捉也不能被忽略。 一般的web框架或者rpc框架都集成了 SIGTERM 信号处理程序, 一般不用担心优雅退出的问题。 但是如果你的容器内有多个程序(称为胖容器,一般不推荐),那么就需要做一些操作保证所有程序优雅退出。 signals 信号是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。 应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉。 常见信号: 信号名称信号数描述默认操作 SIGHUP1当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。终止进程 SIGINT2程序终止(interrupt)信号,在用户键入 Ctrl+C 时发出。终止进程 SIGQUIT3和SIGINT类似,但由QUIT字符(通常是Ctrl /)来控制。终止进程并dump core SIGFPE8在发生致命的算术运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术错误。终止进程并dump core SIGKILL9用来立即结束程序的运行。本信号不能被阻塞,处理和忽略。终止进程 SIGALRM14时钟定时信号,计算的是实际的时间或时钟时间。alarm 函数使用该信号。终止进程 SIGTERM15通常用来要求程序自己正常退出;kill 命令缺省产生这个信号。终止进程 Dockerfile 下面以 supervisor 为例,Dockerfile 如下 1 2 3 4 5 6 7 8 FROM centos:centos7 ENV PYTHONUNBUFFERED=1 TZ=Asia/Shanghai RUN yum -y install epel-release && \ yum -y install supervisor && \ yum -y clean all && rm -rf /var/cache COPY ./ /root/ ENTRYPOINT [ "/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf" ] trap 正常情况,容器退出时supervisor启动的其他程序并不会收到 SIGTERM 信号,导致子程序直接退出了。 这里使用 trap 对程序的异常处理进行包装 1 trap <siginal handler> <signal 1> <signal 2> ... 新建一个初始化脚本,init.sh 1 2 3 4 5 6 7 #!/bin/sh /usr/bin/supervisord -n -c /etc/supervisord.conf & trap "supervisorctl stop all && sleep 3" TERM INT wait 修改 ENTRYPOINT 为如下 1 ENTRYPOINT ["sh", "/root/init.sh"] 参考 https://www.ctl.io/developers/blog/post/gracefully-stopping-docker-containers/ https://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html https://wangchujiang.com/linux-command/c/trap.html

2022/7/10
articleCard.readMore

Python 循环变量泄露与延迟绑定

循环变量泄露与延迟绑定叠加在一起,会产生一些让人迷惑的结果。 梦开始的地方 先看看一开始的问题,可以看到这里lambda函数的返回值一直在变。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 xx = [] for i in [1,2,3]: xx.append(lambda: i) print('a:', xx[0]()) for j in xx: print(j()) print('b:', xx[0]()) for i in xx: print(i, i()) print('c:', xx[0], xx[0]()) for i in [4, 5, 6]: print(i) print('d:', xx[0], xx[0]()) 输出如下 1 2 3 4 5 6 7 8 9 10 11 12 13 a: 3 3 3 3 b: 3 <function main2.<locals>.<lambda> at 0x10ca30310> <function main2.<locals>.<lambda> at 0x10ca30310> <function main2.<locals>.<lambda> at 0x10ca303a0> <function main2.<locals>.<lambda> at 0x10ca303a0> <function main2.<locals>.<lambda> at 0x10ca30430> <function main2.<locals>.<lambda> at 0x10ca30430> c: <function main2.<locals>.<lambda> at 0x10ca30310> <function main2.<locals>.<lambda> at 0x10ca30430> 4 5 6 d: <function main2.<locals>.<lambda> at 0x10ca30310> 6 循环变量泄露 由于Python没有块级作用域,所以循环会改变当前作用域变量的值,也就是循环变量泄露。 注意:Python3中列表推导式循环变量不会泄露,Python2中和常规循环一样泄露。 1 2 3 4 5 x = -1 for x in range(7): if x == 6: print(x, ': for x inside loop') print(x, ': x in global') 输出如下 1 2 6 : for x inside loop 6 : x in global 闭包与延迟绑定 再讲一下闭包,在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包。 这里所谓的引用可以也就是内部函数记住了变量的名称(而不是值,这个从ast语法树可以看出),而变量对应的值是会变化的。 如果在循环中定义闭包,引用的变量的值在循环结束才统一确定为最后一次循环时的值,也就是延迟绑定(lazy binding)。 所以下面的例子,xx的所有匿名函数的返回值均为3 1 2 3 xx = [] for i in [1,2,3]: xx.append(lambda: i) 最后 再分析一开始的问题,这里的匿名函数引用了变量i,而i是全局变量,所以再次使用i作为循环变量时,列表中的匿名函数引用的值就被覆盖了。 正确做法: 在独立的函数中定义闭包 闭包引用的变量应该是其他函数不可修改的 优先使用列表推导式 参考 https://stackoverflow.com/questions/3611760/scoping-in-python-for-loops https://www.educative.io/courses/python-ftw-under-the-hood/N8RW8508RkL https://mail.python.org/pipermail/python-ideas/2008-October/002109.html

2022/3/4
articleCard.readMore

bash 语法备忘

bash 语法作为程序员好像都了解一些,但又缺少体系化学习,需要使用到某些功能时又经常手忙脚乱地查。 本文主要参考阮一峰的bash教程,对bash的知识点进行了梳理。 本文目的是作为bash的语法备忘录、语法速查表。 模式扩展 模式扩展(globbing),类似C语言中的宏展开,我们通常使用的通配符*就是其中之一。 Bash 一共提供八种扩展,前4种为文件扩展,只有文件路径确实存在才会扩展。 ~ 波浪线扩展 ? 问号扩展 * 星号扩展 [] 方括号扩展 {} 大括号扩展 $var 变量扩展 $(date) 命令扩展 $((1 + 1)) 算术扩展 波浪线扩展 波浪线~会自动扩展成当前用户的主目录。 ~user表示扩展成用户user的主目录。如果用户不存在,则波浪号扩展不起作用。 1 2 3 4 5 6 7 8 bash-5.1$ echo ~/projects/ /Users/ruan/projects/ bash-5.1$ echo ~root/.ssh /var/root/.ssh bash-5.1$ echo ~aaa/.ssh ~aaa/.ssh 问号扩展 ?字符代表文件路径里面的任意单个字符,不包括空字符。 只有文件确实存在的前提下,才会发生扩展。 1 2 3 4 5 6 7 bash-5.1$ touch {a,b}.txt ab.txt bash-5.1$ ls ?.txt a.txt b.txt bash-5.1$ ls ??.txt ab.txt 星号扩展 *字符代表文件路径里面的任意数量的任意字符,包括零个字符。 1 2 3 4 5 6 7 8 9 bash-5.1$ ls *.txt a.txt ab.txt b.txt bash-5.1$ ls /usr/local/Cellar/*/*/bin/z* /usr/local/Cellar/ffmpeg/4.4_2/bin/zmqsend /usr/local/Cellar/mysql-client/8.0.26/bin/zlib_decompress /usr/local/Cellar/netpbm/10.86.24/bin/zeisstopnm /usr/local/Cellar/perl/5.34.0/bin/zipdetails /usr/local/Cellar/zstd/1.5.0/bin/zstd 方括号扩展 方括号扩展的形式是[...],只有文件确实存在的前提下才会扩展。 [^...]和[!...]。它们表示匹配不在方括号里面的字符 方括号扩展有一个简写形式[start-end],表示匹配一个连续的范围 1 2 3 4 5 6 7 8 bash-5.1$ ls [ab].txt a.txt b.txt bash-5.1$ ls [^b]b.txt ab.txt bash-5.1$ ls [a-b].txt a.txt b.txt 大括号扩展 大括号扩展{...}表示分别扩展成大括号里面的所有值 大括号也可以与其他模式联用,并且总是先于其他模式进行扩展。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bash-5.1$ echo {1,2,3} 1 2 3 bash-5.1$ echo a{1,2,3}b a1b a2b a3b bash-5.1$ echo --exclude={a,b,c} --exclude=a --exclude=b --exclude=c bash-5.1$ echo foo{1,2{1,2}0,3}bar foo1bar foo210bar foo220bar foo3bar bash-5.1$ echo {1..3} 1 2 3 bash-5.1$ echo {1..10..2} 1 3 5 7 9 变量扩展 Bash 将美元符号$开头的词元视为变量,将其扩展成变量值 1 2 bash-5.1$ echo $HOME /Users/ruan 命令扩展 $(...)可以扩展成另一个命令的运行结果,该命令的所有输出都会作为返回值。 1 2 3 4 5 bash-5.1$ echo $(date) 日 11 28 16:22:09 CST 2021 bash-5.1$ echo `date` 日 11 28 16:22:24 CST 2021 算术扩展 $((...))可以扩展成整数运算的结果 1 2 bash-5.1$ echo $((1+1)) 2 引号使用 单引号 单引号用于保留字符的字面含义,在单引号里转义字符和模式扩展都会失效。 1 2 3 4 5 bash-5.1$ ls '[ab].txt' ls: cannot access '[ab].txt': No such file or directory bash-5.1$ ls '*' ls: cannot access '*': No such file or directory 双引号 双引号比单引号宽松,三个特殊字符除外:美元符号($)、反引号(`)和反斜杠(\)。这三个字符,会被 Bash 自动扩展。 也就是说,相比单引号在双引号中变量扩展,命令扩展,算术扩展以及转义字符是有效的。 1 2 3 4 5 6 7 8 9 10 11 bash-5.1$ echo "$((1+1))" 2 bash-5.1$ echo "$HOME" /Users/ruan bash-5.1$ echo "$(date)" 日 11 28 16:35:27 CST 2021 bash-5.1$ echo -e "1\t2" 12 引号嵌套 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 双引号中使用单引号 bash-5.1$ echo '"' " bash-5.1$ echo '"${HOME}"' "${HOME}" # 单引号中使用双引号 bash-5.1$ echo "'${HOME}'" '/Users/ruan' bash-5.1$ echo "'\"${HOME}\"'" '"/Users/ruan"' # 引号嵌套中使用模式扩展,将需要扩展的字符放在单引号中;典型的有json变量填充 bash-5.1$ echo '{"user": "'${USER}'"}' {"user": "ruan"} here doc Here 文档(here document)是一种输入多行字符串的方法,格式如下。 它的格式分成开始标记(<< token)和结束标记(token), 一般用字符串EOF作为token 1 2 3 << token text token 例如 1 2 3 4 5 6 7 8 bash-5.1$ cat << EOF > 11 > 22 > 33 > EOF 11 22 33 here string Here 文档还有一个变体,叫做 Here 字符串(Here string),使用三个小于号(<<<)表示。 它的作用是将字符串通过标准输入,传递给命令。 1 2 3 4 bash-5.1$ cat <<< foobar foobar bash-5.1$ echo foobar | cat foobar 变量 bash 是基于标准输入在不同进程间交互数据的,大部分功能都是在操作字符串,所以变量的默认类型也是字符串。 声明变量和读取变量 声明时等号两边不能有空格。 Bash 变量名区分大小写,HOME和home是两个不同的变量。 1 2 3 4 bash-5.1$ foo=1 bash-5.1$ echo $foo 1 变量查看和删除 1 2 3 4 5 6 7 8 9 10 11 12 # 查看所有变量, 其中包含父进程export的变量 bash-5.1$ set UID=501 USER=ruan bar=2 foo=1 bash-5.1$ unset foo bash-5.1$ set UID=501 USER=ruan bar=2 变量输出 用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。 如果希望子进程能够读到这个变量,需要使用export命令。 1 2 3 4 bash-5.1$ bash -c set | grep foo bash-5.1$ export foo=1 bash-5.1$ bash -c set | grep foo foo=1 环境变量 平时所说的环境变量,就是init进程export输出的。子进程对变量的修改不会影响父进程,也就是说变量不是共享的。 1 2 3 4 5 # 查看环境变量 bash-5.1$ env SHELL=/bin/zsh LSCOLORS=Gxfxcxdxbxegedabagacad ITERM_PROFILE=Default 下面是一些常见的环境变量。 BASHPID:Bash 进程的进程 ID。 BASHOPTS:当前 Shell 的参数,可以用shopt命令修改。 DISPLAY:图形环境的显示器名字,通常是:0,表示 X Server 的第一个显示器。 EDITOR:默认的文本编辑器。 HOME:用户的主目录。 HOST:当前主机的名称。 IFS:词与词之间的分隔符,默认为空格。 LANG:字符集以及语言编码,比如zh_CN.UTF-8。 PATH:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。 PS1:Shell 提示符。 PS2: 输入多行命令时,次要的 Shell 提示符。 PWD:当前工作目录。 RANDOM:返回一个0到32767之间的随机数。 SHELL:Shell 的名字。 SHELLOPTS:启动当前 Shell 的set命令的参数 TERM:终端类型名,即终端仿真器所用的协议。 UID:当前用户的 ID 编号。 USER:当前用户的用户名。 特殊变量 Bash 提供一些特殊变量。这些变量的值由 Shell 提供,用户不能进行赋值。 $?: 上一个命令的退出码, 0为成功,其他为失败 $$: 当前进程的pid $_: 为上一个命令的最后一个参数 $!: 为最近一个后台执行的异步命令的进程 ID。 $0: bash脚本的参数列表,0是脚本文件路径,1到n是第1到第n个参数 变量默认值 ${varname:-word}: 如果变量varname存在且不为空,则返回它的值,否则返回word ${varname:=word}: 如果变量varname存在且不为空,则返回它的值,否则将它设为word,并且返回word。 ${varname:+word}: 如果变量名存在且不为空,则返回word,否则返回空值。它的目的是测试变量是否存在。 ${varname:?message}: 如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行。 declare 命令 declare命令的主要参数(OPTION)如下。 -a:声明数组变量。 -A:声明关联数组变量。 -f:输出所有函数定义。 -F:输出所有函数名。 -i:声明整数变量。 -p:查看变量信息。 -r:声明只读变量。 -x:该变量输出为环境变量。 数据类型 bash 有字符串,数字,数字,关联数组四种数据类型,默认是字符串,其他类型需要手动声明。 字符串 定义 语法 varname=value 1 2 3 bash-5.1$ s1=abcdefg bash-5.1$ echo $s1 abcdefg 获取长度(length) 语法 ${#varname} 1 2 bash-5.1$ echo ${#s1} 7 子字符串(substr) 语法 ${varname:offset:length}, offset为负数的时候,前面要加空格,防止与默认值语法冲突。 1 2 3 4 5 6 7 bash-5.1$ s1=abcdefg bash-5.1$ echo ${s1:1:3} bcd bash-5.1$ echo ${s1: -6:2} bc bash-5.1$ echo ${s1: -6:3} bcd 替换 (replace) 字符串头部的模式匹配 ${variable#pattern}: 删除最短匹配(非贪婪匹配)的部分,返回剩余部分 ${variable##pattern}: 删除最长匹配(贪婪匹配)的部分,返回剩余部分 匹配模式pattern可以使用*、?、[]等通配符。 1 2 3 4 5 6 7 $ myPath=/home/cam/book/long.file.name $ echo ${myPath#/*/} cam/book/long.file.name $ echo ${myPath##/*/} long.file.name 字符串尾部的模式匹配 ${variable%pattern}: 删除最短匹配(非贪婪匹配)的部分,返回剩余部分 ${variable%%pattern}: 删除最长匹配(贪婪匹配)的部分,返回剩余部分 1 2 3 4 5 6 7 $ path=/home/cam/book/long.file.name $ echo ${path%.*} /home/cam/book/long.file $ echo ${path%%.*} /home/cam/book/long 任意位置的模式匹配 如果匹配pattern则用replace替换匹配的内容 ${variable/pattern/replace}: 替换第一个匹配 ${variable//pattern/replace}: 替换所有匹配 1 2 3 4 5 6 7 $ path=/home/cam/foo/foo.name $ echo ${path/foo/bar} /home/cam/bar/foo.name $ echo ${path//foo/bar} /home/cam/bar/bar.name 数字 使用 declare -i声明整数变量。 1 2 3 4 5 6 7 8 9 10 11 12 # 声明为整数,可以直接计算,不需要使用$符号 bash-5.1$ declare -i val1=12 val2=5 bash-5.1$ echo $val1 12 bash-5.1$ val1+=val2 bash-5.1$ echo $val1 17 # 一个变量声明为整数以后,依然可以被改写为字符串。Bash 不会报错,但会赋以不确定的值 bash-5.1$ val1=aaa bash-5.1$ echo $val1 0 数值的进制 Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。 number:没有任何特殊表示法的数字是十进制数(以10为底)。 0number:八进制数。 0xnumber:十六进制数。 base#number:base进制的数。 1 2 3 4 5 6 7 8 9 10 11 bash-5.1$ declare -i a=0x77 bash-5.1$ echo $a 119 bash-5.1$ declare -i a=0xfe bash-5.1$ echo $a 254 bash-5.1$ declare -i a=2#111 bash-5.1$ echo $a 7 算术表达式 ((...))语法可以进行整数的算术运算。 支持的算术运算符如下。 +:加法 -:减法 *:乘法 /:除法(整除) %:余数 **:指数 ++:自增运算(前缀或后缀) --:自减运算(前缀或后缀) 如果要读取算术运算的结果,需要在((...))前面加上美元符号$((...)),使其变成算术表达式,返回算术运算的值。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bash-5.1$ echo $((1+1)) 2 bash-5.1$ echo $((1-1)) 0 bash-5.1$ echo $((1*2)) 2 bash-5.1$ echo $((1/2)) 0 bash-5.1$ echo $((5%2)) 1 bash-5.1$ a=1 bash-5.1$ echo $((a++)) 1 bash-5.1$ echo $((a++)) 2 bash-5.1$ echo $((++a)) 4 数组 创建数组 array=(item1 item2) 语法可初始化数组,括号内可以换行,多行初始化可以用#注释。 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 # 直接初始化数组 bash-5.1$ a=(1 2 3) bash-5.1$ echo ${a[@]} 1 2 3 # 多行初始化数组 bash-5.1$ a=( > 1 > 2 > 3 > #4 > ) bash-5.1$ echo ${a[@]} 1 2 3 # 模式扩展初始化 bash-5.1$ a=({1..3}) bash-5.1$ echo ${a[@]} 1 2 3 # declare -a命令声明一个数组,也是可以的。 bash-5.1$ declare -a b bash-5.1$ b+=(1) bash-5.1$ echo ${b[@]} 1 #read -a命令则是将用户的命令行输入,存入一个数组。 bash-5.1$ read -a c 11 22 33 bash-5.1$ echo ${c[@]} 11 22 33 访问数组元素 array[index] 语法可访问数组元素,不带index访问则是访问数组首个元素。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bash-5.1$ a=(1 2 3) # 查看元素 bash-5.1$ echo ${a[1]} 2 # 元素赋值 bash-5.1$ a[1]+=1 bash-5.1$ echo ${a[1]} 21 bash-5.1$ a[1]=22 bash-5.1$ echo ${a[1]} 22 # bash-5.1$ a[0]=1111 bash-5.1$ echo ${a} 1111 bash-5.1$ echo ${a[0]} 1111 数组长度 ${#array[@]} 和 ${#array[*]} 可访问获得数组长度 1 2 3 4 5 bash-5.1$ a=(1 2 3) bash-5.1$ echo ${#a[*]} 3 bash-5.1$ echo ${#a[@]} 3 获取非空元素下标 ${!array[@]} 或 ${!array[*]}, 可以获得非空元素的下标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bash-5.1$ a=(1 2 3) bash-5.1$ a[6]=6 bash-5.1$ echo ${a[0]} 1 bash-5.1$ echo ${a[@]} 1 2 3 6 bash-5.1$ echo ${a[3]} bash-5.1$ echo ${a[4]} bash-5.1$ echo ${!a[@]} 0 1 2 6 # 注意此时数组长度为4,并不是7 bash-5.1$ echo ${#a[@]} 4 数组元素切片 ${array[@]:position:length}的语法可以提取数组成员。 1 2 3 4 5 bash-5.1$ a=({1..10}) bash-5.1$ echo ${a[@]} 1 2 3 4 5 6 7 8 9 10 bash-5.1$ echo ${a[@]:1:3} 2 3 4 数组追加元素 数组末尾追加元素,可以使用+=赋值运算符。 1 2 3 4 5 6 bash-5.1$ a=({1..10}) bash-5.1$ echo ${a[@]} 1 2 3 4 5 6 7 8 9 10 bash-5.1$ a+=(11 12) bash-5.1$ echo ${a[@]} 1 2 3 4 5 6 7 8 9 10 11 12 删除元素 删除一个数组成员,使用unset命令。 1 2 3 4 5 6 7 8 bash-5.1$ a=(1 2 3) # 删除单个元素 bash-5.1$ unset a[1] bash-5.1$ echo ${a[@]} 1 3 # 删除整个数组 bash-5.1$ unset a bash-5.1$ echo ${a[@]} 关联数组 declare -A可以声明关联数组,关联数组使用字符串而不是整数作为数组索引。 除了初始化外,使用方法和数组基本相同 1 2 3 4 5 6 7 bash-5.1$ declare -A a bash-5.1$ a['red']=1 bash-5.1$ a['blue']=2 bash-5.1$ echo ${a[@]} 2 1 bash-5.1$ echo ${a[@]:0:1} 2 控制流 注释 #表示注释,每行从#开始之后的内容代表注释,会被bash忽略. 1 2 bash-5.1$ echo 1111 # 222 1111 条件判断 bash 和常规编程语言一样使用if作为分支条件的关键字, fi作为结束的关键字,else和 elif子句是可选的 其中if和elif的condition所判断的内容是命令的状态码是否为0,为0则执行关联的语句。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 因为bash中分号(;)和换行是等价的,所以有下面两种风格,其他多行语句也是类似的 # 本人偏好风格1 # 风格1 if condition; then command elif condition; then command else command fi # 风格2 if condition then command elif condition then command else command fi 这里的condition可以是多个命令,如command1 && command2,或者command1 || command2,则if判断的是这两个命令的状态码的逻辑计算结果。 condition也是可以是command1; command2, 则则if判断的是最后一个命令的状态码。 这里最常用的condition是test命令, 也就是[[]]和[]. test是bash的内置命令,会执行给定的表达式,结果为真满足则返回状态码0, 否则返回状态码1. 下文循环语言的condition也是相同的,就不赘述了 1 2 3 4 5 6 bash-5.1$ test 1 -eq 1 bash-5.1$ echo $? 0 bash-5.1$ test 1 -eq 2 bash-5.1$ echo $? 1 [[]]和[]的区别是[[]]内部支持&&,||逻辑判断,所以以下三种写法是等价的。 由于[和]是命令, 所以两侧一定要有空格,也是就是[ 1 -eq 1 ],否则bash会认为命令找不到。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # test if test 1 -eq 2 || test 1 -eq 1; then echo True fi # [ ] if [ 1 -eq 2 ] || [ 1 -eq 1 ]; then echo True fi # [[ ]] if [[ 1 -eq 2 || 1 -eq 1 ]]; then echo True fi 逻辑操作符 判断条件支持且(&&)或(||)非(!) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # not if [[ ! 'aa' == 'bb' ]]; then echo True fi # or if [[ 1 -eq 2 || 1 -eq 1 ]]; then echo True fi # and if [[ 1 -ne 2 && 1 -eq 1 ]]; then echo True fi 判断时引号使用(quote) 使用[和test时,变量引用注意加双引号,否则得不到正确的结果,[[则不需要。 1 2 3 4 5 6 7 8 bash-3.2$ echo "$SSH_CLIENT" bash-3.2$ if [ -n $SSH_CLIENT ]; then echo 1; else echo 0; fi 1 bash-3.2$ if [ -n "$SSH_CLIENT" ]; then echo 1; else echo 0; fi 0 bash-3.2$ if [[ -n $SSH_CLIENT ]]; then echo 1; else echo 0; fi 0 字符串判断 bash默认数据类型为字符串,所以常见的 >, <是用于字符串判断。 注意:字符串判断不支持>=和<=, 得使用逻辑组合来替代 -z string:字符串串长度为0 -n string: 字符串长度大于0 string1 == string2: string1 等于 string2 string1 = string2: string1 等于 string2 string1 > string2: 如果按照字典顺序string1排列在string2之后 string1 < string2: 如果按照字典顺序string1排列在string2之前 数字(整数)判断 下面的表达式用于判断整数。 [ integer1 -eq integer2 ]:如果integer1等于integer2,则为true。 [ integer1 -ne integer2 ]:如果integer1不等于integer2,则为true。 [ integer1 -le integer2 ]:如果integer1小于或等于integer2,则为true。 [ integer1 -lt integer2 ]:如果integer1小于integer2,则为true。 [ integer1 -ge integer2 ]:如果integer1大于或等于integer2,则为true。 [ integer1 -gt integer2 ]:如果integer1大于integer2,则为true。 文件判断 以下表达式用来判断文件状态。仅列举常用判断,详细支持列表参考 https://tldp.org/LDP/abs/html/fto.html [ -a file ]:如果 file 存在,则为true。 [ -d file ]:如果 file 存在并且是一个目录,则为true。 [ -e file ]:如果 file 存在,则为true, 同-a。 [ -f file ]:如果 file 存在并且是一个普通文件,则为true。 [ -h file ]:如果 file 存在并且是符号链接,则为true。 [ -L file ]:如果 file 存在并且是符号链接,则为true, 同-h。 [ -p file ]:如果 file 存在并且是一个命名管道,则为true。 [ -r file ]:如果 file 存在并且可读(当前用户有可读权限),则为true。 [ -s file ]:如果 file 存在且其长度大于零,则为true。 [ -w file ]:如果 file 存在并且可写(当前用户拥有可写权限),则为true。 [ -x file ]:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true。 switch case bash也支持,switch case,语法如下。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 case EXPRESSION in PATTERN_1) STATEMENTS ;; PATTERN_2) STATEMENTS ;; PATTERN_N) STATEMENTS ;; *) STATEMENTS ;; esac 例如 1 2 3 4 5 6 7 8 9 10 11 a=2 case $a in 1) echo 11 ;; 2) echo 22 ;; *) ;; esac 循环 while 循环 while循环有一个判断条件,只要符合条件,就不断循环执行指定的语句。 condition与if语句的相同,就不赘述了。 1 2 3 while condition; do command done unitl 循环 until循环与while循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环。 1 2 3 until condition; do command done for-in 循环 for...in循环用于遍历列表的每一项。 1 2 3 for variable in list; do commands done 常见的几种用法 1 2 3 4 5 6 7 8 9 10 11 12 for i in 1 2 3; do echo $i done for i in {1..3}; do echo $i done list=(1 2 3) for i in ${list[@]}; do echo $i done for 循环 for循环还支持 C 语言的循环语法。 1 2 3 for (( expression1; expression2; expression3 )); do commands done 上面代码中,expression1用来初始化循环条件,expression2用来决定循环结束的条件,expression3在每次循环迭代的末尾执行,用于更新值。 注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号$。 例如 1 2 3 for ((i=1; i<=3; i++)); do echo $i done 跳出循环 Bash 提供了两个内部命令break和continue,用来在循环内部跳出循环。 break命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环。 continue命令立即终止本轮循环,开始执行下一轮循环。 函数 函数定义 Bash 函数定义的语法有两种,其中fn为定义的函数名称。 1 2 3 4 5 6 7 8 9 # 第一种 fn() { # codes } # 第二种 function fn() { # codes } 函数参数 函数体内可以使用参数变量,获取函数参数。函数的参数变量,与脚本参数变量是一致的。 ${N}:函数的第一个到第N个的参数。 $0:函数所在的脚本名。 $#:函数的参数总数。 $@:函数的全部参数,参数之间使用空格分隔。 $*:函数的全部参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。 函数调用 funcname arg1 arg ... argN 的语法进行函数调用。主要函数的返回值和输出值(标准输出)的区别,这和主流编程语言不同 1 2 3 4 5 6 7 8 9 10 11 add() { declare -i res res=0 for i in $@; do res+=$i done echo $res } # 结果为10 add 1 2 3 4 函数返回值 return命令用于从函数返回一个值。返回值和命令的状态码一样,可以用$?拿到值。 return也可以不接具体的值,则返回值是return命令的上一条命令的状态码。 如果不加return,则返回值是函数体最后一条命令的状态码。 1 2 3 function func_return_value { return 10 } 关键概念 shebang Shebang(也称为Hashbang)是一个由井号和叹号构成的字符序列#!, 其出现在可执行文本文件的第一行的前两个字符。 在文件中存在Shebang的情况下,类Unix操作系统的程序加载器会分析Shebang后的内容,将这些内容作为解释器指令,并调用该指令. 例如,shell脚本 1 2 3 #!/bin/bash echo Hello, world! python 脚本 1 2 3 #!/usr/bin/env python -u print("Hello, world!") 状态码 每个命令都会返回一个退出状态码(有时候也被称为返回状态)。 成功的命令返回 0,不成功的命令返回非零值,非零值通常都被解释成一个错误码。行为良好的 UNIX 命令、程序和工具都会返回 0 作为退出码来表示成功,虽然偶尔也会有例外。 状态码一般是程序的main函数的返回码,如c,c++。 如果是bash脚本,状态码的值则是 exit 命令的参数值。 当脚本以不带参数的 exit 命令来结束时,脚本的退出状态码就由脚本中最后执行的命令来决定,这与函数的 return 行为是一致的。 特殊变量$?可以查看上个命令的退出状态码 文件描述符 文件描述符在形式上是一个非负整数。指向内核为每一个进程所维护的该进程打开文件的记录表。 当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。 标准输入输出 每个Unix进程(除了可能的守护进程)应均有三个标准的POSIX文件描述符,对应于三个标准流: 0:标准输入 1:标准输出 2:错误输出 打开新的文件描述符 手动指定描述符 1 2 3 exec 3<> /tmp/foo #open fd 3. echo "test" >&3 exec 3>&- #close fd 3. 系统自动分配描述符,bash4.1开始支持(在macos报错,原因不明) 1 2 3 4 5 6 7 8 #!/bin/bash FILENAME=abc.txt exec {FD}<>"$FILENAME" echo 11 >&FD echo 22 >&FD $FD>&- 描述符重定向 command > file: 将输出重定向到 file。 command < file: 将输入重定向到 file。 command >> file: 将输出以追加的方式重定向到 file。 n > file: 将文件描述符为 n 的文件重定向到 file。 n >> file: 将文件描述符为 n 的文件以追加的方式重定向到 file。 n >& m: 将输出文件 m 和 n 合并。 n <& m: 将输入文件 m 和 n 合并。 所以命令中常见的ls -al > output.txt 2>&1, 就是将标准输出和错误输出都重定向到一个文件。 等价于ls -al &>output.txt,本人偏好这种写法,比较简洁。 IFS (Input Field Separators) IFS决定了bash在处理字符串的时候是如何进行单词切分。 IFS的默认值是空格,TAB,换行符,即 \t\n 1 2 3 $ echo "$IFS" | cat -et ^I$ $ 例如,在for循环的时候,如何区分每个item 1 2 3 for i in `echo -e "foo bar\tfoobar\nfoofoo"`; do echo "'$i' is the substring"; done 也可以自定义 1 2 3 4 5 6 7 OLD_IFS="$IFS" IFS=":" string="1:2:3" for i in $string; do echo "'$i' is the substring"; done IFS=$OLD_IFS 任务管理 linux进程分前台(fg)和后台(bg)。 在命令的末尾添加&可以将命令后台执行,一般配合输出重定向使用。 jobs可以查看当前bash进程的子进程,并通过fg和bg进行前台和后台切换。 %1代表后台的第一个进程,以此类推%N代表第n个. control + Z可以将当前前台程序暂停,配合bg可以将其转后台。 wait [pid]可以等待子进程结束,如果不带pid参数则等待所有子进程结束。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 bash-5.1$ sleep 100 ^Z [1]+ 已停止 sleep 100 bash-5.1$ jobs [1]+ 已停止 sleep 100 bash-5.1$ bg %1 [1]+ sleep 100 & bash-5.1$ sleep 200 & [2] 63603 bash-5.1$ jobs [1]- 运行中 sleep 100 & [2]+ 运行中 sleep 200 & bash-5.1$ wait %1 [1]- 已完成 sleep 100 后台进程并发控制 可以利用jobs对后台进程并发数目进行控制 1 2 3 4 5 6 7 8 for i in {1..30}; do sleep $((30+i)) & if [[ $(jobs | wc -l ) -gt 10 ]]; then jobs wait fi done wait 参考 https://wangdoc.com/bash/ https://tldp.org/LDP/abs/html/fto.html https://zh.wikipedia.org/wiki/Shebang https://en.wikipedia.org/wiki/File_descriptor

2021/11/28
articleCard.readMore

MySQL 自定义数据库路径

最近的一些文章是整理以前的笔记 MySQL 是最常用的数据,有时希望将数据库文件存放在自定义路径,或者在系统中启动多个 MySQL服务。 当然,如果条件允许,建议直接使用 docker 创建 my.cnf 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [mysqld] datadir=/home/ruan/data/mysql_data socket=/home/ruan/data/mysql.sock user=ruan # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 bind-address=127.0.0.1 port = 12345 character-set-server=utf8 collation-server=utf8_general_ci [mysqld_safe] log-error=/home/ruan/data/mysqld.log pid-file=/home/ruan/data/mysqld.pid 启动和初始化 1 2 3 4 # 启动MySQL mysqld_safe --defaults-file=my.cnf --user=ruan # 初始化数据库 mysql_install_db --defaults-file=my.cnf --user=ruan 目录结构 1 2 3 4 5 6 7 8 9 10 $ tree data data ├── my.cnf ├── mysql_data │   ├── ibdata1 │   ├── ib_logfile0 │   ├── ib_logfile1 ├── mysqld.log ├── mysqld.pid └── mysql.sock 设置密码 1 mysql> use mysql; update user set password=password('m654321') where user='root'; flush privileges;

2021/10/14
articleCard.readMore

hexo 站内搜索内容不完全问题修复

在使用 Hexo 的站内搜索时,发现搜索的内容不全。单步调试发现xml解析不完整,有部分内容被截断了。 在浏览器中打开/search.xml发现以下错误。显然xml中有非法字符,xml解析产生了错误。 将search.xml文件保存,并用python打开,找到具体出错的位置。 utf8解码之后可以发现\x10非法字符,将其删除,重新生成文章问题解决。 1 2 3 4 5 6 7 >>> xxx = open('./tmp.xml', 'rb').read() >>> xxx.index(b'\x10\xE7\x84\xB6') 923278 >>> xxx[923278:923278+31] b'\x10\xe7\x84\xb6\xe5\x88\x99\xef\xbc\x8c\xe4\xbb\x8a\xe4\xb9\x8b\xe4\xb8\x96\xe4\xba\xba\xef\xbc\x8c\xe7\xb1\xbb\xe6\xad\xa4' >>> xxx[923278:923278+31].decode() '\x10然则,今之世人,类此'

2021/10/11
articleCard.readMore

macOS 使用技巧

对程序员来说,macOS 就是一个桌面支持比较好的 Linux/Unix,给日常开发带来了许多便利 本文记录一些日常使用中的小技巧。 macOS mojave 密码位数限制 苹果在10.14的系统上增加了密码最小长度的限制 可以通过这个命令去除 1 pwpolicy -clearaccountpolicies 注意:去除限制之后,通过time machine备份的系统,还原到有限制的机器上,可能会导致一些bug。如卡死在登陆页面,部分应用的资源库损坏等等。 重置账户、配置 如果出现上面的问题,可以尝试重置下系统设置(选择任意一种,建议选1或2) 新建管理员账号,在新机器上去除密码限制。 进入恢复模式(开机按command+r),删除对应用户账户配置文件(恢复模式下文件挂载路径可能不同), 重新开机 1 2 mv ~/Library/Preferences ~/Library/Preferences.bak mv ~/Library/PreferencePanes ~/Library/PreferencePanes.bak 清除机器配置状态,重新设置一个新账户(开机按住command+s;) 1 2 3 /sbin/mount -uaw rm var/db/.applesetupdone reboot Mac 开启任何来源选项 1 sudo spctl --master-disable 让系统更流畅 macOS 10.14 以后系统动画越来越复杂,对显卡要求比较高,导致配置比较老的设备运行起来很卡顿。 特别是big sur系统,使用intel核显的机器都不建议安装。 通过关闭一些动画和特效可以让系统更流畅些 键位修改 使用 karabiner 可以对系统全局快捷键和应用快捷键进行修改。 通过配置 karabiner 将 command,control,option 按键进行重映射,可以让 macOS 的键位 和 Windows/Linux 接近。 使 macOS 的命令风格与Linux相同 macOS 的命令应该是 Unix 风格的,和 Linux 有些不同,可选参数必须放在前面,会有些不方便。 比如 1 2 3 # mac ➜ ~ /bin/ls /tmp -al ls: -al: No such file or directory 可以安装 gnu 工具,覆盖这些命令 1 2 3 brew install coreutils findutils gnu-tar htop export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH" export MANPATH="/usr/local/opt/coreutils/libexec/gnuman:$MANPATH" 清除一些 macOS 系统默认快捷键 创建文件 ~/Library/KeyBindings/DefaultKeyBinding.dict 这里是清除了默认的 Control + Command + 方向键的行为 修改完成后必须重新打开现有app才能生效 1 2 3 4 5 { "^@\UF701" = ("noop:"); "^@\UF702" = ("noop:"); "^@\UF703" = ("noop:"); } 字符含义 PrefixMeaning ~⌥ Option key $⇧ Shift key ^^ Control key @⌘ Command key #keys on number pad 参考: http://xahlee.info/kbd/osx_keybinding.html https://blog.victormendonca.com/2020/04/27/how-to-change-macos-key-bindings/ 关闭启动声音 1 2 3 4 5 # 关闭提示音 sudo nvram StartupMute=%01 # 打开duang sudo nvram StartupMute=%00 crontab 文件路径 用户执行crontab -e之后定时任务存储位置/private/var/at/tabs/<username> hostname 更改 macos 默认没有设置hostname,导致终端PS1显示时会用网卡的mac地址作为主机名,看起来比较别扭。 可以通过sudo scutil --set HostName <name>设置想要的主机名 1 2 3 4 5 6 7 8 ➜ ~ hostname a8a159010000 ➜ ~ scutil --get HostName HostName: not set ➜ ~ sudo scutil --set HostName Mac-mini Password: ➜ ~ hostname ruandeMac-mini catalina 禁用系统更新提示 在终端中输入 1 defaults write com.apple.systempreferences AttentionPrefBundleIDs 0 ; killall Dock 在 /etc/hosts 中加入 1 2 3 # disable mac update 0.0.0.0 swdist.apple.com.edgekey.net 0.0.0.0 swdist.apple.com.akadns.net 固定dock位置,防止在不同屏幕间移动 不是100%有效 1 defaults write com.apple.Dock position-immutable -bool yes; killall Dock 不生成 ._ 开头的隐藏文件 1 defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true

2021/10/10
articleCard.readMore

MySQL 表分区使用

使用MySQL数据库时,当表的数据条数比较大时(1000w以上),数据查询会很慢,索引的效果也不好。 这时我们可以把表的数据分区存储,安装数据值的前缀或者时间字段来分区。 建表 1 2 3 4 5 6 7 8 9 CREATE TABLE test_part ( appid int(11), val int(11), username VARCHAR(25) NOT NULL, start_time DATETIME ) PARTITION BY RANGE (TO_DAYS(start_time) )( PARTITION p20190305 VALUES LESS THAN (TO_DAYS('2019-03-06 00:00:00') ) ) 删除分区 alter table test_part drop partition p1; 不可以删除hash或者key分区。 一次性删除多个分区,alter table test_part drop partition p1,p2; 增加分区 ALTER TABLE test_part ADD partition (partition p20190306 VALUES LESS THAN (TO_DAYS(‘2019-03-07 00:00:00’))); 生成测试数据 创建储存过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 DROP PROCEDURE IF EXISTS proc1; DELIMITER $$ SET AUTOCOMMIT = 0$$ CREATE PROCEDURE proc1() BEGIN DECLARE v_cnt DECIMAL (10) DEFAULT 0 ; dd:LOOP INSERT INTO test_part VALUES ( FLOOR(RAND()*100), FLOOR(RAND()*1000), UUID(), DATE_ADD('2019-03-04 00:00:00', INTERVAL FLOOR(v_cnt / 5000) MINUTE) ); COMMIT; SET v_cnt = v_cnt+1 ; IF v_cnt = 10000000 THEN LEAVE dd; END IF; END LOOP dd ; END;$$ DELIMITER ; 调用储存过程 1 call proc1;

2021/10/10
articleCard.readMore