26
$\begingroup$

I love how notebooks work in Mathematica.

You can edit code in real time and hit Ctrl+Shift to run it.

Additionally you copy and paste different cells around to organize and test ideas.

Search and replace, plot/visualization,etc.. are built into the system.

My question: How could you adequately adapt Mathematica to use as a terminal replacement?

If not (this might get transferred to stackoverflow.com) is there a terminal editor that exists that works like Mathematica's notebook interface?

$\endgroup$
3
  • $\begingroup$ You can call external programs with Run[] and RunThrough[] but I don't think they can work interactively. $\endgroup$
    – 0xFE
    Commented Jan 27, 2013 at 7:25
  • 1
    $\begingroup$ In response to your second question, the BBEdit shell worksheet offers some broadly similar ideas. $\endgroup$
    – cormullion
    Commented Jan 27, 2013 at 9:20
  • 1
    $\begingroup$ Any version of Emacs has full terminal capability, and you find a version (often several) for any OS I know of. $\endgroup$
    – m_goldberg
    Commented Jan 27, 2013 at 9:55

2 Answers 2

37
$\begingroup$

One thing that we can do is exploit the CellEvaluationFunction option of cells to replace the standard evaluator with a function of our own design. In this case, that evaluation function will invoke the system shell.

First, let's create a helper function that can create such cells:

evaluatableCell[label_String, evaluationFunction_] :=
  ( CellPrint[
      TextCell[
        ""
      , "Program"
      , Evaluatable -> True
      , CellEvaluationFunction -> evaluationFunction
      , CellFrameLabels -> {{None, label}, {None, None}}
      , CellGroupingRules -> "InputGrouping"
      ]
    ]
  ; SelectionMove[EvaluationNotebook[], All, EvaluationCell]
  ; NotebookDelete[]
  ; SelectionMove[EvaluationNotebook[], Next, CellContents]
  )

This function creates a cell with the look of a Program cell, but evaluates its contents using a function we provide. So if we wanted to have the system shell evaluate the contents, we would do this:

