Archive for the ‘Command Line’ Category

Piping stdout and stderr to Preview

A while back, I wrote about how handy it was to redirect a man page into Preview. This allows you to keep the man page open, search it, and generally have a better user experience than struggling with more (or less) to navigate through the information provided there.

man -t apropos | open -fa Preview

Recently, someone asked me about more modern command line interaction, specifically, commands that use --help or similar to provide their documentation. Could that information be opened in Preview as well.

So I put on my thinking hat and set to work. The first command line utility I decided to work with was screencapture because I’ve been using it fairly heavily over the last few days. However, it appears that Apple failed to build in an actual help system beyond man. It was a poor choice to try to use to render but I decided to keep plugging away at it because I wanted to be able to pipe both stdoutand stderr to Preview.

What I came up with looked something like this, all in one line of course:

bash -c "screencapture -? &> 
    $TMPDIR/previewrendertext.txt; 
    /usr/sbin/cupsfilter -i text/plain 
        -D $TMPDIR/previewrendertext.txt 
        2> /dev/null | 
    open -fa Preview"

This all relies on cupsfilter, which can convert a file of text to a printable form, which just happens to be readable by Preview as a PDF.

I’m doing quite a bit of conglomeration, joining the stderr and stdout streams using &> and saving them into my Mac’s $TMPDIR. That file is cleaned up by the -D option from cupsfilter.

I also am removing the incessant debug messages from cupsfilter by redirecting them to /dev/null before opening the print output in Preview.

Please note that I’m still using tcsh/zsh over bash on my main system, so that certainly affects things. Since I needed a little of the bash nuance, I decided to run it all squished as a single -c command. (I’m sure if I spent enough time, I could do it all in csh but I really didn’t want to spend that time.)

As you can see in the previous screenshot, an older utility meant for man output doesn’t really look all that hot shoved into Preview via cupsfilter, especially with line lengths. There’s also no nice groffing and troffing to make everything pretty, the way you get with man:

So how could would this kludge work with a modern command-line app, such as those produced using the Swift Argument Parser (https://github.com/apple/swift-argument-parser)? First, I built a utility that would let me run any command (well, so long as it was properly quoted) without having to type all the details out each time I ran it:

#! /bin/bash

$@ &> $TMPDIR/previewrendertext.txt ; /usr/sbin/cupsfilter -i text/plain -D $TMPDIR/previewrendertext.txt 2> /dev/null | open -fa Preview

This allowed me to call preview "now --help" to redirect the standard help message from my now utility (https://github.com/erica/now)  to Preview. Yeah, originally I wanted to just pipe stuff into it but I couldn’t figure out how to get the stderr and the stdout piped together into a single stream, let alone convert them into a file form because cupsfilter doesn’t know or do pipes.

It’s pretty readable and well-formatted due to the automatic configuration that the Swift Argument Parser provides from my code but it just feels, you know, very very plain.

So I went ahead and tried to see what would happen if I groffed it up a little by passing it through /usr/bin/groff -Tps -mandoc -c instead of using cupsfilter:

bash -c "now --help &> 
    $TMPDIR/previewrendertext.txt; 
    /usr/bin/groff -Tps -mandoc -c 
    $TMPDIR/previewrendertext.txt" | 
    open -fa preview

And it’s…pretty meh. I tried mandoc, mdoc, me, mm, ms, and www formats. They all came out the same, and none of the SAP tabs really worked. I think it looks a lot more “manny” than the straight printout but the indentation really bugged:

I decided to stop at about this point as there’s really a time when further effort just isn’t worth further investment — so I could throw it out there and see if this was of interest to anyone else.

Let me know.

Crafting a custom word count service

I just happened to need to do a lot of word counts today so I put together a service to make my life easier. While, I performed my initial work on Mojave but the same approach works all the way to Big Sur and, presumably, the upcoming macOS Malibu Barbie.

Open Automator

To get started, launch automator and create a new document.

Select Quick Action, Choose from the new document dialog:

Add Scripting

Drop in a Run Shell Script and then an Apple Script. You can search from them in the top-left corner. Drag them in order into the right panel.

I used /bin/bash for my shell script, shocking, I know, as I am well known for my love of all things csh. Feel free to use whatever shell suits you. The first non-argument shell variable ($1 here) corresponds to highlighted text, which can be used by the system contextual menu:

echo `echo $1 | wc -w` words. `echo $1 | wc -c` characters.

Switch the pop-up for piping output (“passing input”) from “to stdin” to “as arguments”. This allows the AppleScript to read the selected data and present a dialog. If you forget, you’ll get empty input and something like “1 words, 0 characters” all the time.

on run {input, parameters}
    display dialog input as string buttons {"OK"}
end run

Save and Run

Save the action. I called mine “Word Count”.

The action automatically saves to ~/Library/Services, in case you want to find or delete it in the future.

% ls
Word Count.workflow/

Your new quick action automatically adds a custom service to your contextual pop-ups. Just highlight anything you want to count, from text on a web page to your work in a document and open the contextual menu:

Make sure the output looks reasonable. For easiest testing, copy the text to your clipboard and then use wc directly in terminal.

And, then boom, you are done.

Let me know if this was helpful.

Executing command-line directly from Xcode

I got pulled into one of those conversations where I end up saying, “Fine, I’ll put up a post about it” and this is the post. Yes, you can test and run command-line apps directly from Xcode but I pretty much never do. It’s a pain with few benefits. That said, here’s how you do it.

Arguments

Let’s say you need arguments. Open your scheme (⌘<) and select the Run > Arguments tab. Add the arguments you want to pass on launch one at a time. Double-click to edit any argument:

The arguments are vended byCommandLine.arguments. Either count the array or use CommandLine.argc to find out how many arguments you’re dealing with.

print(CommandLine.arguments)
print(CommandLine.argc)

Counter-intuitively, Xcode does not automatically quote the arguments for you. This produces five arguments, not three, or six if you include the command itself:

["/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test", "first", "second", "third", "fourth", "fifth"]
6

And what do you expect from the following?

You get this if you run directly in Xcode’s console:

["/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test", "first", "several items at once", "third"]
4
Program ended with exit code: 0

But if you set your code to execute using Terminal:

Launching: '/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test'
Working directory: '/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug'
3 arguments:
argv[0] = '/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test'
argv[1] = 'first'
argv[2] = 'several'
["/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test", "first", "several"]
3

Xcode’s Crazy Terminal Option

If you’re running anything with direct key input (using POSIX termios/raw mode) or curses, running in the console doesn’t work. So Xcode provides a way to run those utilities in the terminal. Visit Run > Options and scroll all the way down.

This feature is buggy as hell, produces ridiculous amounts of excess text (see this), can take a significant time to launch, and even more time for Xcode to realize the process has finished. It is impossible to use with paths that use spaces (“warning: working directory doesn't exist: '/Volumes/Kiku/Xcode/Derived'“).

I don’t like it. I don’t use it. But it exists.

Sane Command-Line Execution

Unless you’re dealing with things like automation and such, you can try out your compiled command-line apps by dragging your executable from the Products group onto the terminal. This places the path to your build at the prompt. Type out your arguments and press return:

However, I prefer to use a Copy File build phase. Select your Target > Build Phases, click plus (+) and add the executable. (I use absolute path and disable “only when installing”.) This lets you install directly to  standard locations like /usr/local/bin or ~/bin, or if you don’t want to place it there until it is stable and ready for deployment, you can use a development folder:

Assuming your destination is in your shell’s path, start a new shell for the executable to be picked up the first time. After that, you can compile and run as you like.