Let's Build A 'cat' In Swift 2
As a homework in one of the early college classes, I was asked to write unix
commands such as
cat in C. Let's do that in Swift today! To make things
interesting, let's pretend we are on Linux. That means no Xcode nor Foundation
can be used.
It's hard to find a simpler unix program than
cat: It takes a list of file
names from the shell and write the content of each file to
stdout. When no
argument is given, it uses
stdin as the source of its output.
Writing it in C is trivial. Swift has exellent support for leveraging C. But to call even the standard C functions, we need to import them first.
swiftc command can compile a pure Swift source file like this:
swiftc cat.swift -o cat
We can add Objective-C bridging headers with the argument
-import-objc-header. But to import the standard C functions, we also need
to specify path to an SDK:
swiftc -sdk $(xcrun --show-sdk-path --sdk macosx)\ -import-objc-header bridge.h\ cat.swift\ -o cat
Instead of typing/copying that command, save this
Makefile to the same
SDKPATH = $(shell xcrun --show-sdk-path --sdk macosx) CBRIDGEHEADER = bridge.h TARGETS := cat .PHONY : all $(TARGETS) all: $(TARGETS) $(TARGETS): swiftc -sdk $(SDKPATH) [email protected] -import-objc-header $(CBRIDGEHEADER) -o $@
make cat should take care of the compilation.
Since file I/O is the only concern, we'll need C APIs from
bridge.h is a one liner:
The standard C function for opening a file is
FILE * fopen ( const char *filename, const char *mode );
Hmmmm, how do we deal with all those pesky '*'s?
To reference a certain C
Type in Swift, we can use
UnsafeMutablePointer<Type>. To make our lives easier, Swift
automatically bridge to
const char *. In other words, we can treat the
fopen as if it's the following:
func fopen( filename: String, mode: String ) -> UnsafeMutablePointer<FILE>
A character in C is represented by a byte in memory. Therefore Swift sees
char as of type
Int8 (8-bit integer). So a
char * would be referenced
UnsafeMutablePointer<Int8> in Swift. So
getline, a function from POSIX
ssize_t getline( char **lineptr, size_t *n, FILE *stream );
would look like this in Swift:
func getline( inout lineptr: UnsafeMutablePointer<Int8>, inout n: UInt, stream: UnsafeMutablePointer<FILE> ) -> Int
It returns the number if characters it finds.
We now can open a file, read and print its content line by line, and close it with:
func fclose(stream: UnsafeMutablePointer<FILE>)
Repeat this on each file specified in
Process.arguments, or simply read from
stdin, and we have a
cat! Here's a screenshot of it displaying its own
The code is also available in this gist.