Tramp 使用指南
文章目录
Tramp 是 Emacs 中用来编辑远端文件的模块,全称为『Transparent Remote (file) Access, Multiple Protocol』,类似于 VSCode 的 Remote Development,只不过比它年长 20 岁而已😄。 这篇文章就来介绍下 tramp 的使用方式与注意事项。
使用方式
在使用 find-file
打开文件时,使用下面的语法,即可打开远端的文件:
/method:user@host#port:path/to/file
比如, /ssh:[email protected]:/etc/hosts
即可通过 SSH 协议以 vagrant 用户登录 192.168.31.92 机器,并且打开 /etc/hosts
文件。
/ssh::
会连接到 localhost,一般用来测试 tramp 的功能。在 Windows 系统中,PuTTY 是一个常用的 SSH 客户端,需要用 plink 协议: /plink:user@host:/path/to/file
。
借助于 SSH 的功能,可以通过下面的配置来简化(免密码)tramp 的使用:
# ~/.ssh/config
Host devhost
HostName 192.168.31.92
User vagrant
IdentityFile ~/.ssh/vagrant-pk
这样只需输入 /ssh:devhost:/etc/hosts
即可。通过配置 tramp 默认协议为 SSH,可以进一步简化为: /-:devhost:/etc/hosts
(setq tramp-default-method "ssh")
另外,也可以通过配置 directory-abbrev-alist,达到简化目的:
(setq directory-abbrev-alist '(("^/dev" . "/-:dev:/etc")))
输入 /dev
后,按 TAB,即会自动打开 /-:dev:/etc
。
SSH ControlMaster
ControlMaster 是 SSH 进行多路复用的机制,这样用户只需要在第一次登录时需要输入密码信息,后续 SSH 登录同一主机时,会复用之前的 TCP 连接。
ControlMaster 的主要缺点是第一次建立连接的 SSH 会话必须一直保留着,如果 logout 这个主会话,其他的 SSH 会话则会“卡住”。也因为这个原因,tramp 使用 SSH 时,默认用 tramp-ssh-controlmaster-options
覆盖掉 SSH config 中 ControlMaster 的行为,默认值为:
"-o ControlMaster=auto -o ControlPath='tramp.%%C' -o ControlPersist=no"
没有进行持久化。如果想要使用 SSH config 中的配置,则需配置:
(setq tramp-use-ssh-controlmaster-options nil)
与其他模块结合
在 Emacs 中,shell.el、eshell.el、compile.el、gud.el(gdb)这几个内置模块都与 tramp 做了完美整合,执行相应命令时会通过相应协议在远端执行。
多级跳跃 multiple hops
出于安全考虑,公司会禁止开发同学直接登录生产机器,需要通过一跳板机来登录生产机器 ,这时就需要多次跳跃。tramp 支持通过下面的语法,级联登录多个机器
C-x C-f /ssh:bird@bastion|ssh:admin@production:/path RET
上面命令会先用 bird 用户登录堡垒机 bastion,之后再在堡垒机上以 admin 用户登录 production 打开 /path
。
如果要在多级跳跃时使用 ControlMaster,中转的机器需要配置如下:
# ~/.ssh/config
Host *
ControlMaster auto
ControlPath tramp.%C
ControlPersist no
以 sudo 方式打开文件
一般来说,登录远端机器时都是非 root 用户,有时会需要用 sudo 来打开某些文件,tramp 通过下面的语法支持这类操作:
C-x C-f /ssh:you@remotehost|sudo::/path RET
sudo::
的方式在 Emacs 27 上运行没有问题,其他低版本可能需要输入完整的命令:
C-x C-f /ssh:you@remotehost|sudo:remotehost:/path RET
注意事项
Tramp 打开的远端文件和本地的文件没什么区别,会被记录在 backup、autosave、recentf 等中。在今后重启 Emacs 时,如果这时无法连接远端机器,Emacs 可能会卡住,这是因为 tramp 会对之前打开的文件进行检查,可以通过下面的一些配置来绕过 tramp,让 backup 等机制不对 tramp 打开的文件起作用:
(setq recentf-exclude `(,tramp-file-name-regexp
"COMMIT_EDITMSG")
tramp-auto-save-directory temporary-file-directory
backup-directory-alist (list (cons tramp-file-name-regexp nil)))
如果用了 emacs-dashboard 来展示 project.el 中的项目,Emacs 启动时会检查这些项目,因此也需要跳过那些远端项目,不要持久化保存:
(defun my/project-remember-advice (fn pr &optional no-write)
(let* ((remote? (file-remote-p (project-root pr)))
(no-write (if remote? t no-write)))
(funcall fn pr no-write)))
(advice-add 'project-remember-project :around
'my/project-remember-advice)
添加上面的配置后,还需要检查下之前是否已经有 tramp 的文件被记录,如有手动删除即可。
如果打开 Emacs 还是有卡顿的问题,可以通过调整 tramp-verbose 来进行调试:
(setq tramp-verbose 10); 默认是 3
设置之后再重启时,会在 *debug-tramp*
内打印出详细日志。下图堆栈为笔者调试因 project.el 卡住时的截图:
下面的堆栈是在调试 docker-tramp
导致的卡住问题时,通过开启 (toggle-debug-on-error)
后得到的:
Debugger entered--Lisp error: (file-error "Couldn't find command to check if file exists")
signal(file-error ("Couldn't find command to check if file exists"))
tramp-error((tramp-file-name "docker" nil nil "helix" nil "/vagrant/" nil) file-error "Couldn't find command to check if file exists")
tramp-find-file-exists-command((tramp-file-name "docker" nil nil "helix" nil "/vagrant/" nil))
tramp-get-file-exists-command((tramp-file-name "docker" nil nil "helix" nil "/vagrant/" nil))
tramp-sh-handle-file-exists-p("/docker:helix:/vagrant/")
apply(tramp-sh-handle-file-exists-p "/docker:helix:/vagrant/")
tramp-sh-file-name-handler(file-exists-p "/docker:helix:/vagrant/")
apply(tramp-sh-file-name-handler file-exists-p "/docker:helix:/vagrant/")
tramp-file-name-handler(file-exists-p "/docker:helix:/vagrant/")
file-exists-p("/docker:helix:/vagrant/")
project-forget-zombie-projects()
dashboard-funcall-fboundp(project-forget-zombie-projects)
dashboard-projects-backend-load-projects()
dashboard-insert-projects(8)
#f(compiled-function (els) #<bytecode 0x1573fa35edad7240>)((projects . 8))
mapc(#f(compiled-function (els) #<bytecode 0x1573fa35edad7240>) ((agenda . 5) (recents . 10) (projects . 8) (bookmarks . 5)))
dashboard-insert-startupify-lists()
#f(compiled-function () #<bytecode 0x1f414d8ede95>)()
run-hooks(after-init-hook delayed-warnings-hook)
command-line()
normal-top-level()
file-truename
file-truename
作用在 tramp 打开的 buffer 上时,也会进行远程连接。笔者之前配置时,曾出现过下面这个错误信息:
Tramp: Opening connection nil for ${HOSTNAME} using ssh...failed
主要原因就是在配置中使用了 file-truename
。
Docker/Vagrant
SSH 是 tramp 中常用的协议,除此之外,tramp 还支持非常多的协议,比如:ftp、smb、adb(连接 Android 手机)等,具体可参考文档:TRAMP Inline methods。社区内也有一些插件支持 Docker,Vagrant。use-package 配置如下:
(use-package docker-tramp
:defer t
:custom ((docker-tramp-use-names t)))
(use-package vagrant-tramp
:ensure nil
:load-path "/path/to/vagrant-tramp"
:defer t)
vagrant-tramp 原作者貌似已经不维护了,有些小问题,笔者已经提交了 Pull Request,在作者合并前,读者可使用 fork 的版本。不仅单实例模式正常工作,在 Multi-Machine 模式下也没有问题。
Update:2022-06-27 上述 PR 已经合并
收听方式
反馈
- 对节目有想法或发现内容错误?欢迎来信交流️