29
$\begingroup$

I guess my programming working style is not unique. I develop programs one cell at a time, and when I have some confidence about what I did, I put all my mess together refining the code, building up functions from nailed constructs, scoping variables, etc.

That usually means being on a dirt road most of the time: evaluating a lot of cells, inspecting FullForms, checking different conditions, clicking the |Show More| button, etc.

Before I perform any action on my ongoing .nb I should save my work in advance.
But alas! My mind is weak, my memory fades and my feeble fingers are too quick for my tired eyes.
So, when I press Shift-Enter or |Show More| and my Front End hangs, I remember Michael Douglas' D-FENS character in "Falling Down" with utmost sympathy.

I would like an automatic way to save my input cells (not the output ones, as they could be really large) in a backup .nb file before and every time any computation is requested. How?

PS:
I must confess, my FrontEnd-fu is still in its teens.
I think $pre could be set to the desired command, but I don't know how to select only the input cells and save them programmatically.

$\endgroup$
4
  • 1
    $\begingroup$ Related (possible duplicate): mathematica.stackexchange.com/q/6435/5 $\endgroup$
    – rm -rf
    Commented Jul 27, 2012 at 3:33
  • $\begingroup$ @R.M perhaps, but at first sight he is asking for a timed backup, which is not my target $\endgroup$ Commented Jul 27, 2012 at 3:36
  • $\begingroup$ There's the NotebookAutoSave option in the option inspector, or SetOptions[EvaluationNotebook[], NotebookAutoSave -> True]. This saves after each evaluation---not precisely what you asked for, but may be useful. $\endgroup$
    – Szabolcs
    Commented Jul 27, 2012 at 6:54
  • $\begingroup$ @Szabolcs Yes, two main differences: 1) I lost my last editing session 2) I don't like my "emergency backup" to overwrite the original file (as I perhaps made some mistake and don't want to permanently regret it) $\endgroup$ Commented Jul 27, 2012 at 13:40

2 Answers 2

22
$\begingroup$

The following definition to $Pre will save a copy of your notebook as filename.nb.backup.nb, where filename.nb is your original notebook and strip it of all output cells every time you evaluate a cell.

$Pre = With[
    {nb = NotebookPut[NotebookGet[EvaluationNotebook[]]]},
    FrontEndExecute[{
        FrontEnd`NotebookFind[nb, "Output", All, CellStyle, AutoScroll -> False], 
        FrontEnd`FrontEndToken["Clear"]
    }];
    NotebookSave[nb, NotebookFileName[] <> ".backup"];
    NotebookClose[nb];
    #
] &
$\endgroup$
7
  • 2
    $\begingroup$ Seems to work pretty well! $\endgroup$ Commented Jul 27, 2012 at 4:05
  • 1
    $\begingroup$ It will change the result of evaluation in some special cases, for example when you evaluate an expression with Head Sequence or Unevaluated. Probably $PreRead would be more universal. $\endgroup$ Commented Jul 27, 2012 at 6:14
  • 3
    $\begingroup$ I don't like auto-save as it seems to inevitably result in the far more frustrating event of auto-saving just after an accidental deletion or other major incorrect change. By saving to a separate backup notebook you eliminate this. +1 $\endgroup$
    – Mr.Wizard
    Commented Jul 27, 2012 at 10:22
  • $\begingroup$ One of my fu-moves is Alt-/, which toggles comments. I save very frequently, and almost always modify code by copying it into a new cell and tweaking it there, commenting out the old version when necessary. An accident with the code being tweaked, then, is isolated to a copy, and I can always safely back up. This is a verbose style, and the final notebooks need a LOT of cleanup, but it's robust and easy on the fingers. $\endgroup$
    – Reb.Cabin
    Commented Jul 31, 2012 at 20:36
  • 1
    $\begingroup$ Learning here, Could someone please explain what this does in a little more detail? For example is $pre changed permanently until re-defined or cleared? or is this change only going to stay in effect until you restart mathematica? How does one go about implementing this? Do I just stick this code in a cell and evaluate? Does it have to say in the notebook or can I delete it? Is $pre similar to an environmental variable? Thanks in advance (I didn't think this would warrant a new question, if I'm wrong I could ask one) $\endgroup$
    – Ajay
    Commented Oct 20, 2014 at 14:04
7
$\begingroup$

