Emacs工作流在运行时编辑Bash脚本

时间:2011-12-01 02:09:22

标签: bash emacs

Bash可以提供unexpected results if one edits a script while the script is running。但是,能够编辑我在shell中运行的脚本的临时副本通常会非常方便,但在再次运行它之前确保它回到原始位置。有没有人对Emacs的工作流程或自定义有什么建议来促进这个?

4 个答案:

答案 0 :(得分:8)

M-x delete-file,按down插入获取您正在编辑的文件的路径,然后按RET删除该文件。当您下次保存缓冲区时,这将创建一个新文件。 Bash将继续执行旧的已删除文件。

顺便说一句,在大多数情况下,您甚至不需要采取这种预防措施。 Emacs通常会保存到临时文件,然后将该临时文件重命名为正确的文件名(删除旧文件,除非将其保留为备份)。如果文件检测到无法正确重新创建文件,则Emacs仅覆盖文件:如果文件具有硬链接,或者您对目录没有写入权限。 (我简化了一点;详细信息在basic-save-buffer-2中的files.el函数中。)只要文件只有一个目录条目,并且您对包含脚本的目录有写权限,按C-x C-s

答案 1 :(得分:7)

在这个回复中,我

  1. 用吉尔斯的理论补充了很好的答案。
  2. 建议改进emacs的使用。
  3. 建议替代方案,纯粹通过shell脚本编写。
  4. 警告:我不是专业程序员。

    1理论

    删除的原因是安全的,但修改不是在Unix中,删除的文件无法从文件系统访问,但打开文件的进程(此处为/ bin / bash)仍然可以读取它,如解释here。这不是bash做一些特别的事情,比如缓冲整个文件,但它只是读取。 (即使在删除之后,您也可以在脚本运行时恢复脚本as explained here。在bash中,脚本似乎总是以255 /proc/<pid>/fd/255打开。)

    2 Emacs解决方案

    现在emacs;为了安全起见,您可以自动化所有;添加到before-save-hook函数以检查它是否为sh模式,并在脚本打开时自动删除,方法是选中lsof

    (defvar delete-open-sh-flag nil
      "Used by `delete-open-sh.` If nil, deletion didn't happen.
    Otherwise, the mode is saved.")
    
    (defun delete-open-sh ()
      (setq delete-open-sh-flag nil)
      (when (and (eq major-mode 'sh-mode)
             (buffer-file-name)
             (file-exists-p (buffer-file-name)))
        (let ((buf (generate-new-buffer " *foo*")))
          (call-process "lsof" nil; infile
                buf ; dest
                nil ; display
                (buffer-file-name))
          (unless (eq 0 (buffer-size buf))
            (setq delete-open-sh-flag (file-modes (buffer-file-name)))
            (delete-file (buffer-file-name)))
          (kill-buffer buf))))
    
    (defun delete-open-sh-after ()
      (when delete-open-sh-flag
        (set-file-modes (buffer-file-name) delete-open-sh-flag)))
    
    (add-hook 'before-save-hook 'delete-open-sh)
    (add-hook 'after-save-hook 'delete-open-sh-after)
    

    但这并不完美。它保存了文件模式,但这就是全部。

    3纯粹的bash解决方案

    或者,您可以让bash脚本本身进行删除。使用此:

    # Usage: . /path/to/this-script (Always use abs path)
    
    # Restart the caller script itself in a temporary file; this allows
    # editing of the caller script file while it's run.
    # Temporary file is soon deleted.
    #
    if [[ ! "`dirname $0`" =~ ^/tmp/.sh-tmp ]]; then
        mkdir -p /tmp/.sh-tmp/
        DIST="/tmp/.sh-tmp/$( basename $0 )"
        install -m 700 "$0" $DIST
        exec $DIST "$@"
    else
        rm "$0"
    fi
    

    用法示例:将此文件保存在/home/foo/a.sh中:

    #!/bin/sh
    echo "$0 $@"
    . /home/foo/the-above-code.sh
    echo "Again, $0 $@"
    

    如果您将上述脚本调用为./a.sh 1 2 3,则结果为:

    /home/foo/a.sh 1 2 3
    /tmp/.sh-tmp/a.sh 1 2 3
    Again, /tmp/.sh-tmp/a.sh 1 2 3
    

    这可能是最好的,但使用风险自负。

    众所周知,按照here的说法,将所有内容放在大括号中,以exit结束即可完成工作。

答案 2 :(得分:2)

我已经有了重命名当前缓冲区文件的功能。是一个短暂的跳转到一个功能,这使得很容易切换到文件的临时版本然后再回来。基本上,如果在访问普通文件“foo.txt”时运行此函数,它会将文件复制到同一目录中名为“tmp.foo.txt”的新文件中。根据需要编辑。当准备好切换回来时,再次运行该功能,它会将临​​时文件移回原始文件。我使用了前缀“tmp”。而不是后缀,因为emacs中的大多数模式识别都是基于后缀的(你可以通过记住所有缓冲模式并重新应用来做更好的事情......)。

(defun toggle-temp-file ()
  (interactive)
  (let* ((cur-buf (current-buffer))
         (cur-file (buffer-file-name cur-buf))
         (cur-file-dir (file-name-directory cur-file))
         (cur-file-name (file-name-nondirectory cur-file))
         new-file)
    (if (string-match "^tmp\\.\\(.+\\)\\'" cur-file-name)
        ;; temp file -> orig file
        (progn
          (setq new-file
                (concat cur-file-dir (match-string 1 cur-file-name)))
          (rename-file cur-file new-file t))
      ;; orig file -> temp file
      (setq new-file (concat cur-file-dir "tmp." cur-file-name))
      (copy-file cur-file new-file nil nil t))
    (kill-buffer cur-buf)
    (find-file new-file)))

答案 3 :(得分:2)

只需添加此解决方案,即可轻松,简洁地保护正在运行的BASH脚本不被更改:

#! /bin/bash
{
your_code;
exit;
}