一个老问题,大家如何在emacs中远端和本地互传文件

这个使用过dired中的复制结合emacs-async异步复制,但是感觉emacs-async有时候并不能复制文件,一直提示在复制,实际没有复制,不知道什么问题.老感觉不robust.也在终端使用过scp上传和下载文件.不知道大家就互相传输文件(包括大文件),有没有在emacs中比较好的解决方案.

TRAMP 打开远程文件,再 M-x copy-file

sftp 或磁力链接,不要老想着事事用 emacs 解决。

对了,弄个 HTTPS 也不错。

emacs-async 文件一大或一多就经常歇菜,没什么卵用…

分享自己用rsync在远端和本地互传文件的东西。之前在命令行里面补全各种文件名,后来实在受不了就弄了这个,发现还挺好用的。本质上就是把emacs当作一个选择界面,从~/.ssh/config里面读取远端host名字并选择。如果是下载的话,ssh上去列出要下载的文件并选择,如果是上传的话,在dired里面mark上要上传的文件就可以了。对了,这个只是在远端和本地的相同文件夹下进行操作。

(defun swint-dired-rsync (action)
  (interactive)
  (let ((remote (completing-read "Remote repo: "
                                 (split-string
                                  (shell-command-to-string
                                   "cat ~/.ssh/config | grep \"^host \" | awk '{print $2}'"))))
        (path (abbreviate-file-name default-directory))
        (is-push (equal action "push"))
        (is-pull (equal action "pull"))
        (string-to-escape "\\( \\|(\\|)\\|\\[\\|\\]\\|{\\|}\\)")
        rsync-command)
    ;; 对于rsync,escape本地路径用\,远程路径用\\\。
    (cl-flet ((escape-local (x)
                            (replace-regexp-in-string string-to-escape
                                                      "\\\\\\1" x))
              (escape-remote (x)
                             (replace-regexp-in-string string-to-escape
                                                       "\\\\\\\\\\\\\\1" x)))
      (let ((files (cond (is-push
                          (cl-loop for f in (dired-get-marked-files)
                                   collect (escape-local f)))
                         (is-pull
                          (let ((remote-files (helm-comp-read "Remote files: "
                                                              (split-string (shell-command-to-string
                                                                             ;; 连接remote列出path下文件绝对路径,并不显示错误信息。
                                                                             (format "ssh %s '(cd %s && ls -A | sed \"s:^:`pwd`/:\") 2>/dev/null'"
                                                                                     remote (escape-local path))) "\n")
                                                              :marked-candidates t
                                                              :buffer (format "*helm rsync %s*" remote))))
                            (cl-loop for f in remote-files
                                     collect (concat remote ":" (escape-remote (if (directory-name-p f)
                                                                                   (directory-file-name f)
                                                                                 f))))))))
            (dest (cond (is-pull (escape-local path))
                        (is-push (escape-remote (concat remote ":" path))))))
        (setq rsync-command "rsync -arv --progress ")
        (dolist (file files)
          (setq rsync-command
                (concat rsync-command file " ")))
        (setq rsync-command (concat rsync-command dest))))
    (let ((process (start-process-shell-command "rsync" "*rsync*" rsync-command)))
      (lexical-let ((pos (memq 'mode-line-modes mode-line-format))
                    (mode-string action))
        (setcdr pos (cons (concat "Rsync/Unison " mode-string " ") (cdr pos)))
        (set-process-sentinel
         process
         (lambda (process signal)
           (when (memq (process-status process) '(exit signal))
             (message "Rsync/Unison %s done." mode-string)
             (setcdr pos (remove (concat "Rsync/Unison " mode-string " ") (cdr pos))))))))))

然后绑定快捷键就可以了,希望有帮助。

(global-set-key (kbd "M-g ,") '(lambda (&optional arg) (interactive "P") (swint-dired-rsync "pull")))
(global-set-key (kbd "M-g .") '(lambda (&optional arg) (interactive "P") (swint-dired-rsync "push")))
6 个赞

可以.貌似可以用.

感谢分享!考虑一下扩展成 melpa package?

没弄过melpa,等有时间折腾折腾

能不能改成,去掉这个前提条件:"这个只是在远端和本地的相同文件夹下进行操作".

其实我这里原始函数默认是当前文件夹,而当按键前加C-u时,可以选择不同文件夹。这时候,调用另外一个counsel-read-file-for-rsync函数,通过tramp选择文件和位置,但我对tramp感觉不太好,不怎么用,就没贴出来。有兴趣的话,可以看一下。

还有,这里面还有在终端下面,用percol来做界面的rsync的函数,widget-percol-rsync-pull和widget-percol-rsync-push,我觉得也挺好用。

你好, 我在使用你的代码时出现了错误:

Debugger entered--Lisp error: (wrong-type-argument consp nil)
  setcdr(nil ("Rsync/Unison push "))
  (progn (setcdr (symbol-value --cl-pos--) (cons (concat "Rsync/Unison " (symbol-value --cl-mode-string--) " ") (cdr (symbol-value --cl-pos--)))) (set-process-sentinel process (list (quote lambda) (quote (&rest --cl-rest--)) (list (quote apply) (list (quote function) (function (lambda (G3 G4 process signal) (if ... ...)))) (list (quote quote) --cl-mode-string--) (list (quote quote) --cl-pos--) (quote --cl-rest--)))))
  (let ((--cl-pos-- (make-symbol "--pos--")) (--cl-mode-string-- (make-symbol "--mode-string--"))) (setf (symbol-value --cl-pos--) (memq (quote mode-line-modes) mode-line-format) (symbol-value --cl-mode-string--) action) (progn (setcdr (symbol-value --cl-pos--) (cons (concat "Rsync/Unison " (symbol-value --cl-mode-string--) " ") (cdr (symbol-value --cl-pos--)))) (set-process-sentinel process (list (quote lambda) (quote (&rest --cl-rest--)) (list (quote apply) (list (quote function) (function (lambda ... ...))) (list (quote quote) --cl-mode-string--) (list (quote quote) --cl-pos--) (quote --cl-rest--))))))
  (lexical-let ((pos (memq (quote mode-line-modes) mode-line-format)) (mode-string action)) (setcdr pos (cons (concat "Rsync/Unison " mode-string " ") (cdr pos))) (set-process-sentinel process (function (lambda (process signal) (if (memq (process-status process) (quote (exit signal))) (progn (message "Rsync/Unison %s done." mode-string) (setcdr pos (remove ... ...))))))))
  (let ((process (start-process-shell-command "rsync" "*rsync*" rsync-command))) (lexical-let ((pos (memq (quote mode-line-modes) mode-line-format)) (mode-string action)) (setcdr pos (cons (concat "Rsync/Unison " mode-string " ") (cdr pos))) (set-process-sentinel process (function (lambda (process signal) (if (memq (process-status process) (quote ...)) (progn (message "Rsync/Unison %s done." mode-string) (setcdr pos ...))))))))
  (let ((remote (completing-read "Remote repo: " (split-string (shell-command-to-string "cat ~/.ssh/config | grep \"^Host \" | awk '{print $2}'")))) (path (abbreviate-file-name default-directory)) (is-push (equal action "push")) (is-pull (equal action "pull")) (string-to-escape "\\( \\|(\\|)\\|\\[\\|\\]\\|{\\|}\\)") rsync-command) (let* ((--cl-escape-local-- (function (lambda (x) (replace-regexp-in-string string-to-escape "\\\\\\1" x)))) (--cl-escape-remote-- (function (lambda (x) (replace-regexp-in-string string-to-escape "\\\\\\\\\\\\\\1" x))))) (progn (let ((files (cond (is-push ...) (is-pull ...))) (dest (cond (is-pull ...) (is-push ...)))) (setq rsync-command "rsync -arv --progress ") (let ((--dolist-tail-- files) file) (while --dolist-tail-- (setq file (car --dolist-tail--)) (setq rsync-command (concat rsync-command file " ")) (setq --dolist-tail-- (cdr --dolist-tail--)))) (setq rsync-command (concat rsync-command dest))))) (let ((process (start-process-shell-command "rsync" "*rsync*" rsync-command))) (lexical-let ((pos (memq (quote mode-line-modes) mode-line-format)) (mode-string action)) (setcdr pos (cons (concat "Rsync/Unison " mode-string " ") (cdr pos))) (set-process-sentinel process (function (lambda (process signal) (if (memq ... ...) (progn ... ...))))))))
  swint-dired-rsync("push")
  (lambda (&optional arg) (interactive "P") (swint-dired-rsync "push"))(nil)
  funcall-interactively((lambda (&optional arg) (interactive "P") (swint-dired-rsync "push")) nil)
  call-interactively((lambda (&optional arg) (interactive "P") (swint-dired-rsync "push")) nil nil)
  command-execute((lambda (&optional arg) (interactive "P") (swint-dired-rsync "push")))

好像是:

(lexical-let ((pos (memq 'mode-line-modes mode-line-format))
                    (mode-string action))
        (setcdr pos (cons (concat "Rsync/Unison " mode-string " ") (cdr pos)))
        (set-process-sentinel
         process
         (lambda (process signal)
           (when (memq (process-status process) '(exit signal))
             (message "Rsync/Unison %s done." mode-string)
             (setcdr pos (remove (concat "Rsync/Unison " mode-string " ") (cdr pos)))))))

pos 这个变量在我电脑中获取到为 nil, 我不知道这个参数的作用, 能够帮忙解释下?

这里是执行命令的时候在mode-line上加一个提示,把pos相关的行都去掉不影响使用。出错可能是你的mode-line上面本身不显示major mode和minor mode这些的提示符,mode-line-format里面没有mode-line-modes这个东西。无所谓的,把有pos的行去掉就行勒。

试试这个 GitHub - stsquad/dired-rsync: Support for rsync from Emacs dired buffers

最近发现total commander里面安装sftp插件,就可以通过ssh打开远程电脑上文件夹,像手机上本地文件夹一样,应该只要支持sftp的文件管理器都可以。

感觉比傻傻的按手机上的小键盘好多了,小手机总容易按错。

打开eshell,然后命令行rsync或sftp 01

如果只是编辑服务器端的文件,可以tramp直接编辑