Skip to content

Shell and CLI Interactions

Broken Freelancer edited this page Aug 25, 2018 · 8 revisions
← previous · · · · · · · · · · · · · · next →

Running Shell Scripts

There are two methods by which you can execute shell scripts. The first, more "traditional" method, inherited from AppleScript, is the do shell script command. This method is severely limited. For example:

  • Only one of stderr and stdout is readily accessible (depending on if the last command returns 0), and even this is by no means guaranteed.
    app.doShellScript('false')
    //    !! Error on line 1: Error: The command exited with a non-zero status.
    
    app.doShellScript('asdf; true')
    //  
    
    
  • Return values are not preserved (although they sometimes appear in an error message).
  • Newlines are emitted as \r. Apple advises to pipe to tr.
  app.doShellScript('ls')
  //    => "README.md\rbutton.svg"

Calling C Functions and Handling Arguments

A much more powerful method is to utilize the fact that the ObjC bridge can also be used to call C functions. JXA allows you to import many standard C libraries by header name, and others can be manually mapped.

For example, we can call system(3), and exit(3) from stdlib.h.

This way, exported variables in the environment correctly propagate through. Both standard out and standard error go where they should, and the script returns the correct return value.

The special function run, with a parameter, creates an JavaScript array of the arguments passed to the script/applet.

So, when put together, the following script behaves exactly as if one were to use sh -c:

#!/usr/bin/env osascript -l JavaScript

ObjC.import('stdlib')

function run(argv) {
  argc = argv.length // If you want to iterate through each arg.

  status = $.system(argv.join(" "))
  $.exit(status >> 8)
}

Arguments

You can also use NSProcessInfo with the arguments array:

var args = $.NSProcessInfo.processInfo.arguments

// Build the normal argv/argc
var argv = []
var argc = args.count // -[NSArray count]

for (var i = 0; i < argc; i++) {
    argv.push( ObjC.unwrap( args.objectAtIndex(i) ) ) // -[NSArray objectAtIndex:]
}
delete args

Accessing the environment

To read a single variable, use the getenv() function from C, For example, to get the application running (sometimes equivalent to argv[0]):

ObjC.import('stdlib')
$.getenv('_') // This should always be '/usr/bin/osascript'

To get all of the environment variables, use NSProcessInfo and unwrap the environment dictionary to an object, then unwrap each string:

var env = $.NSProcessInfo.processInfo.environment // -[[NSProcessInfo processInfo] environment]
env = ObjC.unwrap(env)
for (var k in env) {
    console.log('"' + k + '": ' + ObjC.unwrap(env[k]))
}

Setting Exit Code

In shebang scripts, it is often useful to set exit code to signify success or failure.

ObjC.import('stdlib')
$.exit(123)
← previous · · · · · · · · · · · · · · next →