作者归档:songtianlun

helm operation 中断锁死问题解决

两种方案:

解决办法

方法一,卸载重装

helm uninstall <release name> -n <namespace>

方法二,回滚

This error can happen for few reasons, but it most commonly occurs when there is an interruption during the upgrade/install process as you already mentioned.
发生此错误的原因有很多,但最常见的原因是升级/安装过程中出现中断,正如您之前提到的。

To fix this one may need to, first rollback to another version, then reinstall or helm upgrade again.
要修复这个问题,可能需要先回滚到另一个版本,然后重新安装或再次进行 helm upgrade。

Try below command to list
尝试以下命令列出

helm ls --namespace 
<namespace>

but you may note that when running that command ,it may not show any columns with information
但你可能会注意到,运行该命令时,它可能不会显示任何包含信息的列

Try to check the history of the previous deployment
尝试检查上次部署的历史记录

helm history 
<release> --namespace <namespace>

This provides with information mostly like the original installation was never completed successfully and is pending state something like STATUS: pending-upgrade state.
这提供的信息大多类似于原始安装从未成功完成并且处于待处理状态,例如状态:待处理升级状态。

To escape from this state, use the rollback command:
要退出此状态,请使用 rollback 命令:

helm rollback 
<release> <revision> --namespace <namespace>

revision is optional, but you should try to provide it.
修订是可选的,但您应该尝试提供它。

You may then try to issue your original command again to upgrade or reinstall.
然后您可以尝试再次发出原始命令来升级或重新安装。

References

将当前系统做成 docker 镜像

TL; DR

  • Step1: 将当前系统打包

系统进行格式转化并压缩,执行如下命令,生成一个镜像的centos7-base.tar的文件

# 示例打包命令
$ tar --numeric-owner --exclude=/proc --exclude=/sys -zcvpf /data/centos7-svr.tar.gz /

$ tar --numeric-owner \
--exclude=/proc --exclude=/sys \
--exclude=/var/lib/vz \
--exclude=/root \
--exclude=/media \
--exclude=~ \
-zcvpf ~/hci6-ci-base.tar.gz /

$ tar -cvpf /home/buildrpm.tar --directory=/ --exclude=proc --exclude=sys --exclude=dev --exclude=run /
# --numeric-owner:执行所属
# --exclude:排除那些文件或者目录
# -zcvf :打包压缩 p保持文件的绝对路径
  • Step2: 导入镜像并运行

把镜像文件centos7-svr.tar.gz放到装有docker的系统上

docker import centos7-base.tar <docekr-image>
docker run -it --name <contaner-name> <docker-images> /bin/bash

参考文献

cert-manager CNAME 问题记录

在研究 cert-manager 使用 webhook 方式调用 dnspod 使用 DNS-01 方式签发 SSL 证书遇到问题,一直得到错误:

I0306 03:48:38.870605       1 controller.go:144] "syncing item" logger="cert-manager.controller"
I0306 03:48:38.870714       1 dns.go:118] "checking DNS propagation" logger="cert-manager.controller.Check" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_namespace="default" resource_kind="Challenge" resource_version="
v1" dnsName="test1.tsh1.frytea.com" type="DNS-01" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_namespace="default" resource_kind="Challenge" resource_version="v1" domain="test1.tsh1.frytea.com" nameservers=["223.5.5.5:53","8.8.8.8:53"]
I0306 03:48:38.879628       1 wait.go:94] "Updating FQDN" logger="cert-manager.controller" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_namespace="default" resource_kind="Challenge" resource_version="v1" dnsName="test
1.tsh1.frytea.com" type="DNS-01" fqdn="_acme-challenge.test1.tsh1.frytea.com." cname="tsh1.frytea.com."
I0306 03:48:38.897174       1 wait.go:145] "Looking up TXT records" logger="cert-manager.controller" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_namespace="default" resource_kind="Challenge" resource_version="v1" dns
Name="test1.tsh1.frytea.com" type="DNS-01" fqdn="tsh1.frytea.com."
E0306 03:48:38.897227       1 sync.go:208] "propagation check failed" err="DNS record for \"test1.tsh1.frytea.com\" not yet propagated" logger="cert-manager.controller" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_nam
espace="default" resource_kind="Challenge" resource_version="v1" dnsName="test1.tsh1.frytea.com" type="DNS-01"I0306 03:48:38.897688       1 controller.go:164] "finished processing work item" logger="cert-manager.controller"

