Relative file URL and Swift scripts

I'm trying to duplicate the functionality of this very straightforward Python code:

rom = bytearray([0xea] * 32768)

with open("rom.bin", "wb") as out_file:
    out_file.write(rom);

The best I could come up with was:

#!/usr/bin/swift

import Foundation


let rom = [UInt8](repeating: 0xea, count: 32768)
do
{
	try Data(rom).write(to: URL(fileURLWithPath: "rom.bin"))
}

catch let e
{
	print("Error writing rom.bin: \(e)")
}

Which doesn’t actually work because you can't make a URL with a relative path like that (I wanted it to be relative to the current working directory). Is there any way to easily reference a file relative to the pwd?

It’s also pretty slow compared to the equivalent Python (when executed as ./makerom.swift), presumably because it’s compiling it; subsequent invocations are faster.

Where does Swift put the built artifacts when invoking a Swift script like this?

Use FileManager.currentDirectoryPath and edit it from there.

Yeah, I saw that, but it's so clunky. I guess that’s what I’ll have to settle for. Still, the best I can do is:

#!/usr/bin/swift

import Foundation

let rom = [UInt8](repeating: 0xea, count: 32768)
try? Data(rom).write(to: URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("rom.bin"))

Oh well.

It’s also hard to work on this in an Xcode Playground because it doesn't like the shebang, and it makes the cwd /, rather than the Playground’s dir or the user’s home dir.

#! /usr/bin/swift
import Foundation

let d = Data(repeating: 0xea, count: 300)
try d.write(to: URL(fileURLWithPath:
  FileManager().currentDirectoryPath).appendingPathComponent("rom.bin")
)

No need to make the intermediate array. It's still kinda clunky, though.

1 Like

I do, I think, because I want to do some additional work to it (can I treat Data as an array of Uint8?):

#!/usr/bin/swift

import Foundation

let code: [UInt8] =
[
	0xa9, 0xff,				//	lda	#$f2
	0x89, 0x00, 0x60,		//	sta $6000
]

var rom = code + [UInt8](repeating: 0xea, count: 32768 - code.count)

rom[0x7ffc] = 0x00
rom[0x7ffd] = 0x80

try? Data(rom).write(to: URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("rom.bin"))

I’m not sure what your use case is, but in cases where the target location will be consistent relative to the script source, sometimes this is more succinct:

URL(fileURLWithPath: #file)

Yes. AFAIK Data supports all the same APIs that Array does (i.e. a contiguous RandomAccessCollection).

The only difference is that it is its own slice-type (due to what I personally believe are serious design mistakes - no type in the standard library does this), so you sometimes need to be careful with indexing if you pass them around.

1 Like

Which doesn’t actually work because you can’t make a URL with a
relative path like that (I wanted it to be relative to the current
working directory).

Hmmm. Either I’m radically misunderstanding your requirements or you’re really off in the weeds. URL fully supports relative URLs, and that includes current-working-directory-relative file URLs constructed from relative file paths. Your code works as is:

$ cat makerom.swift 
#!/usr/bin/swift

import Foundation

let rom = [UInt8](repeating: 0xea, count: 32768)
do
{
    try Data(rom).write(to: URL(fileURLWithPath: "rom.bin"))
}
catch let e
{
    print("Error writing rom.bin: \(e)")
}

$ chmod +x makerom.swift 
$ ./makerom.swift 
$ ls -l rom.bin 
-rw-r--r--  1 quinn  staff  32768  5 Nov 08:54 rom.bin

Now, just to make sure it’s not relative to the script, let’s change directories:

$ mkdir tmp
$ cd tmp
$ ../makerom.swift 
$ ls -l rom.bin 
-rw-r--r--  1 quinn  staff  32768  5 Nov 08:55 rom.bin

This is on macOS 10.14.6 with Xcode 11.2, but it should behave the same in any version of Swift.


If you want to construct a file URL that’s explicitly relative to some directory (rather than the current working directory), you can use init(fileURLWithPath:relativeTo:).


One thing to watch out for here is that relative URLs ‘latch’ the current working directory in their baseURL property, so if you change working directory the URL will remain relative to the original one:

let u1 = URL(fileURLWithPath: "rom.bin")
print(u1.absoluteURL)   // prints "file:///Users/quinn/rom.bin"
FileManager.default.changeCurrentDirectoryPath("/Library")
print(u1.absoluteURL)   // still prints "file:///Users/quinn/rom.bin"
let u2 = URL(fileURLWithPath: "rom.bin")
print(u2.absoluteURL)   // prints "file:///Library/rom.bin"

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

6 Likes

I bet this is exactly what's happening. I first wrote this as a Playground, not realizing Xcode sets the pwd to /, which is not what I would have expected, figuring the user home dir or the Playground dir would've been more likely. That's why I assumed URL misinterpreting the relative path (filename) I gave it. In retrospect, I think it was, in fact, behaving as you say.

1 Like
Terms of Service

Privacy Policy

Cookie Policy