Unix was conceived as a file-oriented system where any task could be carried out by assembling a set of smaller tools. Having a proper shell language also meant that we had a somewhat real tool to write scripts with.

For a lot of task, shell scripting is fine and is definitely something that every programmer should know in addition to its normal ${LANGUAGE}. Sometimes though, some operations are cumbersome to do in shell, anything that involves XML parsing for instance (and no, regex is not satisfactory).

For these you usually have to resort to heavier languages like Perl or Python. But wouldn’t it be nice to use language you are used to instead?

C# for instance has evolved enough with the years that most of its verboseness can be cut away. And with API like Linq and its various incarnation (Linq to XML in our case) plus syntactic sugar like the var keyword and object initializers, we have a powerful yet simple subset of C# for scripting use.

The only thing remaining is to actually have a tool to execute our scripts since we don’t want to manage the compilation and execution of our scripts. Fortunately we already have that tool in the form of the CSharp REPL (the csharp(1) command) that comes with any recent enough Mono by default.

What is less known is that this REPL can also be used to run scripts, taking the form of a line by line interpreter. Together with API like Linq (and its other incarnation especially Linq to XML) plus syntactic sugar like the var keyword and object initializers, we have a powerful yet simple subset to write scripts in.

The following is for instance an example of a quick script to merge XML trees together, something that is near impossible to do in traditional shell:

#!/usr/bin/csharp

LoadAssembly ("System.Xml.Linq.dll");
using System.Xml.Linq;
using System.IO;

var files = Environment.GetEnvironmentVariable ("FILES").Split ().Where (f => File.Exists (f));
var mainFile = files.First ();
var main = XDocument.Load (mainFile);
foreach (var other in files.Skip (1).Select (d => XDocument.Load (d))) {
	main.Root.Element ("Assemblies").Add (other.Root.Element ("Assemblies").Elements ());
	main.Root.Element ("Types").Add (other.Root.Element ("Types").Elements ());
}
main.Save (mainFile);

The first thing to notice is the shebang at the top that tells that the script runs with the csharp interpreter. The main limitation of using the csharp command that way is that your script has no way to interact with command line argument because they are used by the interpreter itself, I personally used environment variable as a workaround until we make that part better.

The second thing to notice are the calls to LoadAssembly which essentially replicates the /r: flags passed to the compiler and are used the same way.

That’s about it, the rest of the script is the good ol’C# you have come to love.

Of course, nothing is ever all white and the interpreter suffers from a bunch of shortcomings. I already mentioned the lack of support for command line arguments but debugging errors is also extremely painful because as the script is executed line by line, you can get a lot of cascading errors due to the fact the interpreter doesn’t stop at the first one.

All in one, using csharp makes a good solution for short quick&dirty scripts that can’t easily be done with traditional shell and the Unix toolset in domains like XML processing, database, parallel processing or REST API usage.