Sending text from a file to the X11 clipboard to copy and paste

One of the best parts about being a software developer is that I get to build and improve tools to make my life easier. On Monday I did just that.

Copy and Paste

A common task anyone does on a computer is to copy and paste. This acts as an easy way to export and import data from applications.

Copy and paste is pretty simple on Windows and OSX, as you only get one buffer from the system. On Linux though, there are three different buffers (called selections): primary, secondary, and the clipboard.

The clipboard buffer is what most people think about (the Ctrl-C, Ctrl-V). Some Linux users also know about the primary buffer, which is used when text is highlight (the copy) and then the middle mouse button is clicked (the paste).

I use the primary buffer for most things (the middle click one), since it works almost everywhere including in terminals. But sometimes the middle click buffer doesn’t work, like in a web form that is binding to clicks or in Flash.

Introducing clip

So awhile back I created a little utility called clip that would copy a file’s contents into both buffers. This let me then paste the file contents using Ctrl-V or the middle click.

The early version was simple and just sent the file contents to xsel which manipulates the clipboard buffers.

#!/bin/bash
# Clip content to a clipboard
# C-v clipboard
cat "$1" | xsel -b
# X11 middleclick clipboard
cat "$1" | xsel -p

Supporting pipes

After using this for a few months I quickly ran into a limitation.

I do a lot of my draft writing in large text files, some approaching 5,000 lines long. This lets me focus on writing first, without having to think about where to save the file and what to call it.

The problem is that if I want to copy some of it out of the file, clip will copy the entire file. Trying to select the text in my emacs terminal wouldn’t work either because of extra characters I have on the screen and word-wrapping.

So then I decided to rewrite clip so that it could accept a stream of content instead of a file (aka STDIN). I started trying to port this to Ruby but it got complex quickly because I needed to shell out to multiple external processes like xsel.

Back in bash I played around with the old code a bit and got something simple and easy to understand by using a while loop.

Code

#!/bin/bash
# Clip content to a clipboard
#
# Either a filename arg or STDIN can be used to copy the content
# to the clipboard (both C-v and X11/middleclick clipboards)
 
if [ -t 0 ] ; then
    # C-v clipboard
    cat "$1" | xsel -b
    # X11 middleclick clipboard
    cat "$1" | xsel -p
else
    content=''
    while read -r line ; do
        content=$content$line"\n"
    done
    # C-v clipboard
    echo -e $content | xsel -i -b
    # X11 middleclick clipboard
    echo -e $content | xsel -i -p
fi

There are three important parts to clip now.

  1. The first part of the if statement occurs when there isn’t any steamed content (i.e. STDIN is empty). In this case clip just reads a filename passed in as the first argument.
  2. The second part is when there is a stream of content from a pipe. Using while read -r line each line of the content is read in and concatenated onto the content variable.
  3. Finally, in both paths the content it piped to xsel either using cat for a file or as echo for a string (-e is used on echo to respect newlines in the content).

Note: in both examples xsel is called twice, once for the primary buffer (-p) and once for the clipboard buffer (-b). Unfortunately they both can’t be called at the same time.

The end result of this is that I can now easily copy content from a file or a string into my clipboards.

Examples

  • Copy tmp/scratchpad.txt’s contents
    $ clip tmp/scratchpad.txt
  • Copy a JSON response
    $ clip tmp/response.json
  • Copy the “Hello” string
    $ echo "Hello" | clip
  • Copy the lines from a Ruby on Rails production log that were successful
    $ grep -iR '200 OK' log/production.log | clip

This lets clip be used at the end of any command pipeline, which makes it valuable for reporting.

Find all views rendered in a Ruby on Rails production log file, sort them, and count how many times each was rendered:
$ grep -iR 'rendering' log/production.log | grep -v 'layouts' | cut -f 2 -d ' ' | sort | uniq -c | clip

Results in:

 1 account/login
 2 activities/index
 2 admin/index
 1 calendars/show
 1 context_menus/issues
12 issues/index.rhtml
 1 issues/new
32 issues/show.rhtml
 2 my/page
10 previews/issue
 5 projects/settings
13 projects/show
 3 repositories/show
15 rescues/layout
 3 search/index
 3 settings/edit
 1 users/show
 1 versions/index
 1 versions/show
51 welcome/index
 1 wiki/show

Hopefully I’ve showed you an example of what you can do when you start thinking about what tools you use and how you can improve them. clip isn’t a lot of code and isn’t sexy but it saves me minutes each time I use it and I use it dozens of times per day.