Get path to Swift script from within script - swift

I'm writing a script in Swift, and I want it to modify some files that always exist in the same directory as the script itself. Is there a way to get the path to the script from within itself? I tried:
print(Process.arguments)
But that outputs only the path that was actually given to the script, which may be the fully resolved path, just the file name, or anything in between.
I'm intending the script to be run with swift /path/to/my/script.swift.

The accepted answer doesn't work in Swift 3. Also, this is a more straightforward approach:
import Foundation
let currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
let url = URL(fileURLWithPath: CommandLine.arguments[1], relativeTo: currentDirectoryURL)
print("script at: " + url.path)
However, it has the same problem pointed out by #rob-napier, if the script's directory is in your PATH.

just in swift:
let cwd = FileManager.default.currentDirectoryPath
print("script run from:\n" + cwd)
let script = CommandLine.arguments[0];
print("\n\nfilepath given to script:\n" + script)
//get script working dir
if script.hasPrefix("/") { //absolute
let path = (script as NSString).deletingLastPathComponent
print("\n\nscript at:\n" + path)
} else {
let urlCwd = URL(fileURLWithPath: cwd)
if let path = URL(string: script, relativeTo: urlCwd)?.path {
let path = (path as NSString).deletingLastPathComponent
print("\n\nscript at:\n" + path)
}
}

Related

Path part of "filename.m4v" is "/private/tmp", which is unexpected

