November 24, 2011 at 10:31 PM by Dr. Drang
Getting the last input in a bash session in the Terminal (or in any terminal emulator, like iTerm) is easy: just press the ↑ key. Getting the last output, though, isn’t so easy, because there isn’t anything built into bash for the last output. But you can put it on the Clipboard with a little scripting of the Terminal.
You can, of course, put the output of any shell command on the Clipboard by piping it to
pbcopy, which is Mac addition to the Unix command suite.
If you’re not using pbcopy and pbpaste when you work at the command line on a Mac, you’re really missing out: leancrew.com/all-this/2011/…
That’s only good, though, if you have the forethought to add
| pbcopy—or better yet,
tee pbcopy so you can see the output, too—to the end of the command before you execute it. I seldom have that forethought.
Often, you can just rerun the command with
| pbcopy tacked onto the end. I do that often enough that I’ve made a TextExpander snippet, with abbreviation
;pb, that expands to that with a space before the pipe character. So when I want the previous command’s output on the clipboard, I type
It doesn’t save many keystrokes, but it does eliminate typos.
(Yes, I certainly could select the text with the mouse/trackpad and copy it to the clipboard. But mousing around in the Terminal just seems wrong. It’s a very keyboard-centric application, and I want a workflow that’s consistent with that.)
Sometimes, rerunning the command isn’t feasible because running it the first time changed a file or did some other manipulation that would make the output different if you ran it a second time. I wanted something that would work in either case but sporadic Googling never turned up anything useful.
Yesterday I realized I had been looking for a bash solution when I should have been looking for a Terminal solution. Terminal is AppleScriptable, and one of its properties is
history, which contains all the
visible text of a window or tab. With a little regex massaging,
history could be whittled down to just the output of the last command (assuming it’s small enough to fit in the Terminal window).
As Ben points out in the comments,
history gives all the text in the window, including the stuff that’s scrolled off the top. This is a much better solution, and I’ve changed both scripts to adopt his suggestion.
Here’s the script I came up with. Because it’s heavy on text processing, I didn’t even try to write it in AppleScript and went with Python and the
appscript module instead.
python: 1: #!/usr/bin/python 2: 3: from appscript import * 4: from osax import OSAX 5: 6: sa = OSAX() 7: term = app('/Applications/Utilities/Terminal').windows.selected_tab 8: inout = term.history.get().split('\n$ ') 9: 10: sa.set_the_clipboard_to('\n'.join(inout[-2].split('\n')[1:-1]))
osax module comes with
appscript and is needed to handle the clipboard interaction. Clark’s Tech Blog has a nice introduction to
osax, which is how I learned about it.
To understand how the script works, you have to know my bash prompt. I use a two-line prompt, with the current working directory on the first line and a dollar sign on the second. This does eat up some of Terminal’s vertical space, but it lets me know where I am in the directory structure. I used to use a single-line prompt with the directory before the dollar sign, but that often took up so much of the line that my commands would wrap around and be hard to read.1
Line 7 gets the window/tab combination that I’m currently working in, and Line 8 gets its contents and splits it at the line-starting dollar signs. Each item of the
inout list now contains an input line, whatever output it produced, and the first line of the following prompt (the line with the current working directory).
Here’s an example of how the splitting is done. Each outlined colored area is one item in the
Line 10 probably squeezes too many operations into one command, but it
- Gets the next-to-last item in the
inoutlist. Note that although we want the most recent output, we don’t want the last item in this list. As you can see in the figure, because of the way
contentsworks, the last item is always the one or more blank lines after the last prompt.
- Splits that item into lines.
- Selects all the lines from the second through the next to last. This gets rid of the input line at the beginning and the directory portion of the prompt at the end. If I had a one-line prompt, I’d have the slice be
- Joins all the lines back together with newline characters.
- Puts the resulting text on the clipboard.
I named the script “Copy Last Output” and put it in
~/Library/Scripts/Applications/Terminal so FastScripts could get at it. I gave it a keyboard shortcut (in FastScripts) of ⌃⌥⌘C, which is easy to type. FastScripts is smart enough to know that the script should be run only when Terminal is the active application.
The other Terminal script, “Copy Last Output Line,” is a simple variation. It does exactly what it says, putting just the last line of the last command output on the clipboard. It’s helpful when I’ve used my
up2flickr script to upload a bunch of photos to Flickr. The output of
up2flickr includes the URLs of the Flickr pages of all the uploaded photos. By typing ⌥⌘C when the upload is over, I get the URL of the last photo on the clipboard, which makes it easy to browse to that page by typing
open and then pasting the URL.
Here’s the source of “Copy Last Output Line”:
python: 1: #!/usr/bin/python 2: 3: from appscript import * 4: from osax import OSAX 5: 6: sa = OSAX() 7: term = app('/Applications/Utilities/Terminal').windows.selected_tab 8: inout = term.history.get().split('\n$ ') 9: 10: sa.set_the_clipboard_to(inout[-2].split('\n')[-2])
The only difference is in Line 10, which gets just the next-to-last line of the next-to-last item in
inout. If you look at the annotated Terminal screenshot above, you’ll see why that gets the last line of the last command output.
After writing these scripts and playing with them for a little while, I’ve been thinking about expanding them to handle the output from Octave and Python interactive sessions. This should be easy—I can check the window title to see which command is running and split on whichever prompt is appropriate.
What’s interesting to me about these scripts is how they’re a twist on my normal way of working. I’m used to using Terminal as a conduit for interacting with the shell and other programs, but running a script outside the Terminal that communicates with Terminal itself is new to me.
It wouldn’t have been new to me if I’d found this page on StackExchange’s Superuser site. It includes an AppleScript/Ruby script written by the redoubtable Lri that does exactly what mine does (but written for a one-line prompt, I think—I don’t use Ruby enough to remember the difference between the
I’m more than a little embarrassed to have written a script so much like Lri’s so shortly after the Superuser page went up. I suspect the reasons I never found Lri’s solution in my Google searches were
- Most of my searching was done more than a week ago, before that page appeared.
- I spent most of my time looking for a bash solution, not a Terminal solution.
Whatever the reason, Lri deserves credit for primacy on this one. As I said in the comments, if I’d seen his script before writing mine, I would have used it directly or done a straight translation to Python so I could modify it as needed without stumbling with my toddler-level Ruby knowledge.