I have a Swift program that reads from FileHandle.standardInput
(in Objective-C, this would be +[NSFileHandle fileHandleWithStandardInput]
. It should terminate reading when it hits end-of-file on the input stream, but when I run it using the Terminal (on macOS Sierra) as input, it doesn't detect end-of-file when I hit Ctrl+D.
Here is a simplified example of what I'm doing. This program simply reads from standard input and writes what it has read to standard output:
#!/usr/bin/swift
import Foundation
let input = FileHandle.standardInput
let output = FileHandle.standardOutput
let bufferSize = 1024
var data = input.readData(ofLength: bufferSize)
while data.count > 0 {
output.write(data)
data = output.readData(ofLength: bufferSize)
}
I expect readData(ofLength:)
to return a Data
object with a count
of zero when it reaches end of file.
When I run with a file redirected to standard input, like this:
./echo.swift < foo.txt
It writes out the file and terminates.
However, if I run it like this:
./echo.swift
and then type some text and hit Ctrl+D, I expect it to treat the Ctrl+D as end-of-file and terminate. But it doesn't do that. It just keeps running and echoing lines. It will eventually terminate if I hit Ctrl+D over and over, but that's not what I want.
Changing the bufferSize
doesn't seem to help. I get the same behavior if I set it to 1
.
I suspect I need to set some sort of buffering parameter on the stdin
file descriptor, or the terminal device, or catch a signal, or something, but I don't know what.
I know that I could use the C stdio fread()
API instead, which properly detects an end-of-file condition from the terminal, or I could use Swift's readLine(_:)
to read from standard input without worrying about file handles/descriptors, but I want to know if there is a way to do this with FileHandle
or the raw file descriptor without re-implementing C stdio.
Update: After spending an hour reviewing Apple's LibC source, I concluded "It's complicated" and so now I'm just using fread(..., stdin)
in my program. But I'd still like to know if there is some simple way to get this to work with FileHandle
.
Today, Swift 5, something like this ...
while (FileHandle.standardInput.availableData.count > 0) {
FileHandle.standardOutput.write(FileHandle.standardInput.availableData)
}
Simple and does work almost like a cat program, some stuff to tune for that.
Better with correct behaviour :
var data: Data
repeat {
data = FileHandle.standardInput.availableData
FileHandle.standardOutput.write(data)
} while (data.count > 0)
The doc about it :
The data currently available through the receiver, up to the the maximum size that can be represented by an NSData object.
If the receiver is a file, this method returns the data obtained by reading the file from the current file pointer to the end of the file. If the receiver is a communications channel, this method reads up to a buffer of data and returns it; if no data is available, the method blocks. Returns an empty data object if the end of file is reached. This method raises NSFileHandleOperationException if attempts to determine the file-handle type fail or if attempts to read from the file or channel fail