8

Bash can give unexpected results if one edits a script while the script is running. But it would often be very convenient to be able to edit a temporary copy of a script that I run within a shell, but make sure it is back in the original location before I run it again. Does anyone have suggestions for a workflow or customizations to Emacs to facilitate this?

Community
  • 1
  • 1
Michael Hoffman
  • 32,526
  • 7
  • 64
  • 86

4 Answers4

7

M-x delete-file, press down to insert obtain the path to the file you're editing, and RET to delete the file. When you next save your buffer, this will create a new file. Bash will keep executing the old, deleted file.

By the way, in most cases, you don't even need to take this precaution. Emacs usually saves to a temporary file, then renames that temporary file to the proper file name (which deletes the old file, unless it's being kept as a backup). Emacs only overwrites the file in place if it detects that it can't recreate the file properly: if the file has hard links, or if you don't have write permission on the directory. (I'm simplifying a little; the details are in the basic-save-buffer-2 function in files.el.) As long as the file has a single directory entry and you have write permissions on the directory containing the script, just press C-x C-s.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • That sounds really simple. I need to double-check that this actually works. If so, does that mean that Emacs's existing save-and-rename behavior will keep this problem from cropping up? – Michael Hoffman Dec 02 '11 at 00:33
  • @MichaelHoffman Yes, if Emacs is doing save-and-rename (which is the default behavior in most cases) then you won't have that problem at all. Note that I'm assuming a real unix here, if you're running bash on something like Cygwin you may not be able to remove the file (I'm not sure about this). – Gilles 'SO- stop being evil' Dec 02 '11 at 00:34
  • Can you please edit your answer to include this? It seems the whole question was unnecessary, because Emacs does the right thing out of the box. – Michael Hoffman Jan 27 '12 at 19:01
  • If the description of Emacs' default behaviour was true in 2011, it seems that it has changed for the worse (at least so far as this question is concerned, unless I have missed something, which is certainly possible). `file-precious-flag` is `nil` by default (but may break hard-links if enabled). The *number* of hard-links is only considered if `break-hardlink-on-save` is non-`nil` (which it isn't by default). I don't see any configuration which would mean "never break hard-links, but otherwise write-temp-file-and-rename if possible"... – phils Sep 15 '16 at 22:07
  • ...This may be *partly* mitigated by the default `backup-by-copying` setting (`nil`), as Emacs would rename the original file to be a backup, and then create a new file for the original filename; however that would only apply if a backup has actually occurred. – phils Sep 15 '16 at 22:07
7

[edit] The best workaround is done in shell script side, not in Emacs. In short, _put all in a brace, and put "exit" before the closing brace. Read the linked answer well to avoid pitfalls.

In this reply, I supplement Gilles's answer with theory.

The reason deletion is safe but modification is not is that in Unix, a deleted file gets inaccessible from the file system, but the processes that had opened the file (here, /bin/bash) can still read it, as explained here. It's not that bash does something special, like buffering the entire file, but it simply reads. (Even after the deletion, you can recover the script while it's running, as explained here. In bash, the script seems to be always opened as 255, /proc/<pid>/fd/255.)

[edit] My old answer included Emacs solution and co, as per OP's request. But after years of experience, I'm sure that the above pointer is the best, far better than any other attempts.)

teika kazura
  • 1,348
  • 1
  • 16
  • 21
2

Just adding this solution as an easy and clean way to protect running BASH scripts from being changed:

#! /bin/bash
{
your_code;
exit;
}
korkman
  • 1,952
  • 1
  • 11
  • 14
  • Mentioned at the end of teika kazura's answer as well, but still handy to highlight it I think. – phils Sep 16 '16 at 00:19
2

i already had a function for renaming the current buffer's file. was a short jump to a function which made it easy to toggle to a temp version of a file and then back again. basically, if you run this function when visiting a normal file "foo.txt", it will copy the file to a new file in the same dir with the name "tmp.foo.txt". edit as desired. when ready to switch back, run the function again and it will move the temp file back over the original file. i used the prefix "tmp." instead of a suffix because most mode recognition in emacs is based on the suffix (you could probably do something fancier with remembering all the buffer modes and re-applying...).

(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)))
jtahlborn
  • 52,909
  • 5
  • 76
  • 118