作为一个开发或运维人员,登录到服务器之后,最常用的编辑器之一就是vim。使用vim的过程中经常遇到一个尴尬的情况, 拷贝了一段内容后,切换到其他窗口想要粘贴,却意外的发现并不是自己想要的内容。 重复两次后,才意识到刚才是在vim中拷贝的,拷贝的这些内容是无法进入你本地电脑的粘贴板的。 网上有很多相关的解决方案,但或多或少都有配置麻烦或根本没有效果。我经过一番摸索,也形成了自己的解决方法, 不过这个方法暂时只能在Mac系统上使用。我自己也使用了一段时间,觉得还不错,配置也很简单,不需要安装任何软件,期望对你所有帮助。

方案

总体方案如下图所示:

原理图

方案分成三个部分:

  1. 在vim中执行拷贝的时候,我们要做些额外的事情,就是将其发送回本地;这个通过autocmd+TextYankPost实现;
  2. 将拷贝的内容发送回本机需要一个数据传输通道,登录服务器用的最多的就是ssh,ssh提供的RemoteForward就可以满足这个需求。
  3. 将传送回本机的内容复制到系统的粘贴板中,这个通过mac系统上的pbcopy可以实现。

方案说完了,下面开始配置吧。

配置

配置ssh

修改你本地的ssh配置文件~/.ssh/config,在这个文件中添加如下的配置:

1
2
Host *
    RemoteForward 22222 127.0.0.1:22222

添加上面的配置后,在终端中通过ssh登录服务器时,就会建立一个额外的数据通道。 ssh的服务器端会启动一个进程并在22222端口监听,并将收到的数据发送回你本机的22222端口。

有的服务器的ssh配置不允许端口转发,所以,配置好之后,你最好登录服务器确认一下。

1
$ netstat -ntlp | grep -w 22222

如果输出不为空,表示可以建立连接。如果为空,需要修改ssh的配置。

如果你使用的是有图形界面的ssh客户端,则需要在相应的地方配置端口转发。

配置服务器上的vim

方案一:使用vim channel

在vim的配置文件中添加如下的内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function! s:send2net()
    let ch = ch_open('127.0.0.1:22222')
    if ch_status(ch) != "open"
        echomsg "Failed to open channel, status = " .. ch_status(sh)
    endif
    call ch_sendraw(ch, getreg('"'))
endfunction

augroup send2net
    au!
    au TextYankPost * if v:event.operator ==# 'y' | call s:send2net() | endif
augroup END
1
# 测试看看吧 我也不确定是否好用

方案二:使用自己编写的脚本

如果你的vim不支持channel,还一个可选的方法,自己编写脚本替代,同时改下vimrc的配置。 具体如下,首先执行下面的命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ cat > /usr/local/bin/pbcopy << END
#!/bin/bash

if [ $# -eq 1 ]; then
    cat $1 | nc localhost 22222
else
    [[ -p /dev/stdin ]] && { cat | nc localhost 22222; }
fi
END

$ chmod +x /usr/local/bin/pbcopy

上面的命令会在/usr/local/bin目录下,创建一个名为pbcopy的脚本,并赋予可执行权限。

接着将如下的内容添加到vim的配置文件中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
let s:selfpid = getpid()

function! s:pbcopy()
    let l:path = printf("/tmp/%d.pbcopy", s:selfpid)
    let l:command = printf("/usr/local/bin/pbcopy %s", l:path)
    call writefile(split(getreg('"'), "\n", 1), l:path)
    call system(l:command)
endfunction

function! s:cleanup()
    let l:path = printf("/tmp/%d.pbcopy", s:selfpid)
    let l:command = printf("/bin/rm -f %s", l:path)
    call system(l:command)
endfunction

augroup pbcopy
    au!
    au TextYankPost * if v:event.operator ==# 'y' | call s:pbcopy() | endif
    au VimLeave * call s:cleanup()
augroup END

上面这段配置在vim执行拷贝后,会先将拷贝的内容写入一个临时文件,然后调用pbcopy这个脚本,将拷贝的内容发送回本机。

配置Mac系统

通过步骤1、2,可以实现将拷贝的内容传递回本机,这之后如何将这些内容拷贝到系统粘贴板呢?这就需要配置你的Mac了。具体如下:

 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

# 创建一个plist文件
$ cat > $HOME/Library/LaunchAgents/pbcopy.plist << END
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
            <string>localhost.pbcopy</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/bin/pbcopy</string>
        </array>
        <key>inetdCompatibility</key>
        <dict>
            <key>Wait</key>
            <false/>
        </dict>
        <key>Sockets</key>
        <dict>
            <key>Listeners</key>
            <dict>
                <key>SockServiceName</key>
                <string>22222</string>
                <key>SockNodeName</key>
                <string>127.0.0.1</string>
            </dict>
        </dict>
    </dict>
</plist>
END

# 加载配置
$ launchctl load -w $HOME/Library/LaunchAgents/pbcopy.plist

# 检查配置
$ launchctl list | grep localhost.pbcopy
-     0      localhost.pbcopy

通过上面的配置,launchd启动一个进程监听127.0.0.1:22222,并将其收到的内容传递给系统粘贴板。 我们通过ssh传送回来的内容正好是发送到本机的127.0.0.1:22222这个地址,这样整个拼图的最后一块就补齐了。

测试

通过ssh登录服务器,然后打开vim,在vim中通过V选择一段文本,然后按下y,或者直接用yy复制一行的内容, 然后切换到本机的其他应用程序,试试cmd+v,应该可以看到刚才拷贝的内容了。 从此,不管是本地,还是远程,都可以愉快的复制、粘贴了。 有点遗憾的是,这个方法暂时只能在Mac系统上使用。

参考资料

  1. https://gist.github.com/andyshinn/dd6cabc38194e57d4014d291a03a1995
  2. https://gist.github.com/ljjjustin/f99171b337213d5e00de6ac0c87b631b