如何从外部关闭Vim?


23

假设我有一个挂起的X11服务器,这使我无法保存X11服务器控制的XTerm Vim会话中的工作。(不是GVim,只是常规的Vim-in-XTerm。)

有没有办法(从另一个终端)告诉正在运行的Vim进程从命令行“全部保存并退出”?通过发送信号还是通过其他方式?

我知道Vim交换文件,并且可以杀死Vim并从交换中恢复。我问是否有一种“清洁”的方式。


6
如果这些Vim会话是在启用服务器的情况下启动的(例如gvim会默认启用),则可以使用Vim的客户端-服务器功能来实现。另一个选择是使用reptyr来将Vim程序强制到一个新的终端(如TTY),然后关闭它们。
muru 2015年

Answers:


25

最近遇到了这个问题(通过另一种方式:Vim在远程服务器上运行,而我忘记了屏幕),我决定寻找一种方法。

第一个想法是查找Vim使用的文件描述符并尝试对其进行写入。Vim的fds指向由终端仿真器打开的psedoterminal,这很自然:

$ ls -l /proc/$(pgrep -n vim)/fd/
total 0
lrwx------ 1 muru muru 64 Nov 17 01:25 0 -> /dev/pts/14
lrwx------ 1 muru muru 64 Nov 17 01:25 1 -> /dev/pts/14
lrwx------ 1 muru muru 64 Nov 17 01:25 2 -> /dev/pts/14
lrwx------ 1 muru muru 64 Nov 17 01:25 3 -> socket:[99564312]

但是,最初的几次尝试都失败了:

echo '^[:wq^M' > /proc/$(pgrep -n vim)/fd/0
echo ':wq^M' > /proc/$(pgrep -n vim)/fd/0
echo ':wq^M' > /proc/$(pgrep -n vim)/fd/0
echo '^C' > /proc/$(pgrep -n vim)/fd/0
printf "%s" '^[:wqa!^M' > /proc/$(pgrep -n vim)/fd/0

^[^M通过获得CtrlVEscCtrlVEnter分别。

它们都会导致字符显示在终端上(在将其应用于远程会话之前,我正在本地进行测试)。谷歌搜索,我发现了这篇SO帖子,使用Python编写了伪终端设备:

#!/usr/bin/python

import sys,os,fcntl,termios
if len(sys.argv) != 3:
   sys.stderr.write("usage: ttyexec.py tty command\n")
   sys.exit(1)
fd = os.open("/dev/" + sys.argv[1], os.O_RDWR)
cmd=sys.argv[2]
for i in range(len(cmd)):
   fcntl.ioctl(fd, termios.TIOCSTI, cmd[i])
fcntl.ioctl(fd, termios.TIOCSTI, '\n')
os.close(fd)

并在交互式python shell上进行尝试:

$ sudo python3
Python 3.5.0 (default, Sep 20 2015, 11:28:25) 
[GCC 5.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os, fcntl, termios
>>> fd = os.open('/dev/pts/14', os.O_RDWR)
>>> a = '\033:wqa!\n'
>>> for i in a: fcntl.ioctl(fd, termios.TIOCSTI, i);
... 
b'\x1b'
b':'
b'w'
b'q'
b'a'
b'!'
b'\n'
>>> 

做完了!


1
请注意,python脚本需要root权限才能访问终端。
martinkunev

6

您可以在运行时将命令发送到外部vim ...

Vim服务器

例如,执行以下操作:

vim --servername vim

将导致vim启动名称为“ vim”的服务器。调用两次,新服务器将被命名为“ vim1”,三次将被命名为“ vim2”,依此类推。您可能想对该命令做一个别名。

通过查看窗口标题,您可以知道特定实例命名的服务器。当你看到:

[无名称] +-VIM3

服务器名称不区分大小写“ VIM3”(“ vim3”将指向同一实例。)。请注意,如果您看到:

[无名称] +-VIM

这并不一定意味着它有一个名为“ VIM”的服务器。您可以通过列出服务器名称来确保服务器存在:

vim --serverlist

但是,仅对于“ VIM”才出现问题。如果您看到“ GVIM”或其他名称后加数字,则表示它是服务器。

如何使用客户端

现在,对您的问题,您可以保存所有内容并通过执行以下操作退出给定的vim实例:

vim --servername vim2 --remote-send $'\e:wqa\n'

如果您处于插入或命令模式,我们使用转义键返回正常模式。您可以执行除之外的其他操作:wqa,但这对我来说似乎是最合适的,因为它将留下无法保存的缓冲区交换文件(因为它们是新文件并且没有文件名等)。

如果要对所有实例(例如此处的情况)都这样做,则可以像这样遍历服务器列表:

for instance in $(vim --serverlist); do
  vim --servername $instance --remote-send $'\e:wqa\n'
done

如果由于某种原因您不喜欢--remote-send,则可以改用--remote-exprwhich,它的优点是它将导致客户端输出结果或可能导致的错误,例如:

$ vim --servername vim2 --remote-expr 'execute("wqa")'

E141: No file name for buffer 1

注意,使用Vim的服务器功能要求Vim是使用该+clientserver选项构建的。


5

reptyr使用系统的程序包管理器安装命令,例如:

sudo apt install reptyr
pacman -Sy reptyr

然后使用reptyr命令将远程tty切换到本地(新)tty,如下所示:

ssh user@remote-hostname
ps auxw | grep -i vim
reptyr PID

命令输出中PID的进程ID 在哪里ps

在错误:

无法附加到pid 12345:权限被拒绝

将“ ptrace范围”更改为0:

sudo su -
echo 0 > /proc/sys/kernel/yama/ptrace_scope

一旦vim会话从旧会话切换到新会话,保存并照常退出。请注意,您可能必须按Enter刷新控制台。


0

如果您优雅地杀死Vim怎么办?

kill -s 15 -p [PID for Vim]

kill -s(信号)15称为SIGTERM,它告诉该进程正常关闭自身。

要获取Vim的PID(进程ID),请使用:
ps ax | grep vim


1
无论发送哪个信号,Vim都不会自动写入未保存的更改。
大师
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.