evaluatableCell["shell", Import["!"~~#, "Text"]&]

Note that when we evaluate this expression, the input expression is erased and replaced with a new cell. I'll say that again: the original expression is erased. This is convenient for frequent use, but if you have typed in a complex expression for testing purposes, save it before you evaluate! If you don't like this behaviour, simply remove the SelectionMove and NotebookDelete lines from evaluatableCell.

The replacement cell is treated as an evaluatable input cell. The following example was run on Windows:

evaluatableCell

This shell cell is very basic. The error-handling is non-existent and, on Windows at least, ignores all lines after the first. We can make this a little more robust if we write the text to be evaluated into a script file, run it, and capture the output. The following helper function captures this common idiom:

Options[runTempFile] = {FilePattern -> "_", FileExtension -> Automatic};
runTempFile[command_, text_, OptionsPattern[]] :=
  Module[{stream, file, result}
  , stream = OpenWrite[]
  ; Export[stream, text, "Text"]
  ; file = Close[stream]
  ; OptionValue[FileExtension] /.
      ext:Except[Automatic] :> (file = RenameFile[file, file~~"."~~ext])
  ; result = Import[
      StringReplace[command, OptionValue[FilePattern] -> file]
    , "Text"
    ]
  ; DeleteFile[file]
  ; result
  ]

Error-recovery is still a bit lax in this example, but we'll ignore that and blunder on. Armed with this helper function, we can handle the Windows limitation a bit better:

shellEvaluate[cmd_] /; $OperatingSystem == "Windows" :=
  runTempFile["!_ 2>&1", "@echo off\n"~~cmd, FileExtension -> "cmd"]

shellEvaluate[cmd_] :=
  Import["!"~~cmd~~" 2>&1", "Text"]

shellCell[] := EvaluatableCell["Shell", shellEvaluate[#]&]

Note that we are now merging the standard output and standard error streams. Let's use shellCell:

shellCell[]

shellCell

So we have met our stated goal of being able to execute shell commands from within Mathematica. But why stop there? All the arguments for being able to intersperse shell commands into our notebooks apply to any other language we use. Maybe we have Haskell installed on our machine:

haskellEvaluate[source_] := runTempFile["!runhaskell _ 2>&1", source]

haskellCell[] :=
  EvaluatableCell["Haskell", haskellEvaluate[#]&]

then:

haskellCell[]

haskell

Mathematica's built-in Java also opens up some interesting possibilities. The included Rhino Javascript interpreter, for example:

Needs["JLink`"]
InstallJava[];

evaluateRhino[script_String] :=
  JavaBlock @ Module[{engine}
  , engine = JavaNew["javax.script.ScriptEngineManager"]@getEngineByName["JavaScript"]
  ; engine@eval[script]
  ]

rhinoCell[] :=
  evaluatableCell["Rhino", evaluateRhino[#]&]

Like this:

rhinoCell[]

rhino

I could go on with more examples, but I think the point is made.

For an example of a user-defined input cell type that remembers state between evaluations, see this question.

Once again, I'll point out that the error-handling presented here is weak. I omitted it in order to keep this posting to a manageable size and to avoid drowning out the key ideas with boilerplate code.

$\endgroup$
4
  • $\begingroup$ Why do you use ~~ in evaluatableCell["shell", Import["!" ~~ #, "Text"] &] opposed to evaluatableCell["shell", Import["!" <> #, "Text"] &] ? Is this simply a style question or is their a another reason? $\endgroup$
    – William
    Commented Jan 31, 2013 at 1:03
  • $\begingroup$ @Lime There is no deep reason for my choice... until you mentioned it I hadn't really noticed that I had switched my common usage from <> to ~~. $\endgroup$
    – WReach
    Commented Jan 31, 2013 at 1:17
  • 1
    $\begingroup$ Somehow I missed this version of your answer. Big +1. $\endgroup$ Commented Jul 25, 2013 at 14:53
  • $\begingroup$ Yes, I also only saw the previous version :). $\endgroup$ Commented Jul 25, 2013 at 15:03
2
$\begingroup$

This can be generalized to multiple languages.

evaluatableCell[label_String, evaluationFunction_] := (
   CellPrint[
    TextCell[label, "Program", Evaluatable -> True, 
     CellEvaluationFunction -> Function[{x},
       SelectionMove[EvaluationNotebook[], All, EvaluationCell];
       SelectionMove[EvaluationNotebook[], Previous, Cell];
       (* evaluatableCell[label,evaluationFunction] *)
       Module[{sc = NotebookRead[InputNotebook[]]},
        NotebookWrite[EvaluationNotebook[], sc];
        NotebookWrite[EvaluationNotebook[], Cell[BoxData[
           previous = evaluationFunction[x]
           ], "Text"]];
        ];
       SelectionMove[EvaluationNotebook[], All, CellContents];
       SelectionMove[EvaluationNotebook[], Next, Character];
       ], CellFrameLabels -> {{None, None}, {None, None}}, 
     CellGroupingRules -> "InputGrouping"]];
   SelectionMove[EvaluationNotebook[], All, EvaluationCell];
   SelectionMove[EvaluationNotebook[], Next, CellContents];
   SelectionMove[EvaluationNotebook[], Next, Character];
   );
evaluatableCell["command prompt", Import["!" ~~ #, "Text"] &]
evaluatableCell["cygwin", 
 Import["!" ~~ "C:\\cygwin64\\bin\\ls.exe", "Text"] &]
evaluatableCell["powershell", (
   str = OpenWrite[$TemporaryDirectory <> ToString@Unique["x"] <> 
      ".ps1" ];
   WriteString[str, #];
   Close[str];
   Import["!powershell -File \"" ~~ str[[1]] <> "\"", "Text"]
   ) &
 ]
$\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.