2011-12-01 12 views
7

Bash può dare unexpected results if one edits a script while the script is running. Ma sarebbe spesso molto comodo poter modificare una copia temporanea di uno script che eseguo all'interno di una shell, ma assicurati che sia tornato nella posizione originale prima di eseguirlo di nuovo. Qualcuno ha suggerimenti per un flusso di lavoro o personalizzazioni per Emacs per facilitare questo?Flusso di lavoro di Emacs per modificare gli script di Bash mentre vengono eseguiti

risposta

8

M-x delete-file, premere per inserire down ottenere il percorso del file che si sta modificando, e RET per eliminare il file. Al prossimo salvataggio del buffer, verrà creato un nuovo file. Bash manterrà in esecuzione il vecchio file cancellato.

A proposito, nella maggior parte dei casi, non è nemmeno necessario prendere questa precauzione. Emacs di solito salva in un file temporaneo, quindi rinomina quel file temporaneo con il nome file corretto (che cancella il vecchio file, a meno che non venga tenuto come backup). Emacs sovrascrive il file solo se rileva che non è in grado di ricreare correttamente il file: se il file ha collegamenti fisici o se non si dispone dell'autorizzazione alla scrittura nella directory. (Sto semplificando un po ', i dettagli sono nella funzione basic-save-buffer-2 in files.el.) Finché il file ha una singola voce di directory e si dispone delle autorizzazioni di scrittura nella directory contenente lo script, è sufficiente premere C-x C-s.

+0

Che suona davvero semplice. Devo controllare che funzioni effettivamente. In tal caso, vuol dire che l'attuale comportamento di salvataggio e ridenominazione di Emacs manterrà questo problema fuoriuscito? –

+0

@MichaelHoffman Sì, se Emacs sta facendo save-and-rename (che è il comportamento predefinito nella maggior parte dei casi), allora non avrai affatto questo problema. Nota che sto assumendo un vero unix qui, se stai usando bash su qualcosa come Cygwin potresti non essere in grado di rimuovere il file (non ne sono sicuro). – Gilles

+0

Puoi modificare la tua risposta per includerla? Sembra che l'intera domanda non sia necessaria, perché Emacs fa la cosa giusta fuori dagli schemi. –

2

ho già avuto una funzione per rinominare il file del buffer corrente. era un breve salto a una funzione che rendeva facile passare a una versione temporanea di un file e quindi tornare di nuovo. in sostanza, se si esegue questa funzione quando si visita un file normale "foo.txt", verrà copiato il file in un nuovo file nella stessa dir con il nome "tmp.foo.txt". modifica come desiderato. quando sei pronto a tornare indietro, esegui di nuovo la funzione e il file temporaneo si sposterà sul file originale. ho usato il prefisso "tmp". invece di un suffisso perché la maggior parte del riconoscimento di modalità in emacs si basa sul suffisso (probabilmente potresti fare qualcosa di più divertente ricordando tutte le modalità del buffer e applicando nuovamente ...).

(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))) 
7

In questa risposta, ho

  1. supplemento la bella risposta di Gilles con la teoria.
  2. suggeriscono un miglioramento nell'utilizzo di emacs.
  3. suggerire alternative, esclusivamente tramite script di shell.

Attenzione: non sono un programmatore professionista.

1 Teoria

Il motivo eliminazione è sicuro, ma la modifica non è che in Unix, un file cancellato ottiene inaccessibili dal file system, ma i processi che avevano aperto il file (qui,/bin/bash) può ancora leggerlo, come spiegato here. Non è che bash faccia qualcosa di speciale, come il buffering dell'intero file, ma semplicemente legge. (Anche dopo l'eliminazione, è possibile recuperare lo script mentre è in esecuzione, as explained here In bash, lo script sembra essere sempre aperto come 255, /proc/<pid>/fd/255..) Soluzione

2 Emacs

Ora emacs; per sicurezza, puoi automatizzare tutto; aggiungi ai before-save-hook una funzione per controllare se è sh-mode, ed eliminare automaticamente se lo script è aperta, controllando con 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) 

Ma questo non è perfetto. Salva la modalità file, ma questo è tutto.

3 soluzione bash pura

In alternativa, è possibile lasciare che lo script bash per sé fare l'eliminazione. Utilizzare questa:

# 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 "[email protected]" 
else 
    rm "$0" 
fi 

Esempio di utilizzo: salvare il file in /home/foo/a.sh:

#!/bin/sh 
echo "$0 [email protected]" 
. /home/foo/the-above-code.sh 
echo "Again, $0 [email protected]" 

Se si richiama lo script di cui sopra come ./a.sh 1 2 3, il risultato è:

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

Questo potrebbe essere il migliore, ma utilizzare a proprio rischio.

È anche noto che mettere tutto in una parentesi, concludendo con exit fa il lavoro, come spiegato here.

1

Solo l'aggiunta di questa soluzione come un modo semplice e pulito per proteggere l'esecuzione di script BASH venga modificata:

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

Menzionato anche alla fine della risposta di teika kazura, ma comunque utile per evidenziarlo, penso. – phils

Problemi correlati