Saturday, August 13, 2022

#1 2021-05-29 03:12:18 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1406

NSTask and Standard Output

I'm working to learn NSTask, and I've gotten the basics to work, but I'm stuck on one point. How can I set a variable to standard output? I've been working with the following script just for test purposes.

BTW, the standard output will be text, a random example of which might be a command-line utility that returns the number of pages in a PDF. Thanks for the help.

Applescript:

use framework "Foundation"

set theTask to (current application's NSTask's new())
theTask's setLaunchPath:"/bin/echo"
theTask's setArguments:{"OK"}
theTask's launchAndReturnError:(missing value)
set theOutput to theTask's standardOutput()

theOutput --> «class ocid» id «data optr000000006060870300600000»


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#2 2021-05-29 05:57:54 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 6791

Re: NSTask and Standard Output


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#3 2021-05-29 07:22:36 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1406

Re: NSTask and Standard Output

Thanks Shane. I read through your script, which is at or beyond the boundary of my knowledge level, and it's clear that doing what I want is not going to happen right now. I'll use do shell script in those instances, which is fine.

In some cases, I only need to ascertain if a particular command completed successfully but even that doesn't seem to work. For example, the first of the following scripts succeeds and the second fails but they both return the same results.

Applescript:

set theFolder to POSIX path of (path to desktop as text) & "FolderDoesNotExist/NewFolder"
set theTask to (current application's NSTask's new())
theTask's setLaunchPath:"/bin/mkdir"
theTask's setArguments:{"-p", theFolder}
set {theResult, theError} to theTask's launchAndReturnError:(reference) --> {true, missing value}

Applescript:

set theFolder to POSIX path of (path to desktop as text) & "FolderDoesNotExist/NewFolder"
set theTask to (current application's NSTask's new())
theTask's setLaunchPath:"/bin/mkdir"
theTask's setArguments:{theFolder}
set {theResult, theError} to theTask's launchAndReturnError:(reference) --> {true, missing value}

Last edited by peavine (2021-05-29 10:09:19 pm)


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#4 2021-05-29 11:56:03 pm

Fredrik71
Member
Registered: 2019-10-23
Posts: 961

Re: NSTask and Standard Output

peavine wrote:

...but they both return the same results.


the reason is... NSTask only execute something and in your case both do that /bin/mkdir

In this case you will get a error from NSTask

Applescript:

