Including a shell escape character in a Swift string - swift

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.

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"]

Backslash in string from standard input, no way to stripe it

I have a string that comes from the standard input in Xcode, using Swift. When I type in my string, I write something like this :
He told me "Hello maaan"
What I see when I print my message after putting a breakpoint is this :
"He told me \"Hello maaan""
Now, the quotes at the start and the end can be ignored, but what's that backslash? If I run this code in the console
po message.contains("\\")
it returns me false. The thing is that I need to run a regex on that string and the regex fails because of that \ char. What's the solution?
EDIT
If I transform my string to a NSString, the console prints this :
He told me "Hello maaan
In order for the compiler to ignore the quotation marks you'll need to add the backslashes before both.
So, you'll need to format your string as so:
var string = " He told me: \"Hello Man\"."
print(string)

Swift Arguments in task.arguments

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.

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.

specman issue with parsing quotation marks

I am trying to import a string from the unix shell to the program space of specman.
The string i want to import contains quotation marks ("") - for example "hi".
in these cases, the string is not parsed properly . for example
suppose i want to 'echo' some string with quotation marks, i would do the following:
%> echo echo \"\"hi\"\"
will output
""hi""
but if i use the following program, written in e:
<'
extend sys {
run() is also{
print output_from("echo \"\"hi\"\"");
stop_run();
};
};
'>
i get the following output:
output_from("echo \"\"hi\"\"") =
0. "hi"
as you can see - quotation marks are gone. the ones that we see here are coming from the default printing of list values.
I'm not familiar with the output_from action, but I assume it treats the input string as a shell command.
By writing "echo \"\"hi\"\"" what you will essentially get is a string containing echo ""hi"". This is because the \ will be "eaten up" (it's an escape character in e as well). The resulting string is what will be executed, which if you try in the shell will also output the same thing. Try adding an escaped \ as well. I don't have the possibility to start Specman anytime soon so you'll have to try it out.
To test my hypothesis:
// just to see what happens with your original string
var some_string : string = "echo \"\"hi\"\"";
print some_string; // should output echo ""hi""
To try out my solution do something like this:
// might need to fiddle with the escaping here
var some_other_string : string = "echo \\\"\\\"hi\\\"\\\"";
print some_other_string; // should output echo \"\"hi\"\"
You're passing your string through multiple string interpreters. First Specman's, then your shell's string interpreter.
You can debug getting your string through Specman's interpreter first by printing out command you want to pass to the shell first
message(None,"echo [...]")`
Once the printed command looks like it would when you execute it on the shell, then it is ready to be put into output_from command. You can build up the shell command using normal Specman string manipulation functions.