Motivated by the same concerns, I wrote the following code and added it to my init.m file. There are two main entries you'll want to change to use this. The global variable $SwapDirectory is where the swap files are saved (by swap file, I mean it in the VIm sense; an "extra" copy of your notebook, separate from your manually saved copy that periodically saves any new work). The swap files are organized within the swap directory in a directory structure which "mirrors" their original file locations, and have ".swp" appended to their file names. The other variable you might want to change is the number of seconds between autosaves, indicated by the "300" (corresponding to 5 minutes) near the bottom of the code below. At the appropriate times, this code will (automatically in the background) save swap files for ALL open notebooks, unless they are unmodified from their manually-saved versions (this exception makes the code more efficient, and more importantly, prevents the storage of swap files for documentation notebooks, for example).

In its current form, the code does not filter for only the input cells, but hopefully you can use the other answers to make that modification yourself.

Some things to note:
1) the Mathematica Put command seems to have trouble writing to network drives, even when offline access is enabled. Therefore, it is probably best to choose a SwapDirectory that is on your local machine.

2) Within SwapDirectory, you should create a sub-directory called "Recovery". This is where the AutoSaveSwap routine will make an initial save of any notebooks for which there is NO existing manual save location.

3) Simply evaluate

RecoverSwap["filePath"]
where "filePath" is a string representing the filePath of the MANUALLY-SAVED copy of the file (i.e., not the file that was created by AutoSave). This will then pop up a window containing the most recent auto-saved version of the file. The manually saved version is NEVER overwritten, unless you explicitly choose to do so. Once the recovered version pops up, you can save it whereever you like, or discard it at your discretion.

4) You should probably add this code to the KERNEL version of init.m ($UserBaseDirectory/Kernel/init.m) rather than the frontend version... this way, if you quit and restart the kernel, the autosave feature will also restart. On the other hand, this means that you must evaluate at least one expression after each start or restart to begin auto-saving. Once this initial evaluation is done, you do NOT need to have evaluated a cell for it to be backed up (unlike the built-in autosave utility).

Hope this helps someone! Feel free to respond with any questions, suggestions, or requests for improvement you may have. And, if you find this post useful, upvotes would be most appeciated! Take care.


$SwapDirectory= "C:\\Users\\pacoj\\Swap Files\\"; 
SaveSwap[nb_NotebookObject]:=Module[
    {fileName, swapFileName, nbout, nbdir, nbdirout, recoveryDir},
    If[ ! SameQ[Quiet[NotebookFileName[nb]], $Failed],
    (* if the notebook is already saved to the file system *)
        fileName = Last[ FileNameSplit[ NotebookFileName[nb]] ];
        swapFileName = fileName <> ".swp";
        nbdir = Rest[FileNameSplit @ NotebookDirectory[nb]];
        nbdirout= FileNameJoin[ FileNameSplit[$SwapDirectory]~Join~nbdir]<>"\\";
        If[!DirectoryQ[nbdirout], CreateDirectory[nbdirout]];
        nbout = NotebookGet[nb];
        Put[nbout, nbdirout <> swapFileName],
        (* else, if the file has never been saved, save as untitled *)
        recoveryDir= $SwapDirectory <> "Recovery\\";
        fileName= ("WindowTitle" /. NotebookInformation[nb])<>".nb";
        NotebookSave[nb, recoveryDir <> fileName]
    ] 
]; 
RecoverSwap::noswp= "swap file 1 not found in expected location";
    RecoverSwap[nbfilename_String]:=Module[
    {fileName, swapFileName, nbin, nbdir, nbdirout},
    fileName= Last[ FileNameSplit[ nbfilename] ];
    swapFileName= fileName <> ".swp";
    nbdir= Most[ Rest[FileNameSplit @ nbfilename] ];
    nbdirout= FileNameJoin[ FileNameSplit[$SwapDirectory]~Join~nbdir]<>"\\";
    If[ FileNames[swapFileName, {nbdirout}] == {},
       Message[RecoverSwap::noswp,nbdirout <> swapFileName]; Return[],
       nbin= Get[nbdirout <> swapFileName]; NotebookPut[nbin]
    ] 
]; 
AutoSaveSwaps= CreateScheduledTask[
    SaveSwap /@ Select[Notebooks[], "ModifiedInMemory" /. NotebookInformation[#]&],
    300 
] 
StartScheduledTask[AutoSaveSwaps] 

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.