Any idea why this shell output won't save to a variable?

Seashells is a service that will pipe the output of this python script to a web URL, which it generates randomly. I need to capture that URL later in the Macro. If I run this command in the Terminal and append 2> test.txt then it writes the URL to that file perfectly.

Include Errors is checked.

python3 "Users/ak/Code/dbxcli_progress_monitor.py" "$KMVAR_fileToMoveFinal" "/Cloud/$KMVAR_fileName" | seashells 2>&1

Are the paths to python and seashells included in your KM ENV_PATH variable? You could test with a shell script action, eg:

image

If that doesn't display the correct path then either edit ENV_PATH appropriately or include the full path in your shell script. Especially important if you are using python virtual environments -- you may have included those in your Terminal PATH or have explicitly sourced then earlier in the session, but the KM action has its own environment and knows nothing about them until they are explicitly included.

And check that you are passing the KM variables in to the shell script -- click the down-arrow next to the text area and make sure either "All Variables" or both fileToMoveFinal and fileName are checked.

Make sure that "Failure aborts macro" is unchecked as well.

For troubleshooting, try using "Display results in a window" rather than saving to a variable -- and make sure Notifications are allowed!

Hi Nige,

Seashells is thankfully found with "which seashells" but I've hardcoded it just in case.

The Python part works perfectly. The file begins uploading in the background, but no matter what combination of 2>&1 I use, nothing goes to the variable.

This works perfectly however.

python3 "Users/ak/Code/dbxcli_progress_monitor.py" "$KMVAR_fileToMoveFinal" "/Cloud/$KMVAR_fileName" | /usr/local/bin/seashells 2> ~/Code/seashellsurl.txt

It's not a big deal to use a file, I'm just more curious about what I don't understand. If someone has resources to point me to, I'd love to read them.

I did some more reading and I think I understand that when I use 2> to go to a file, then it's just grabbing the stderr right away, but 2>&1 is trying to merge the stderr into the stdout and therefore waiting for stdout to finish which it takes a long time to do, therefore nothing is getting written to the variable.

So I think my question is, is there some bash wizardry that can ONLY grab stderr as soon as it's available and send it to KM?

thanks,
Aaron

By using the Execute Shell Script action, you are limited to the features of that action, which may be a limiting factor in this case. I'm not sure.

However if you want to think outside the box, you could run the command inside a Terminal window, and then, by polling the window using "Select All" and "Copy", you can check for any error results even before the command finishes. This approach should work fine.

What does the Python code output? Unless it's absolutely huge it shouldn't take seashells long to send the data.

You can test using something quicker -- echo 'Hello World' is nicely traditional:

Seashells Group.kmactions (2.2 KB)

image

I don't know if this is "as soon as it's available" or if it still waits for seashells to complete, but you can redirect stderr to stdout but suppress the utility's actual stdout with

..."/Cloud/$KMVAR_fileName" | /usr/local/bin/seashells 2>&1 > /dev/null

But remember that you're looking at layers here. Regardless of when the error message is written out and whether or not KM saves that value to the variable then or after seashells has finished, your macro doesn't move on to the next action until the "Execute Shell Script" action has itself completed -- and that's after seashells is done.

If you can't wait for that then you'll need to set the action to "Asynchronously", and that means you can't save the results to a variable! So you'll have write to a file, and your macro will have to "Pause Until..." that file has been written to.

Something along the lines of

  1. Generate an UUID using the %RandomUUID% token
  2. Have seashells redirect standard error to /tmp/theRandomUUID.txt
  3. Check /tmp/theRandomUUID.txt every second until its contents are a) not empty, and b) haven't changed since the last check
  4. Whatever come next...

I don't see any problems with getting information about URI - see below.

Test exec.kmmacros (3.0 KB)

Keyboard Maestro Export

test1.sh:

#!/bin/sh

echo "$1 STDERR" >/dev/fd/2

echo "$1 STDOUT" >/dev/fd/1

In the main method of seashell.py first action after checking parameters is getting uri from server, write it to stderr and flush, what means the it is immediate visible on stdout of seashell, but as @Nige_S wrote, the result will be visible in variable after finishing all processes in pipeline.

How big is the result of first process in pipe? Maybe the part of output was overwritten? Seashell has option -q, which transfer output to the web, but not to stdout of seashell. Check what will you see if you set this option.

Thanks guys.

The purpose of all this is to initiate a file upload to Dropbox on a headless server, using a command line Dropbox client. The Python script reads the progress from the program and writes some additional data, like this:

Progress: 1.2% (11.0/936.0 MiB) | Rate: 4.90 MB/s | ETA: 3m 17s
Progress: 1.3% (12.0/936.0 MiB) | Rate: 1.04 MB/s | ETA: 15m 31s
Progress: 1.7% (16.0/936.0 MiB) | Rate: 4.19 MB/s | ETA: 3m 50s
Progress: 2.2% (21.0/936.0 MiB) | Rate: 2.62 MB/s | ETA: 6m 6s
Progress: 2.5% (23.0/936.0 MiB) | Rate: 2.09 MB/s | ETA: 7m 38s

So it can take as long as an hour. Seashells pipes that to a random HTML url so I can monitor the progress remotely.

The python writes to sdtout like this: sys.stdout.write(full_line_to_print)

I got it working. I run Macro 2 from Macro 1 right before Macro 1 runs the shell script, saves the stderr to the file, then Macro 2 waits for the file to appear and sends me the URL. Macro 1 keeps going and I can cancel the transfer by aborting the Macro if need be.

I came into this process with the mental model that I wouldn't need a temp file, but now I mostly understand why. My weird use case and KM's noninteractive shell opens up a whole can of worms.

Thanks!

If you want asynchronous info about URI you can always modify python code and put notification part after this section

      # get URL from server first
        conn.settimeout(SOCKET_TIMEOUT)
        data = conn.recv(RECV_BUFFER_SIZE)
        stderr.write(data)
        stderr.flush()

The code looks like (variables set for easy modification and to understand flow)

        import posix
        kmapp = "/Applications/Keyboard Maestro.app/Contents/MacOS/keyboardmaestro"
        macro = "Seashell info"
        value = str(data, sys.getdefaultencoding())
        command = '"%s" -p "%s" "%s" ' % (kmapp, value, macro)
        posix.system(command)

It calls KM CLI app which call KM macro with name 'Seashell info' with parameter received from server.

My macro just display notification, but you may directly call web browser :slight_smile:

image