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:
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[]
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[]
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[]
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.
Run[]
andRunThrough[]
but I don't think they can work interactively. $\endgroup$