set theFolder to POSIX path of (path to desktop as text) & "FolderDoesNotExist/NewFolder"
set theTask to (current application's NSTask's new())
theTask's setLaunchPath:"/bin/mkdir1"
theTask's setArguments:{theFolder}
set {theResult, theError} to theTask's launchAndReturnError:(reference)
if theResult as boolean is false then error (theError's localizedDescription() as text)

If you like to understand why the first example succeed and second one do not.
You need to look what mkdir does.

The manpage for the option -p say:
If this option is not specified, the full path prefix of each operand must already exist.
In your case that is false. And that's why your second example false but NSTask its true.

On other hand if you do this it will success

Applescript:

use framework "Foundation"
use scripting additions

set theFolder to POSIX path of (path to desktop as text) & "FolderDoesNotExist"
set theTask to (current application's NSTask's new())
theTask's setLaunchPath:"/bin/mkdir"
theTask's setArguments:{theFolder}
set {theResult, theError} to theTask's launchAndReturnError:(reference) --> {true, missing value}
if theResult as boolean is false then error (theError's localizedDescription() as text)

Last edited by Fredrik71 (2021-05-30 12:17:15 am)


if you are the expert, who will you call if its not your imagination.

Offline

 

#5 2021-05-30 01:36:51 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 6791

Re: NSTask and Standard Output

peavine wrote:

For example, the first of the following scripts succeeds and the second fails but they both return the same results.



The launchAndReturnError: method only returns an error if the task can't be launched.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#6 2021-05-30 07:15:57 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1406

Re: NSTask and Standard Output

Shane Stanley wrote:

The launchAndReturnError: method only returns an error if the task can't be launched.



Thanks Shane and Fredrik71 for the explanation, which makes perfect sense.

I spent some time studying Shane's script and carefully read the paragraphs that introduce the script, and I now have a pretty good idea of what's required to use NSTask in place of do shell script. Given the simple stuff I do, NSTask will not be as useful as I'd hoped, but I learned a lot, which is always good.


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#7 2021-05-30 09:16:17 am

Fredrik71
Member
Registered: 2019-10-23
Posts: 961

Re: NSTask and Standard Output

@peavine...
Its not a bad idea to build useful handlers in Script Library to use in your scripts.
And if you like them to be AppleScriptObjC that is also okey.

You could build record and dictionary for parameters for any  unix command.

ex.

Applescript:

use framework "Foundation"
use scripting additions

set thePath to POSIX path of (path to desktop) & "myFile.pdf"
set theRecord to {removeImagesFromTheFile:"-draft"}
set cpdfProperties to current application's NSDictionary's dictionaryWithDictionary:theRecord

set theParams to (cpdfProperties's valueForKey:"removeImagesFromTheFile") as text
return "/usr/local/bin/cpdf " & theParams & space & quoted form of thePath

Or the next thing for you could be to build UI for cpdf and that is not so difficult. smile

Last edited by Fredrik71 (2021-05-30 09:21:49 am)


if you are the expert, who will you call if its not your imagination.

Offline

 

#8 2021-05-30 11:50:03 am

Mark FX
Member
From:: UK
Registered: 2011-08-12
Posts: 163

Re: NSTask and Standard Output

@peavine...

I found this code snippet I wrote as an exercise 3 or 4 years ago, buried in my library of code snippets, that I kept as they might be useful in future projects.

Applescript:


use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

property myApp : a reference to current application

set shellTask to myApp's NSTask's new()
shellTask's setLaunchPath:"/bin/bash/"
shellTask's setArguments:{"-c", "whereis osascript"}

set shellOutputPipe to myApp's NSPipe's pipe()
set shellErrorPipe to myApp's NSPipe's pipe()

shellTask's setStandardOutput:shellOutputPipe
shellTask's setStandardError:shellErrorPipe

shellTask's |launch|()
shellTask's waitUntilExit()

if not (shellTask's isRunning() as boolean) then
   set shellTaskStatus to shellTask's terminationStatus() as integer
   if shellTaskStatus = 0 then -- Task Succeeded
       set shellTaskOutputHandle to shellOutputPipe's fileHandleForReading()
       set shellTaskOutputData to shellTaskOutputHandle's readDataToEndOfFile()
       set shellTaskOutputString to myApp's NSString's alloc()'s initWithData:shellTaskOutputData encoding:(myApp's NSUTF8StringEncoding)
       return shellTaskOutputString as text
   else if shellTaskStatus = 1 then -- Task Failed
       set shellTaskErrorHandle to shellErrorPipe's fileHandleForReading()
       set shellTaskErrorData to shellTaskErrorHandle's readDataToEndOfFile()
       set shellTaskErrorString to myApp's NSString's alloc()'s initWithData:shellTaskErrorData encoding:(myApp's NSUTF8StringEncoding)
       set shellTaskErrorReason to shellTask's terminationReason() as integer
       if shellTaskErrorReason = 1 then -- Task Failed Because of an Empty String Result, Or Has Error String Result
           return shellTaskErrorString as text
       else if shellTaskErrorReason = 2 then -- Task Properly Failed to Complete Execution for Unknown Reason, Maybe No Error String Result
           return "Task Properly Failed to Complete Execution for Unknown Reason" as text
       end if
   end if
end if

I'm not suggesting it is a definitive way of using the NSTask class, as Shane would have a lot more experience with this stuff, but it certainly worked ok back on Sierra.
I know a lot of the NSTask function's where marked for deprecation some time ago, so it may not work on the later MacOS's.

The "if not (shellTask's isRunning() as boolean) then" line can be eliminated, as the code execution waits at "shellTask's waitUntilExit()" for the task to complete anyway, but I included it just for completeness, and to show all of the possibilities.

The "shellTask's terminationReason()" and if clauses can also be excluded most of the time, but they Highlight the difference between an Error message failure, and a massive failure by the NSTask object itself.

Applescript:


-- If you change the line.

shellTask's setArguments:{"-c", "whereis osascript"}

--TO

shellTask's setArguments:{"-c", "whereis supermodels"}

You will see what I mean, as the error is merely an empty string, which is the same result you would get in the Terminal. because supermodels are not installed on the system unfortunately.
But you may well want to check for the empty string, even though it was sent to the StandardError pipe.
So it is an error, but not a failure error.

Anyway it might be useful stuff for you, or maybe not.
But Good Luck with it.

Regards Mark

Last edited by Mark FX (2021-05-30 01:50:37 pm)

Offline

 

#9 2021-05-30 05:54:17 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 6791

Re: NSTask and Standard Output

Mark,

Relying on waitUntilExit works fine -- as long as the task doesn't return more data than can be buffered. The repeat loop testing for isRunning in my script allows the output to be collected periodically, avoiding overflows (and hence lost output).


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#10 2021-05-30 07:16:09 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1406

Re: NSTask and Standard Output

Thanks Mark. Just to better understand your script, I used my example from post 1 and deleted everything but the bare essentials. I also changed launch to launchAndReturnError. The script worked exactly as expected. I also tested it with a utility that can get the number of pages in a PDF and it also worked well. In these instances, of course, its probably best just to use do shell script but it's good to have this as an alternative. My edited version of your script doesn't return standard error but perhaps that would not be needed in every instance.

Applescript:

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

property myApp : a reference to current application

set shellTask to myApp's NSTask's new()
shellTask's setLaunchPath:"/bin/echo"
shellTask's setArguments:{"-n", "OK"} -- the -n option removes trailing linefeed

set shellOutputPipe to myApp's NSPipe's pipe()
shellTask's setStandardOutput:shellOutputPipe

set {theResult, theError} to shellTask's launchAndReturnError:(reference)
shellTask's waitUntilExit()
if theResult as boolean = false then return "The echo command failed"

set shellTaskOutputHandle to shellOutputPipe's fileHandleForReading()
set shellTaskOutputData to shellTaskOutputHandle's readDataToEndOfFile()
set shellTaskOutputString to myApp's NSString's alloc()'s initWithData:shellTaskOutputData encoding:(myApp's NSUTF8StringEncoding)

if shellTaskOutputString as text = "" then
   return "The returned string was empty"
else
   return shellTaskOutputString as text
end if

Last edited by peavine (2021-05-30 07:42:27 pm)


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#11 2021-05-31 04:51:50 am

Mark FX
Member
From:: UK
Registered: 2011-08-12
Posts: 163

Re: NSTask and Standard Output

Shane

You're absolutely right, you wouldn't use waitUntilExit() for time consuming tasks with a lot of data, one because it would obviously block the script for a long time, and two it could possibly cause data loss.
And you would obviously not need to use both, you would use either waitUntilExit() or isRunning in a repeat loop, depending on the type of task and and the expected data load.

My code snippet library is a lot of useful bits of code, rather than complete solution's, a lot of them are OTT examples, showing many of the possible options, where you might just use bits of the code, or indeed none of it.
I thought the OP might find bits in their useful, and show some alternative ways of using the NSTask class.

The terminationReason() part is not always required, but does show how standardError output might be expected to contain a useful result string, and not just errors.

Regards Mark

Last edited by Mark FX (2021-05-31 05:42:48 am)

Offline

 

#12 2021-05-31 05:03:22 am

Mark FX
Member
From:: UK
Registered: 2011-08-12
Posts: 163

Re: NSTask and Standard Output

@peavine

My posting was not suppose to be a complete solution, I posted it to show some alternative ways of using the NSTask class, as you've discovered, a mixture of your's and mine can work equally as well.

Using NSTask in AppleScript has it's place in certain circumstances, but I often just use "do shell script" myself, because it's generally good enough for the job.
I suspect "do shell script" possibly uses NSTask's "launchAndReturnError:" behind the scenes.

I posted the code snippet to highlight that standardError can often contain a useful and sometimes expected result, especially when looking for a supermodel installation on your system, as the empty string result tells you that there not installed, and is not just spitting out an error message.

You may have noticed that the NSTask class has a number of pending deprecations planned, this includes launchPath(), and launch(), so it's possible that my code won't run in future OS releases.
I've seen these same deprecation's are listed in the NSTask Swift equivalent Process() class as well.
And have read a lot recently about Apple encouraging developers to use XPC Services in there app's, instead of NSTask() and Process().
So this could be a sign of the future for the NSTask class.

Regards Mark

Last edited by Mark FX (2021-05-31 05:14:54 am)

Offline

 

#13 2021-05-31 02:01:56 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1406

Re: NSTask and Standard Output

Just for learning purposes (not for actual use), I modified my earlier edit of Marks's script to create a script that returns standard error. If the script is unable to run the command (mkdir in this case), then an error message to that effect is returned. If the mkdir command runs but returns an error message, then that error message is returned by the script. I'll probably seldom if ever use this, but it's good to know the basics of how something works.

Applescript:

use framework "Foundation"
use scripting additions

set shellTask to current application's NSTask's new()
set shellErrorPipe to current application's NSPipe's pipe()

shellTask's setLaunchPath:"/bin/mkdir"
shellTask's setArguments:{"/Users/Robert/DoesNotExist/New Folder"}
shellTask's setStandardError:shellErrorPipe
set {theResult, theError} to shellTask's launchAndReturnError:(reference)
shellTask's waitUntilExit()
if theResult as boolean = false then return "The mkdir command failed"

set shellTaskErrorHandle to shellErrorPipe's fileHandleForReading()
set shellTaskErrorData to shellTaskErrorHandle's readDataToEndOfFile()
set shellTaskErrorString to current application's NSString's alloc()'s initWithData:shellTaskErrorData encoding:(current application's NSUTF8StringEncoding)
return shellTaskErrorString as text --> "mkdir: /Users/Robert/DoesNotExist: No such file or directory"


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#14 2021-05-31 02:24:38 pm

Mark FX
Member
From:: UK
Registered: 2011-08-12
Posts: 163

Re: NSTask and Standard Output

@peavine

Yeah I think sometimes folks could just use some ideas or options, rather than complete solutions pushed on them, especially when there are a number of different ways to achieve the same result.
In the case of running shell script's, there are a few option's for AppleScripter's with the "do shell script" command, and the various ways of using "NSTask", your choice would be based on the type of shell command and data load.

I'm with you, I think it's good to know how things work thoroughly sometimes, in order give you the knowledge for future projects and solutions.
And a lot of people can generally figure out what they want, when they understand all of the options available to them.

Good luck with it.

Regards Mark

Last edited by Mark FX (2021-05-31 02:33:00 pm)

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)