I'm writing some Swift code to parse filenames of video files and extract the show, season and episode. These are returned as key/value parts in a dictionary. As part of my unit tests, I found something odd. First the code (comments and whitespace removed):
public static func parse(_ filename: String, defaults: [String: String] = [String: String]()) -> [String: String] {
let url = URL(fileURLWithPath: filename)
let file = url.deletingPathExtension().lastPathComponent
if file.count == 0 {
return ret
}
if file.count > 0 {
ret["file"] = file
}
let ext = url.pathExtension
if ext.count > 0 {
ret["extension"] = ext
}
let path = url.deletingLastPathComponent().path
if path.count > 0 {
ret["path"] = path
}
I called this in my test case thus...
ParseVideoFilename.parse("non-empty-filename.m4v")
And this is what resulted:
["ext": "m4v", "file": "non-empty-filename", "path": "/private/tmp"]
I am a bit surprised about that path. I did not pass that in, so I assume URL is doing something here. I don't expand the path nor resolve it. Is this expected behavior, and if so, why?
Running your code in a playground just gave me file:///private/var/folders/1p/wpwdypm96_s5zfwxxzvwwp0m0000gn/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Playground-C18F0418-5C1D-4772-9AE9-E3EF9AA2F07C/non-empty-filename.m4v as the output of
let url = URL(fileURLWithPath: filename)
print(url.absoluteString)
This looks to me as the current directory of the process that gets executed by Playground. I'm not at all surprised by this, since we gave URL a relative path without any base and the file URI scheme doesn't really know how to handle relative paths (see e.g. this answer)
If you absolutely don't want to see these artifacts, you can modify your parser to use an absolute path:
let url = URL(fileURLWithPath: "/" + filename)
print(url.absoluteString) // prints "file:///non-empty-filename.m4v"

Access root file created by script Swift

From this question: How can I get all image names in asset catalog group?
I want to access a file created in the root of an App in Swift.
My App is called Challenge and my script is
# Type a script or drag a script file from your workspace to insert its path.
#!/bin/sh
>./Challenge/fileNames.txt
for FILE in ./Challenge/Images.xcassets/actions/*; do
echo $FILE >> ./Challenge/fileNames.txt
done
I have tried
let fileManager = FileManager.default
let imagePath = Bundle.main.resourcePath! + "fileNames.txt"
let imageNames = try! fileManager.contentsOfDirectory(atPath: imagePath)
And many other combinations .
This is not homework, I have tried various solutions but as I am not sure where the file is within the file system (how the root can be accessed within the App) I seem unable to locate the file.
Question: How to read ./Challenge/fileNames.txt from an App called challenger, created by a script during the build phase.
The file is located in the main bundle folder, not resource path:
let fileManager = FileManager.default
let imagePath = Bundle.main.path(forResource: "fileName", ofType: "txt")
let content = try! String(contentsOfFile: imagePath)

local file path won't import

I am trying to import the content of a .txt file to a string in my Xcode 9 project using Swift 4. When I use the full path name it imports successfully, current code:
let filePath = URL(fileURLWithPath: "/Users/main/Documents/ClaasPDI/PDIapp/PDIapp/holdMachines.txt")
do
{
machineString = try String(contentsOf: filePath)
}
catch
{
print("MACHINE INFORMATION DID NOT IMPORT")
}
I want to be able to import the data from the local path so it can be run on other computers besides mine. My swift files and holdMachines.txt are in the same folder PDIapp but when I change the code to:
let filePath = URL(fileURLWithPath: "holdMachines.txt")
it now crashes my app and says it could not access the file.
I also tried it with a / infront of the file name (below) but that also failed.
let filePath = URL(fileURLWithPath: "/holdMachines.txt")
How can I change my code to access the file through a local file path?
Put the text file in your Xcode project and use the following code to read it into a string:
let txtFile = Bundle.main.path(forResource: "holdMachines", ofType: "txt")
do {
let contents = try? String(contentsOfFile: txtFile!, encoding: .utf8)
} catch let err {
print(err.localizedDescription)
}

Get path of a file in a data set located in Assets.xcassets

I have a data set of audio files in my Assets.xcassets:
I'm trying to get the path of one of those audio files like this:
let path: String = Bundle.main.path(forResource: "acoustic_grand_piano/A4", ofType: "f32")!
But I get a EXC_BAD_INSTRUCTION. I tried to look on the internet but I don't find anything on Data Sets.
How can I get the content of one of these files?
Thanks!
Try this:
Manually put your files into a folder, named anything you want.
Append ".bundle" to the folder to create a bundle. You'll get a warning, accept it. Congrats, you've just created your first bundle! :-)
Manually drag that folder into your app.
Get at your files by using the following code....
public func returnFile(_ named:String) -> String {
let path: String = Bundle.main.path(forResource: "myAudioFiles", ofType: "bundle")! + "/" + name + ".f32"
do {
return try String(contentsOfFile: path)
}
catch let error as NSError {
return error.description
}
}
Now, my files are text files of CIKernel code. Since your's are audio files you may need to change the String return to something else.
EDIT:
In my case I'm using a framework, as I wish to share these files/images with extensions and other apps. If you are working in such a set up, here's the unaltered code:
public func returnFile(_ resource:String, _ fileName:String, _ fileType:String) -> String {
let identifier = "com.companyname.appname" // replace with framework bundle identifier
let fileBundle = Bundle.init(identifier: identifier)
let filePath = (fileBundle?.path(forResource: resource, ofType: "bundle"))! + "/" + fileName + "." + fileType
do {
return try String(contentsOfFile: filePath)
}
catch let error as NSError {
return error.description
}
}

Where do I get the OSX volume for replacing "/" [duplicate]

When using
let directoryEnumerator = FileManager().enumerator(at: ...
in Swift 3, I get all files from the folder, e.g.
"file:///Volumes/MacOS/fasttemp/Fotos/"
The results are not including the leading path (here "/Volumes/MacOS"). So I get
"file:///fasttemp/Fotos/2005/"
How can I get the fullpath (directly from the enumerator) or convert them. I want to use URL functions, not string function manipulating by assumptions.
If "MacOS" is the name of your current startup disk then "/Volumes/MacOS" is a symbolic link to "/", so both "/fasttemp/Fotos/2005/" and "/Volumes/MacOS/fasttemp/Fotos/" are absolute paths to the same file.
In order to get a unique file name representation you can query
a URL for its canonical path. Example:
let url = URL(fileURLWithPath: "/Volumes/MacOS/Applications/Utilities/")
if let cp = (try? url.resourceValues(forKeys: [.canonicalPathKey]))?.canonicalPath {
print(cp)
}
// Output: "/Applications/Utilities"
This requires macOS 10.12/iOS 10 or later. On older systems you can
use the realpath() system call:
if let rp = url.withUnsafeFileSystemRepresentation ({ realpath($0, nil) }) {
let fullUrl = URL(fileURLWithFileSystemRepresentation: rp, isDirectory: true, relativeTo: nil)
free(rp)
print(fullUrl.path)
}
// Output: "/Applications/Utilities"
Note that you want to use URL wherever possible, from the NSURL documentation:
URL objects are the preferred way to refer to local files. Most
objects that read data from or write data to a file have methods that
accept an NSURL object instead of a pathname as the file reference.
Here’s an example of how to get all the objects from a directory:
import Foundation
let manager = FileManager.default
// Get URL for the current user’s Documents directory
// Use URL instead of path, it’s more flexible and preferred
if let documents = manager.urls(for: .documentDirectory, in: .userDomainMask).first,
// Get an Enumerator for the paths of all the objects in the directory
// but do not descend into directories or packages
let directoryEnumerator = manager.enumerator(at: documents, includingPropertiesForKeys: [URLResourceKey.pathKey], options: [.skipsSubdirectoryDescendants, .skipsPackageDescendants]) {
// iterate through the objects (files, directories, etc.) in the directory
for path in directoryEnumerator {
print(path)
}
}