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.
- 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. - 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. - Finally, in both paths the content it piped to
xsel
either usingcat
for a file or asecho
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.