Swift Arguments in task.arguments - swift

In Swift 4, I try to launch a command line to know the frame rate of a video. I use mediainfo tool.
The command to execute is (tested in Terminal)
"/Users/Lorenzo/mediainfo --Inform="Video;%FrameRate%" /Users/Lorenzo/Desktop/1.mov"
And my swift code for that purpose is:
let taskfindfps = Process()
taskfindfps.launchPath = "/Users/Lorenzo/mediainfo"
taskfindfps.arguments = ["--Inform=\"Video;%FrameRate%\"", myVideo]
let pipefindfps = Pipe()
taskfindfps.standardOutput = pipefindfps
But the first argument isn't valid, and I don't know why...
The result I have is like the result of the command without the optional argument:
"/Users/Lorenzo/mediainfo /Users/Lorenzo/Desktop/1.mov"
Is there something wrong in "--Inform=\"Video;%FrameRate%\"" ?

Without knowing Swift exactly, I would try without escaped quotes, quotes are used on e.g. command line only for forcing the command to not handle the semi column as something for the command line (the command line removes them during processing. Process.arguments being a list, the language is expected to handle correctly itself characters to escape, and if it escapes quotes (instead of processing them as it is done on the command line) MediaInfo will not understand the command.
Jérôme, developer of MediaInfo.

You are right, I have my fps number!
With the code:
taskfindfps.arguments = [ "--Inform=Video;%FrameRate%", myVideo]
Thank you for your help.
And thanks for the powerful MediaInfo tool.

Related

Call Process with arguments in Swift macOS App

I'm trying to run a Process from my Swift macOS app. But, I'm having trouble formatting the arguments to my Process.
So far, the following code is working well:
let task = Process()
var environment = ProcessInfo.processInfo.environment
environment["PATH"] = "/usr/local/bin"
task.environment = environment
task.currentDirectoryPath = "/usr/local/bin"
task.executableURL = URL(fileURLWithPath: "/usr/local/bin/convert")
task.arguments = [filename, filename+".pdf"]
let outputPipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = outputPipe
task.standardError = errorPipe
...
try! task.run()
When I try and add the arguments "-trim", "-fuzz 10%" to the existing task.arguments the program crashes. The process error is:
New error: convert: unrecognized option `-fuzz 10%' # error/convert.c/ConvertImageCommand/1718.
How do I properly format these arguments to run the process?
When you run commands on the command line, your shell typically splits arguments to pass to a program by whitespace — when you run a command like
/usr/local/bin/convert -trim -fuzz 10% <input> <output>
the shell will split those arguments and pass them along to convert like so:
["/usr/local/bin/convert", "-trim", "-fuzz", "10%", "<input>", "<output>"]
When convert goes to parse the arguments you pass in, it iterates over those arguments and attempts to match them against a known list that it accepts. It looks up -trim, which it recognizes, and -fuzz, which it recognizes, and it accepts 10% as the argument to -fuzz... and so on.
When you execute a program directly from your code, whether through Process, or the lower level execve or posix_spawn, the arguments that you pass in are forwarded directly to the receiving process. In your original case, this means that convert received:
["/usr/local/bin/convert", "-trim", "-fuzz 10%", "<input>", "<output>"]
convert, and many other programs, don't further split up arguments on whitespace themselves, so when it tried to match the entirety of -fuzz 10% as a single command line option (space and all), it couldn't find anything.
This is equivalent to running
/usr/local/bin/convert -trim "-fuzz 10%" <input> <output>
on the command line, where the quotes prevent your shell from splitting words on whitespace. When I run that command locally on my machine, I get the exact same error:
convert: unrecognized option `-fuzz 10%' # error/convert.c/ConvertImageCommand/1718.
The solution is to do the same splitting your shell might do, and pass in each argument separately as its own string:
task.arguments = ["-trim", "-fuzz", "10%", filename, filename+".pdf"]

Including a shell escape character in a Swift string

The goal is to be able to launch my Command Line Tool and have it automatically resize the terminal window. I found this code to help with that:
#discardableResult
func shell(_ args: String...) -> Int32 {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
You can use it like this:
shell("ls")
Which will make the terminal call the 'ls' command as soon as it runs
Commands like this work great, but the command I need to run is (which will resize the terminal window)
printf '\e[8;50;100t'
But if I do
shell("printf", "'\e[8;50;100t'")
I get an error saying 'Invalid escape sequence in literal'. I understand why it is giving this, but I don't know how to work around it. I've tried adding an extra backslash but then it won't actually execute the command, it will just print 'e[8;50;100t' to the terminal.
How can I work around this issue?
The problem is that you're not actually running a shell, so the escaped form \e is not being interpreted as the single character that you want. You need to directly include the correct character in the string. One way to do that is with a Unicode escape (see the "Special Characters" heading there): \u{XX}, where XX is the hexadecimal for the code point you want.
The \e character is originally from ASCII, and its hex value is 1B. Therefore:
shell("printf", "'\u{1B}[8;50;100t'")
will pass through the string that you need.

Xcode breakpoint shell command argument length

Trying to pass a large string to a shell script using a breakpoint in Xcode
let value = Array(repeating: "a", count: 1500).joined()
let string = "{\"key\": \"\(value)\"}"
Unfortunately, the string is being truncated. Is this limitation documented and can it be overcome?
It's been nearly a year since you asked this, and I'm not sure if it will solve your question, but I've recently had a similar problem so thought I'd share my solution.
I had two issues:
LLDB was truncating any arguments to my shell script (and string variables printed in the console using po foo) to 1023 characters. I believe this is the issue to which your question relates.
Xcode was incorrectly confusing a comma , in my string as a separator for multiple arguments (e.g. passing foo, bar, and baz as arguments to the script wouldn't work correctly if any of the variables contained a , as Xcode would try to create another argument).
So, firstly, the LLDB issue...
It seems that by default LLDB has a limit on the character length that it will print to the console (or pass to a shell script via a breakpoint argument) of around 1023 characters. You can easily change this to something larger by setting another breakpoint before the breakpoint that uses your variable and running (lldb) set set target.max-string-summary-length 10000 in the console. This can be a bit annoying so I created a ~/.lldbinit file and placed set set target.max-string-summary-length 10000 in there instead so I don't have to keep setting it in the console.
Secondly, the comma issue...
Inside the Edit breakpoint... menu that you provided a screenshot of above there is the option to not only provide a path to a script but to also provide arguments. I can see from your question that you provided the argument #string#. For my script, I was passing multiple arguments, which Xcode allows you to do using a comma separated list, e.g. #foo#, #bar#, #baz#. Each of these arguments was a string.
I noticed that sometimes one or more of these strings would truncate if they contained a comma: ,.
So the string:
{ "num_friends" : "7" }
would be passed to my script as expected. But the string:
{ "num_friends" : "7", "num_deleted_friends" : "1" }
would truncate and would be passed to my script as two separate arguments. It seems that Xcode would split any string with a , even when entered using #string#.
I validated this in my script by simply using something like:
for var in "$#"
do
echo "$var"
echo "===="
done
Where $# expands to contain each argument. From this I could see that #string# was being correctly passed to my script but separated as multiple arguments wherever there was a ,. So if #string# contained a comma my script would print:
#"{ \"num_friends\" : \"7\"
====
\"num_deleted_friends\" : \"1\" }"
instead of what I expected which was:
#"{ \"num_friends\" : \"7\", \"num_deleted_friends\" : \"1\" }"
So it seems like it might be a bug in how Xcode passes strings inside # expressions in the breakpoint editor window.
My crude solution has been to just replace any commas with another character and then replace them back again inside my script. There's probably a better way to do this but I don't require it for my needs.

External program called with backticks still produces output

so I call an external program in perl and want to capture it's output:
my #RNAalifoldOut = `RNAalifold some parameters`;
If called from command line the output consists of three lines, e.g:
4 sequences; length of alignment 48.
__GCCGA_UGUAGCUCAGUUGGG_AGAGCGCCAGACUGAAAAUCAGA
...((((.....((((.........)))).(((((.......)))))
However my array #RNAalifoldOut contains only the two last lines and the first line appears directly on the screen when the line is being executed.
How can this be? I thought maybe the program writes the first line to STDERR, but isn't that discarded by the backticks operator? And what could I do to hide this output?
Regards
Nick
You are likely seeing the standard error from RNAalifold. Backticks capture only the standard output.
Capture both standard output and standard error by changing your code to
my #RNAalifoldOut = `RNAalifold some parameters 2>&1`;
To discard the standard error, use
my #RNAalifoldOut = `RNAalifold some parameters 2>/dev/null`;
on Unix-like platforms. On Windows, use
my #RNAalifoldOut = `RNAalifold some parameters 2>nul`;

run matlab and pass integer parameter from command line

I have a matlab function and I was able to run it from command line. Now I want to pass a parameter to the file from command line. The parameter is a integer. It seems when I pass from command line, it is always taken as a "char".
Here is how I run the command
matlab -nodesktop -nosplash -r "mycommand 3"
For example, if I have a function as
function [ ] = mycommand( a )
a = a+3;
disp(a)
end
it prints 54 instead of 6.
Is there a way to work around this? I don't want to check the type of the variable in my code.
Thanks
You need to execute the function as you would in the matlab interpreter:
matlab -nodesktop -nosplash -r "mycommand(3)"
(Notice the parenthesis around the 3)
MarkD gave a good answer. Although you mentioned you might be unhappy doing this (I'm sure for good reasons), another option would be to put a little extra code in to the beginning of your function, which would convert character inputs to numerical if the command were called via matlab -r:
if ischar(a)
a = str2num(a);
end