我使用了以下资源:

在相关仓库找到这些 issue

发现,只要申请证书的域名能够匹配到 CNAME 记录,就会默认跟随,找不到正确的 TXT 记录,导致认证失败。 虽然 cert-manager 提供了这个参数 cnameStrategy: None ,能够在声明 ISSUE 时使用,但是似乎大部分实现的 webhook 都没有实现这个特性:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: dnspod
spec:
  acme:
    email: xxxx # 在证书过期的时候,会发邮件通知
    preferredChain: ""
    privateKeySecretRef:
      name: example-com-letsencrypt-dev-key # 用于存储ACME帐户私钥的密钥名称
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    #server: https://acme-v02.api.letsencrypt.org/directory # 生产
    solvers:
      - dns01:
          cnameStrategy: None
          webhook:
            config:
              secretId: xxxxxx
              secretKeyRef:
                key: secret-key
                name: dnspod-secret
              ttl: 600
            groupName: acme.imroc.cc
            solverName: dnspod

目前临时的解决办法,只能是 避免 cert-manager 托管域名能够解析到 CNAME 记录,等有空了研究一下 cert-managerwebhook 的实现方法,看能否解决这个问题。

References

ProxmoxVE (PVE) NAT 网络配置方法

伪装允许只有私有 IP 地址的访客使用主机 IP 地址来访问网络,以处理传出流量。每个传出数据包都会被重写 iptables ,使其看起来来自主机,响应也会相应地被重写以路由到原始发件人。

auto lo
iface lo inet loopback

auto eno1
#real IP address
iface eno1 inet static
        address  198.51.100.5/24
        gateway  198.51.100.1

auto vmbr1
#private sub network
iface vmbr0 inet static
        address  10.10.10.1/24
        bridge-ports none
        bridge-stp off
        bridge-fd 0

        post-up   echo 1 > /proc/sys/net/ipv4/ip_forward
        post-up   iptables -t nat -A POSTROUTING -s '10.10.10.0/24' -o eno1 -j MASQUERADE
        post-down iptables -t nat -D POSTROUTING -s '10.10.10.0/24' -o eno1 -j MASQUERADE
        post-up   iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
        post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1

其中 vmbr1 为 NAT 网桥,网桥 IP 为 10.10.10.0/24 ,该网段流量会被转换为 eno1 网卡的 IP 发出,并在收到回复保温时转换为原始 IP,实现共享外部 IP 的目标。

重载网络配置使其生效:

ifreload -a

实测立刻可以生效,VM 中配置该段 IP 并将 10.10.10.1 作为网关即可,也可根据需要配置 dnsmasq 之类的实现 DHCP 自动分配 IP。

References

Linux 备份和恢复 docker volume 脚本分享

脚本

Dump

docker-volume-dump.sh

#!/usr/bin/env bash

dump_dir=~/docker-volume-dump

if [ ! -d $dump_dir ]; then
  mkdir -p $dump_dir
fi

for volume in $(docker volume ls -q); do
  dump_file=$dump_dir/$volume.tar.gz
  if [ -f $dump_file ]; then
    ( set -x; echo rm $dump_file; )
  fi
  echo "Dump docker volume \"$volume\" to \"$dump_file"\"
  docker run --rm -v $volume:/from alpine sh -c "cd /from; tar -cf - ." | gzip > $dump_dir/$volume.tar.gz
done

Restore

docker-volume-restore.sh

#!/usr/bin/env bash

dump_dir=~/docker-volume-dump

