Swift

Swift, Shells In The 1960s, And Some Swift Scripting Examples For Admins

The reason Ken Thompson wrote the Thompson Shell (/bin/sh) when he and the team at Bell Labs developed Unix was that they didn’t want to have to teach programming to people in the patent office, who funded the PDP they used to write Unix.

Ken-Thompson-2019.png
Ken Thompson

Shell environments evolved over the years with tcsh, bash, and zsh to name a few. These added more concepts from programming environments, like the environment from C that the binaries they exposed were compiled in. Other languages emerged that were simpler than a language like C but added new techniques – and so perl, python, ruby, and others evolved. Some of those were either object-oriented from the outset or had object-orientation bolted on the side, like a FrankenLanguage. The addition of these shells to the Mac, gave those who wanted to automate tasks a simple interface to do so. Over time, shells have become more restricted, other scripting (or hybrid) languages removed from the Mac, and one alternative to shell scripting is now the ability to run a swift script.

Swift scripts feel a bit more like procedural languages in their simplest incantations. They can be as simple or as complicated as we want them to be. However, whereas in bash there are dozens of standard scripting functions, simple pipes, process controls, and commands that we can call, we have to use pre-built APIs or functions to perform similar tasks with a language like swift. This is easier shown than said. Let’s start with a simple swift example, the old “hello world” script. For this, most will use the print function (https://developer.apple.com/documentation/swift/print(_:separator:terminator:)) We’ll quote the string to print to the screen and, as with bash or zsh, begin the little script with a shebang line that call the swift environment to run the script:

#!/usr/bin/env swift

print("Hello Cruel World!")

Now let’s make it executable (assuming a name of HelloCruelWorld.swift):

chmod 750 HelloCruelWorld.swift

Then we can run it:

./HelloCruelWorld.swift

We could also have variabalized the words as follows:

#!/usr/bin/env swift

var firstWord = "Hello"

var secondWord = "Cruel"

var thirdWord = "World"

print(firstWord + " " + secondWord + " " + thirdWord + "!")

At this point, it’s worth pointing out that a function dates back to the era of lambda calculus when mathemeticians (most notably Alanzo Church in the 1930s) wanted a shorthand for a bunch of steps they might need to repeat ( the following image from https://medium.com/hackernoon/lets-code-the-roots-of-functional-programming-lambda-calculus-implemented-in-typescript-36806ebc2857 shows what that might look like:

Let's code the roots of Functional Programming: Lambda calculus implemented  in Typescript | by Enrico Piccinin | HackerNoon.com | Medium

The early programming languages built on this foundation and even often used the same symbols in lambda calculus. After the dawn of interactive computing, many (most notably Grace Hopper, but also John Backus with FORTRAN, etc) wanted to make computers more useful and programming more approachable by compiling these higher level functions into machine code. At the dawn of interactive computing and then timesharing, Bell Labs programmers were able to take the lessons learned from a generation of research, throw out the parts that Multics had stuffed in based on a “design by committee approach” and create C. C was so good it has only recently fallen to the 8th most used programming language of the day.

The syntax in simple swift scripts can be similar to that in C. For example, to print our hello world example to the screen in C with a function we’d use the following:

void myFunction();

int main() {

myFunction();

return 0; }

void myFunction() {

printf("Hello Cruel World!"); }

We can do the same in swift:

let secondWord = " Cruel World"

func hellocruelworld(person: String) -> String {

let theString = "Hello, " + secondWord + "!"

return theString }

The above is a little more condensed, but retains the parentheticals to denote an opening and closing stanza of objects to pass into the function, etc. We can avoid a step to void and init as swift takes care of some of that on our behalf. The printf is shortened to just print. But the similarities are there. Modern languages usually have lots of prebuilt functions, sometimes bundled into libraries or frameworks (or packages now). The trend here is that we’ll import frameworks that swift doesn’t come bundled with and call on functions from the frameworks for more complicated scripts. We’ll also want to use variables and constants in our scripts. Constants are immutable, so don’t change (mostly). Variables are, well, variable; they can change. So for a loop with maximums we might use the following:

let maximumNumberOfThings = 10

var currentNumberOfThings = 0

To add a tad bit of complexity, when we run the let, we can call a function and use the output of the function to supply that integer (or a string or array). We’re lazy typing. Before we set the contents of the variable we could have defined the type, like a String or an integer (Int in swift as follows):

var currentNumberOfThings: Int

With these basics in mind, let’s move on to what we often call “shelling out” a bash command from swift. We’ll again import Foundation, which gives us the Process() and Pipe() functions. The next section (most like to split these sections with an extra newline) then makes it simpler to call these by loading them into a constant via let. The next section loads the executable (/usr/sbin/netstat) and the next appends arguments to it (-R). Then, we run the information loaded into process with the type method run (https://developer.apple.com/documentation/foundation/process/2890108-run). The pipe (https://developer.apple.com/documentation/foundation/pipe) is necessary as it allows us to communicate between processes. The full netstat swift file is as follows:

#!/usr/bin/env swift

import Foundation

let process = Process()

let pipe = Pipe()

process.executableURL = URL(fileURLWithPath: "/usr/sbin/netstat")

process.arguments = ["-R"] try! process.run()

let data = pipe.fileHandleForReading.readDataToEndOfFile()

guard let standardOutput = String(data: data, encoding: .utf8)

else {

FileHandle.standardError.write(Data("Error in reading standard output data".utf8))

fatalError()

}

Now, anyone that runs the above file will notice why I chose netstat. The netstat will continue to run and display output to the screen, even after the swift script is stopped. We spawned a process but didn’t kill it. There’s always a better way than shelling out a zsh command in swift. However, while learning swift it provides a partial ramp for tasks people know how to do easier. After years with swift I’m just finally to the point where I refuse to invoke a bash command any more… Just keep in mind that sip or sandboxes can block certain tasks from completing.

Most of the people I know that use shell scripts or drop into the command line consistently aren’t patent attorneys; they’re people charged with automating tasks on vast numbers of computers. A common task for those who manage Macs is to read a property list into a variable (or a part of a property list) give that this is how preferences are stored on a Mac. That can be done via bash easily by using cat to display the raw contents of a file, defaults to walk through simple defaults domains within property lists, and plutil to walk and parse through more complicated property lists (e.g. those with nested arrays). Let’s take the property list off the table for a second and look at how we might read the contents of any file (although in this example it will be a property list) and then print it to the screen:

#!/usr/bin/env swift

import Foundation

let path = "/Library/Preferences/org.cups.printers.plist"

do {

let plist = try NSString(contentsOfFile: path, encoding: String.Encoding.ascii.rawValue)

plist.enumerateLines({ (line, stop) -> () in

print("\(line)") }) }

In the above script, define the path for the file (swap this with most any file not protected by SIP or a sandbox to simply produce its contents written to the screen. Then, we import the Foundation framework (https://developer.apple.com/documentation/foundation) to get at NSString (https://developer.apple.com/documentation/foundation/nsstring), which has options for reading the contentsOfFile (in this case the file is the path variable). Other parameters are included via dot notation. That’s loaded into the “let plist” but there’s one more issue, we have to do this line by line, so we’ll loop through those with enumerateLines (https://developer.apple.com/documentation/foundation/nsstring/1408459-enumeratelines). At this point the script still processes linearly like in old school procedural programming and the variables are lazily typed, so the structure is about as simple as we can make it. Still, with the all the curly braces, parenthesis, and extra steps (not to mention a little dot notation which looks nothing like the lambda calculus that inspired early programming languages), this script isn’t simple to read or write without some understanding of swift.

We can also add logic to our scripts. For example, if we wanted to look at the number of incorrect logins, we’d populate a variable, let’s call it incorrectLogins, with an integer and then if there’s data in the field we know a user account has attempted a failed login:

if incorrectLogins != nil {

print("The user has logged in incorrectly.") }

Another common task when wrapping our scripts around logic is to loop through a set of files or items in a tuple or array. To simplify this, let’s

var countNumber = 100

while countNumber > 0 {

print("\(countNumber)…")

countNumber -= 1 }

print("The count is complete")

Throughout this we’ve defined some of the most common tasks required for basic systems administration: how to print information to a screen (or pipeline it to other scripts), read information off a disk, substantiate variables and constants, create loops, perform if statements, create functions, and run shell commands. There’s much, much more, of course. It’s important that these are basic tasks. We’re treating swift as an interpreted language, so skipped compiling and all that goes with that. Compiling a simple script shouldn’t produce any errors provided it can run as interpreted by the swift interpreter. Here, we just just the swiftc command followed by the name of the swift script (as follows with the readdefaults.swift from our previous example):

swiftc readdefaults.swift

The resultant file would just be a file called readdefaults that no longer requires the use of .swift when calling (although unless added to a location in a path for the shell would still require the full file path. While swiftc is easy enough to use for simple one file scripts, I personally still use Xcode to do most of the work for larger projects – which is way easier than converting each .swift file to object code and then linking them. The most basic tasks of an admin include automating a task, finding or flipping a bit of information in those property lists (a task often best left to an MDM now, but check out https://medium.com/cracking-swift/parsing-remote-xml-using-swift-bfa9701fff84 for more on doing so within swift). Having said this, everyone is more savvy than they were when the shells were evolving. People like Brian Fox and Bill Joy helped add to what those shells could be used to script by teams of admins charged with managing larger and larger groups of Unix machines, who wrote vast scripts to help with their jobs. Apple has done a great job at building out MDM into a robust framework to manage devices, but the admins with sprawling fleets of machines to manage, while more technically advanced with every year, can benefit greatly from more accessible languages like the shell scripting environments than from having to script in much more complicated languages. Having said that, understanding object oriented programming is a skill anyone on the planet can benefit from – and everything that can be done with a shell can be done more efficient with swift.