Given a gzip-compressed file, the shell command
gzip -l returns various information about it, including the name of the uncompressed file. I needed to read that name from the output of gzip -l.
The output looks like this.
compressed uncompressed ratio uncompressed_name 115815 136227 15.0% Foo.jpg.gz
The lines are separated by return characters, and the formatting of columns is done using multiple spaces. Splitting the output into lines is easy. of course.
dim outputLines() as String = Split(theShell.Result, Encodings.UTF8.Chr(13))
The string I want is on the second line. As Ken Thompson famously stated, "when in doubt, use brute force";. I apply his advice here.
dim secondLine() as String = Split(outputLines(1), " ")
This array contains a bunch of empty strings corresponding to the extra spaces. So here is a function to remove them.
Function RemoveBlanks(s() as String) As String() dim outputList() as String for i as Integer = 0 to UBound(s) if s(i) <> "" then outputList.Append s(i) end if next return outputList End Function
Before I do that, I would like to trim the elements in the array to also eliminate any other all-whitespace lines.
Function Trim(s() as String) As String() dim outputList() as String for i as Integer = 0 to UBound(s) outputList.Append Trim(s(i)) next return outputList End Function
Using these functions, my code now looks like
dim secondLine() as String = RemoveBlanks(Trim(Split(outputLines(1), " ")))
Now I can reasonably expect to find the uncompressed file name as the last element of the array secondLine.
Parsing the output of gzip is made quite simple by the use of the two auxiliary functions Trim and RemoveBlanks. Each of these functions is of course more generally useful. And we can make them yet more general.
Let's take a look at Trim first. What this function does is to apply the built-in function Trim to every element of a string array. Replace Trim by an arbtrary function of the same signature, and we have something quite general.
Delegate Function StringMapDelegate(s as String) As String Function Map(s() as String, f as StringMapDelegate) as String() dim outputList() as String for i as Integer = 0 to UBound(s) outputList.Append f.Invoke(s(i)) next return outputList End Function
We can similarly decompose RemoveBlanks.
Delegate Function StringFilterDelegate(s as String) As Boolean :::xojo Function Filter(s() as String, f as StringFilterDelegate) As String() dim outputList() as String for i as Integer = 0 to UBound(s) if f.Invoke(s(i)) then outputList.Append s(i) end if next return outputList End Function
Using these functions Map and Filter, I could write my code as follows.
Function IsNotEmpty(s as String) as Boolean return s <> "" End Function
dim secondLine() as String = Filter(Map(Split(outputLines(1), " "), AddressOf Trim), AddressOf IsNotEmpty)
Not long after you decide that Filter is a very useful addition to your toolkit, you will ask questions like "how do I pass two conditions to Filter"; or "how do I pass the negation of a condition?". It turns out this requires a bit more work to do in a clean way. I will post some answers in the future.