for file in ~/docker-volume-dump/* ; do
  volume=$(basename $file)
  volume=${volume%%.*}
  echo "$volume"
  docker volume inspect $volume &>/dev/null
  if [ $? -eq 0 ]; then
    ( set -x; docker volume rm $volume 1>/dev/null )
  fi
  ( set -x; docker volume create $volume 1>/dev/null )
  cat $file | docker run --rm -i -v $volume:/to alpine sh -c 'tar zxvf - -C /to'
done

References

ArchLinux 休眠到交换文件

来源: ArchLinux 休眠到交换文件

Linux 使用交换分区来休眠,首先冻结所有进程并申请足够的交换内存(位于磁盘),把当前内存都存进去。 然后下次启动时,initramfs 会直接加载上次休眠时的内存状态,跳过内核的 init 过程。 因此首先需要有足够大的交换分区或交换文件;再把内核指向到休眠的分区上;最后再配置 initramfs 让它加载休眠的内存文件。 官方文档请参考 Power_management/Suspend_and_hibernate#Hibernation, 本文细述如何休眠到交换文件,并对其中一些概念和细节进行了解释。

在本文讨论的范围内, 挂起(suspend)是指冻结当前的进程,保留它们的内存,并把几乎除了内存之外的设备都断电。 休眠(hibernate)是指把挂起后的内存写入磁盘并完全关机。 锁定(lock)则只是显示一个模态的全屏软件输入正确的密码才能退出。

交换文件

安装系统 前需要创建交换分区,现在的机器普遍内存较大不太需要交换分区来扩展内存空间, 而且磁盘一般使用读写快速但读写次数有限 SSD,因此我的交换分区也很小根本不够用来休眠。

你可以通过 swapiniss 来让你的交换分区只用于休眠。

所以我们用交换文件来替代交换分区,在创建交换文件之前首先需要知道系统休眠需要多大空间。 可以从 sysfs 来看查看:

cat /sys/power/image_size

可以参考 [官方教程][https://wiki.archlinux.org/index.php/Swap#Swap_file] 来创建:

按照你需要的大小创建,bs * count 是最终文件大小
dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress
chmod 600 /swapfile
# 检查大小和权限
ls -l /swapfile

# 初始化交换文件并立即应用到系统
mkswap /swapfile
swapon /swapfile

# 在 /etc/fstab 中写入以下内容,交换文件会在重启后生效
/swapfile none swap defaults 0 0

重启后通过 swapon -s 来检查是否生效:

> sudo swapon -s
Filename    Type    Size     Used    Priority
/swapfile   file    12582908    0    -2

让内核找到交换文件

我们需要设置 resume 和 resume_offset 两个内核参数,告诉内核在挂起时把内存写入到哪里。

  • 第一个参数是交换文件所在的磁盘分区,可以用任何 fstab 中接受的名字格式。比如 resume =/dev/sda1,或者 resume = UUID = xxx。如果你在上一步中创建的交换文件和 / 在同一分区下,可以复制已有的 root 内核参数的值。
  • 第二个参数是交换文件的偏移量,就是它在分区中的什么位置。因此这个参数给交换文件用的,如果是交换分区则不需要填写。可以通过 filefrag -v /swapfile | awk '{ if($1=="0:"){print $4} }' 命令得到。

可以通过 cat /proc/cmdline 来查看当前的内核参数,但是在哪里设置取决于你的 Boot Loader。 以 rEFInd 为例,打开 /boot/refind_linux.conf 写入 resume 和 resume_offset:

"Boot with standard options" "ro root=/dev/sda3 resume=/dev/sda3 resume_offset=3192832"

重启后用 journalctldmesg 来找到写入休眠镜像的日志:

Oct 19 13:57:56 harttle.arch.mac kernal: PM: Creating hibernation image:
Oct 19 13:57:56 harttle.arch.mac kernel: PM: Need to copy 596422 pages
Oct 19 13:57:56 harttle.arch.mac kernel: PM: Normal pages needed: 596422 + 1024, available pages: 1477067

如果看到这样的错误说明设置有误,请检查你交换文件所在分区和 filefrag 给出的偏移量:

Oct 19 13:57:56 harttle.arch.mac kernal: PM: Image not found (code -22)

让 initramfs 加载休眠的内存

initramfs 是由 Boot Loader 直接加载的一个早期的用户空间,其中已经加载了一些内核模块。 它会进行设备初始化、挂载文件系统、运行磁盘检查等工作,之后再交给内核的 init 过程。 加载休眠的内存也是它的工作,但 ArchLinux 默认并未开启,需要去 /etc/mkinitcpio.conf 中添加 resume 钩子:

HOOKS=(base udev resume autodetect modconf block filesystems keyboard fsck)

注意因为 resume 参数用到了磁盘设备名称,resume 需要写在 udev 之后。 然后重新编译 initramfs(就像更新内核时一样):

默认使用当前系统的内核,如果你现在位于启动盘的系统则需要指定宿主环境上的内核版本。
mkinitcpio -p linux

至此配置工作都完成了,通过 systemctl hibernate 来休眠,再按下电源键开机来检查休眠功能是否正常。

开着盖子无法挂起

MacBook Pro 的显示器盖子开着默认会阻止挂起,可能会出现挂起屏幕变黑后立即结束挂起。 这是因为显示器盖子默认可以唤醒休眠,在 /proc/acpi/wakeup 中可以查看哪些设备可以唤醒,其中 LID0 是显示器盖子:

> cat /proc/acpi/wakeup 
Device  S-state   Status   Sysfs node
P0P2      S3    *disabled
EC    S4    *disabled  platform:PNP0C09:00
HDEF      S3    *disabled  pci:0000:00:1b.0
RP01      S3    *enabled   pci:0000:00:1c.0
RP02      S3    *enabled   pci:0000:00:1c.1
RP03      S3    *enabled   pci:0000:00:1c.2
ARPT      S4    *disabled  pci:0000:03:00.0
RP05      S3    *enabled   pci:0000:00:1c.4
RP06      S3    *enabled   pci:0000:00:1c.5
XHC1      S3    *enabled   pci:0000:00:14.0
ADP1      S4    *disabled  platform:ACPI0003:00
LID0      S4    *enabled  platform:PNP0C0D:00

我们可以 echo LID0 > /proc/acpi/wakeup 来更改它的状态,然后再试休眠。 我们希望它的状态默认就是 disabled,需要一个这样的 systemd 服务:

[Unit]
Description=Disable LID0 wakeup triggers in /proc/acpi/wakeup

[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo LID0 > /proc/acpi/wakeup"
ExecStop=/bin/sh -c  "echo LID0 > /proc/acpi/wakeup"
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

把它添加到 /etc/systemd/system 并启用即可。

自动休眠

自动休眠和其他电源管理功能,由很多不同的软件和配置方式来实现。为避免混淆先介绍几个常见的软件:

systemd-logind:ArchLinux 的默认安装包含了 systemd,其中的 systemd-logind 是自动启用的。 它包含了一些非常简单的电源管理功能,比如按下电源按钮时关机、笔记本合上盖子时挂起。 因此 ArchLinux 装好之后就基本可以用了。

acpidacpid 是一个比较基础的电源管理工具,工作方式是响应 ACPI 事件,做相应的处理比如关机还是休眠。注意 acpid 只是电源管理工具,ACPI 是设备配置接口跟它没关系。

dpmsdpms 是 xorg 提供的显示器电源管理服务,用来控制显示器关闭等动作。有 standby, suspend, off 等阶段,跟 systemd 事件一样需要有人来订阅(比如 xss-lock)才能执行具体操作。可以通过 xorg.conf 的 StandbyTime, SuspendTime, OffTime 等来配置,也可以在运行时用 xset s 来配置。

tlptlp 是一个比较无脑的电源管理工具,提供类似电池模式、电源模式、性能优先这样级别的配置。

xss-lock, xidle, xautolock:这些是 X11 下的工具用来在用户无操作时执行挂起等操作,有些还会监听 ACPI 事件,这样在 suspend 时屏幕也能锁定。

无操作自动休眠

自动休眠和自动挂起需要桌面环境(DE)或者 X11 软件的支持。 如果你在用 Gnome 或 KDE 在控制面板中配置后,会把 idle 信息报告给 systemd-logind,后者接管具体操作。

如果你像 Harttle 一样没有桌面系统和登录管理器的话, 需要安装一个类似 xss-lock, xidle 这样的工具来靠 X11 事件计时, 然后调用 systemctl hibernatesystemctl syspend

先挂起后休眠

合上盖子后 systemd-logind 的默认行为是挂起,可以在 /etc/systemd/logind.conf 中把它重新设置为休眠,或先挂起再休眠:

HandleLidSwitch=suspend-then-hibernate

挂起后休眠前的时间可以在 /etc/systemd/sleep.conf 中设置:

HibernateDelaySec=15min

除了合上盖子之外,其他场景也可以直接调用 systemctl suspend-then-hibernate

电量低自动休眠

这件事情需要具体的软件来做,或者直接安装 tlp 并启动 tlp, tlp-sleep 两个 systemd 服务。 下面提供一个简单的 udev 规则,在电量小于等于 5% 时休眠:

SUBSYSTEM=="power_supply", ATTR{status}=="Discharging", ATTR{capacity}=="[0-5]", RUN+="/usr/bin/systemctl hibernate"

把它写入 /etc/udev/rules.d/99-lowbat.rules,重启即可生效。

References

Vim 下大小写敏感的搜索-替换

来源: Vim 下大小写敏感的搜索/替换

Vim 中的搜索默认是大小写敏感的,即搜索 vim 不会匹配到 Vim。 这一点跟多数编辑器/IDE 都不同,因此 Vim 的默认设置其实很不顺手。 本文来分享一些个性化的配置方法,让 Vim 下的大小写敏感/不敏感用起来更加顺手。 比如当搜索词包含大写时应用大小写敏感搜索;其他情况应用大小写不敏感搜索。

TL;DR

以搜索词为 harttle 为例(省略了最后的回车):

  • 强制大小写不敏感搜索:/harttle\c
  • 强制大小写敏感搜索:/harttle\C
  • 强制大小写不敏感替换:s/harttle\c/Harttle
  • 强制大小写敏感替换:s/harttle\C/Harttle
  • 设置为大小写敏感::set ignorecase
  • 设置为大小写不敏感::set noignorecase
  • 设置为智能模式(有大写时敏感否则不敏感)::set smartcase
  • 设置为非智能模式::set nosmartcase

大小写敏感控制字符

正如在正则表达式有类似 i 这样的开关,Vim 也有特殊字符来控制大小写敏感。 在模式末尾加 \c 表示大小写不敏感,加 \C 表示大小写敏感。 例如:

" 大小写不敏感搜索,可以匹配:vim, Vim, VIM
/vim\c
<CR>
" 大小写敏感搜索,只可以匹配:Vim
/Vim\C
<CR>
" 把出现的所有 vim, Vim, VIM 等都替换为 Vim,在写文章时会经常会用到
:%s/vim\c/Vim/g

这一语法的优先级高于下文的 ignorecase, smartcase 等选项, 所以比较万能,在远程机器上、别人的电脑上,一般用这个操作。

ignorecase/smartcase

Vim 中的 ignorecase 用于设置大小写敏感,它将在所有搜索、替换命令中生效。 在 normal 模式中 :set ignorecase 设置为不敏感;:set noignorecase 设置为敏感。 ignorecase 属于选项变量,因此也可以通过 & 来设置,例如::let &ignorecase=1。 把冒号去掉后可以直接放到 .vimrc 文件里持久生效。

更多 Vim 变量赋值和引用的细节,可参考这篇文章:Vim 中的变量赋值、引用与作用域

开启 ignorecase 之后还可以把 smartcase 也打开(后者要求前者出于开启状态), Vim 会启用智能模式:

  • 在你输入的模式中包含大写时,启用大小写敏感模式;
  • 在你输入的模式中只有小写时,启用大小写不敏感模式。

例如:

:set ignorecase
:set smartcase

" 大小写不敏感,可以匹配:vim, Vim, VIM
/vim
<CR>
" 大小写敏感,只可以匹配 Vim
/Vim
<CR>"

当前词搜索

smartcase 只对输入的模式(pattern)生效,其他不需要输入 pattern 的搜索命令不生效。 比如 在 Vim 中优雅地查找和替换 中介绍过可以用 *(向后),#(向前),g*(不切词)等命令来搜索光标所在的词搜索光标所在的词。 为了让它们好使,可以先按下 * 来搜索一次,然后按下 /(向后)再按上箭头找到上次历史(这是一个具体的 pattern)再按回车搜索。同样地,按 ?(向前)也可以。

“展开光标所在词”是存在 Vim 命令的,因此我们可以把 *, # 映射掉来自动化上面的过程:

" respect to smartcase, expand the pattern
:nnoremap * /\<<C-R>=expand('<cword>')<CR>\><CR>
:nnoremap # ?\<<C-R>=expand('<cword>')<CR>\><CR>

这样下次按下 *# 时,Vim 就会展开光标处的词,分别应用 /? 进行搜索。 这样当光标处的词有大写时就用大小写敏感搜索,全小写时就用大小写不敏感搜索。 <C-R>= 用来插入计算表达式并插入到命令里,类似我们在 使用 Vim 寄存器 中介绍的 <C-R>" 可以把匿名寄存器(上次拷贝、剪切、删除)的内容插入到命令里。

References

对 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 是运行在 Terminal 里的 Shell 程序,所以要把内容拷贝出来可以通过 Terminal,也可以通过 Vim 自己。不配置 Vim 剪切板时只能通过 Shell 来拷贝粘贴:

  • 通过 Shell 拷贝(比如在 iTerm 里按住 Alt 选取内容)会有问题:比如会包含 Vim 左侧行号、折行变成了换行。
  • 通过 Shell 粘贴有类似的问题:相比于 p 命令,在插入模式下 Ctrl+V 时 Vim 会把内容当作字符键入,触发 Vim 的所有处理键入的插件,比如自动补全、语法检查等。不仅会很慢,而且可能会破坏你的内容(比如粘贴一对括号,可能 Vim 会再帮你自动补全一个多余的右括号)。

所以完美的拷贝粘贴一定要通过 Vim 本身。Vim 中 p(paste)、d(delete)、y(yank)等拷贝粘贴操作使用的是 匿名寄存器 ""(unnamed register),本文就来解释怎么在 Mac/Windows/Linux 上把 Vim 的 匿名寄存器 映射到操作系统的剪切板。

TL; DR

  1. 确保你的 vim 支持剪切板,通过 vim --version | grep clipboard 检查。
  2. 确定你的剪切板寄存器是 "+(XA_SECONDARY)还是 "*(XA_PRIMARY)。
  3. 同步剪切板和匿名寄存器,在 ~/.vimrc 添加配置比如 set clipboard=unnamed

确保你的 Vim 支持剪切板

你的 Vim Build 没有支持 clipboard,那么无论怎样配置都不会生效。 可以用如下命令检查:

vim --version | grep clipboard

如果输出包含 +clipboard+xterm_clipboard 就支持,如果这两项都是 - 则不支持。例如我的 Vim 输出为(MacOS 上的 macvim):

+clipboard         +keymap            +printer           +vertsplit
+emacs_tags        -mouse_gpm         -sun_workshop      -xterm_clipboard

如果你的 Vim 不支持剪切板,则需要重新安装一个带 clipboard 的 Vim:

重新安装后再执行 vim --version 来查看 clipboard 是否支持。注意:如果安装到了其他路径你需要改 PATH 或重启 Terminal。

确定你的剪切板寄存器

Vim 有 48 个寄存器,y, d, p 等命令一般使用匿名寄存器 "", 支持剪切板的 Vim 会支持额外的选区寄存器 "*"+。 更多 Vim 寄存器的信息,可以参考这篇文章:Vim 寄存器完全手册

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

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

上述哪个寄存器对应于你的剪切板和 Linux 发行版有关,在配置 Vim 前可以测试一下。 比如用 Vim 打开一个文件,在 normal 模式下(进入 Vim 后默认的模式)键入 gg"*yG, 来把当前文件内容拷贝到 "* 寄存器。键入 gg"+yG 拷贝到 "+ 寄存器。

到目前为止,你已经可以通过命令来拷贝粘贴内容了。接下来我们希望通过 Vim 配置, 让匿名寄存器和系统剪切板同步。

同步剪切板和匿名寄存器

以下配置可以让主选区寄存器 "* 和匿名寄存器 "" 保持同步(即共享剪切板), 一般适用于 Windows 和 MacOS,Linux 下的表现是共享 X11 剪切板、PRIMARY 选区(鼠标中键粘贴)。

set clipboard=unnamed

Vim 7.3.74 及以上支持了 unnamedplus:

set clipboard=unnamedplus

即让剪切板寄存器 "+ 和匿名寄存器 "" 保持同步, Linux 下一般对应于桌面系统的剪切板,比如 GNOME 的系统剪切板、以及 SECONDARY 选区(Ctrl+V 粘贴)。

不支持 clipboard 的情况

如果你的 Vim 不支持 clipboard 且没法升级或其他 clipboard 选项不好使的情况, 可以调用外部命令来实现拷贝粘贴,在 Vim 里直接调用,或设置快捷键调用。 比如 让 Tmux 远程 Vim 使用本地系统的剪切板

如果在 MacOS 下,可以用 pbcopy/pbpaste 命令来实现。

  • 拷贝一段文本:先按 v 进入 visual 模式选中后执行 :w !pbcopy。拷贝整个文件可以 :%w !pbcopy
  • 粘贴一段文本:把光标移动到要插入的行,执行 :r !pbpaste

如果在 Linux 下,可以借由 xclip 来实现。用 xclip -i -sel c 代替上面的 pbcopy,用 xclip -o -sel -c 代替上面的 pbpaste

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)