Handling special characters in Swift - swift

I'm developing a Cocoa App in Swift, but I'm stuck trying to do something that must be easy to do but somehow I'm not getting the result I expect.
In my application, I'm trying to hash a word to MD5. Everything works fine when I skip special characters. When I try to hash a word with special characters, the resultant hash doesn't correspond to the correct hash.
Here's my code.
import Cocoa
let task = NSTask()
let pipe = NSPipe()
task.standardOutput = pipe
task.launchPath = "/bin/bash"
task.arguments = ["-c", "echo -n 'Canción' | md5"]
let file:NSFileHandle = pipe.fileHandleForReading
task.launch()
task.waitUntilExit()
let data = file.readDataToEndOfFile()
let datastring = NSString(data: data, encoding: NSUTF8StringEncoding) as String!
print("Hash = \(datastring)")
If you run this code in a Playground, you'll get this hash:
Hash = 770f92a03e9c82af426549941a9df70b
But, if you run the next command directly in your OS X terminal, you'll get the correct hash, which is Totally different to the first one.
$ echo -n 'Canción' | md5
This is the terminal response:
3abf536a5262c902a30d74f68f38067b
It is clear that I'm missing something important, but I can't find what's in my code.
Anyone?
Please... help! 😅

Related

Running shell commands in a swift script

I am looking for a solutuion to run shell commands in a Swift script.
Here is my code:
func shellEnv(_ command: String) -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
It works with built-in commands but it cannot deal with commands like "brew", "node" and other commands that were installed manually.
So how can I solve it?
You need to set the PATH environment variable for the task. This has been set in your terminal, which is why you are able to do brew and node and other cool things directly, without specifying their full path.
You can see what this is set to in your terminal by doing:
echo $PATH
and it will print something like (for me it has a bunch more things. This is only an excerpt):
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
If you copy and paste the entire output of echo $PATH, and put it into the environment property of task, then you will be able to use the same commands in your swift script as in your terminal.
task.environment = ["PATH": "<paste the output here>"]
As Alexander said in the comments, another way is to add the l option. Here is a MCVE:
#!/usr/bin/swift
// main.swift
import AppKit
func shellEnv(_ command: String) -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-cl", command]
task.launchPath = "/bin/zsh"
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
print(shellEnv("brew list")) // assuming you have brew
To run, chmod +x main.swift then ./main.swift, and you will see all your homebrew packages listed.

Executing python from swift via bash: "can't open file...[Errno 1] Operation not permitted"

I am learning how to make apps for mac, and I am starting with an app to manage my many discord bots. Essentially, the goal is to have many switches to turn bots on and off, which requires executing the python files for the bots, written using discord.py. I read about a PythonKit module for swift, but when I tried to run a discord bot from the files using that, the build continuously failed, so I decided to use the bash shell to excecute the python. Here is my swift code for using bash shell commands:
func shell(_ command: String) -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}func shell(_ command: String) -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
I then called shell("/usr/local/bin/python3.9 path/to/file.py"), and made it print the output to the console. This was the output:
/usr/local/bin/python3.9: can't open file 'path/to/file.py': [Errno 1] Operation not permitted. I am running this from both AppCode and Xcode, and made sure to give both of those apps full disk access, as well as giving terminal full disk access to be sure. In addition, I tried running /usr/local/bin/python3.9 path/to/file.pyin my terminal, and it works fine. What is happening here? Why can swift not open this file in the bash shell while I can? What do I do to fix it? Let me know if you need more info(if you think PythonKit is the answer, I'll send the build error info from that to debug that process)
Thanks!
Yonatan Vainer was right, turns out sandboxing was on in the entitlements file, so I had to set it to off, which fixed it.

Get ffmpeg info from Pipe (stdout)

I want to call ffmpeg to get the duration of a video file. When using the command in OSX Terminal everything works fine:
ffmpeg -i MyVideo.MOV 2>&1 | grep "Duration"
I get this:
Duration: 00:01:23.53, start: 0.000000, bitrate: 39822 kb/s
It is perfect for me. But now I tried this call from within my code:
func shell(launchPath: String, arguments: [String]) -> String
{
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
do {
try task.run()
// task.launch() till 10.12, but now catchable!
} catch let error as NSError {
print(error.localizedDescription)
return ""
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
return output
}
This code works fine for all other external commands. But here I get error:
[NULL # 0x107808800] Unable to find a suitable output format for '2>&1'
2>&1: Invalid argument
I defined the arguments for ffmpeg like this:
let arguments = ["-i", video.path, "2>&1", "|", "grep \"Duration\"" ]
Even if I put them all in one argument as a larger string, it doesn't work. Using "pipe:1" instead of "2>&1" and rest of arguments results also in an error.
Any idea, how I get it to work?
2>&1 means everything in standardError(Output) goes into standardOutput.
Well, you don't really need that, yes?
Why not reading the standardError(Output) directly and do the grep yourself?
let pipe = Pipe()
task.standardError = pipe
... //task.run()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = String(data: data, encoding: .utf8)
By the way, let's avoid NSString and double re-casting into String.
Let's do the grep ourselves too.
let durationLine = output.components(separatedBy: .newlines).first(where: { $0.contains("Duration") })
Steps: Let's get an array of new lines, and find the first line with "Duration".

Bash command not found using swift

I created a swift function which runs command in bash which is :
func getConnectedDevices(lblOut: NSTextView)
{
let pipe = Pipe()
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["--login", "-c", "mobiledevice get_device_prop DeviceName"]
process.standardOutput = pipe
let fileHandle = pipe.fileHandleForReading
process.launch()
lblOut.string += "\n" + String(data: fileHandle.readDataToEndOfFile(), encoding: .utf8)!
}// Gets all connected iOS Devices
This function works if I just use mobiledevice in command but when I pass proper command to obtain list it gives me error that command not found. I am not very experienced in swift.
The issue resolved by itself. I don't really know how as I didn't change anything at all.

Run shell command with pattern in Swift

I would like to run a shell command on many files that should match on a given filename regex. I found this code snippet that runs a shell command with arguments:
func shell(_ arguments: [String] = []) -> String {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8) ?? "unknown"
return output
}
It runs great, but it does not resolve the parameters:
shell(["ls", "~/Desktop/*.txt"])
Does not resolve the * to all txt files, it tries to only work on a file called *.txt. Is there some option I need to set on Process?
Thanks in advance for your help!
I just found out the answer! The resolving of * and other patterns is done by the shell, Process only runs a given command. So the solution is to create a shell and run the command in there: (will do some clean up in the code, but this works)
shell(["bash", "-c", "ls ~/Desktop/*.txt"])