When I started my on-call shifts, we had pretty little in the way of automation for day-to-day issues. Tasks like SSH’ing into our cluster, starting a Rails console, or doing a deep search through our gigantic mail directories, were either shelved away in someone’s bashrc
, history log, or just ingrained into someone’s memory. This pain was also felt by a few other of my fellow programmers, and we started cobbling together a Git repo simply named “37s shell scripts”.
This repo started very innocently: a little Ruby script named console
that mapped a product name (basecamp
) to a server name inside of our cluster (jobs-03
), SSH’d in, and then ran a production Rails console. Several more bash and Ruby scripts started to trickle in as we started to share more of our personal code that we used when on-call. Eventually Sam laid down a foundation of bash scripts and directories borrowed from rbenv, and dubbed it “37”.
Enter the 37
37 works in a deceptively simply manner: an executable (bin/37
) parses out subcommands, which map to a single script in the repo’s libexec/
directory. This gave convention to our scrappy outfit of battle-hardened shell scripts. No longer were they to be randomly named misfits! They each now were subcommands of 37, organized and named by a convention of libexec/37-SUBCOMMAND
. Our first recruits were 37 console
, 37 sftp
, and 37 scp
, and many more subcommands have been drafted since.
In the 10 months since we started 37, we now have over 30 subcommands at our disposal. The secret to automation was simple: convention, and sharing. From one command, we can run a report for a customer (37 script basecamp file_usage_summary
), open a repo in GitHub (37 gh
), or even search across gigabytes of log data to hunt down a bug (37 grep bcx all misc [PATTERN]
).
Introducing sub
I was really inspired by how Sam laid out 37 initially and how we rallied around the conventions set forth to produce an extremely useful tool for everyone at 37signals. I think this pattern is worth sharing, and with some help I’ve extracted the pattern of how rbenv and 37 were crafted into sub: a model for laying out programs by convention.
You’re more than welcome to prepare your own sub, for your organization, or just for your own personal use! They don’t even have to be bash scripts: We write mostly Ruby subcommands for 37, but you could choose any scripting language you’d like for your sub.
Autocompletion magic
I know what you’re thinking: Why is this better than a ~/bin
directory, or several aliases? My answer to this is simple: autocompletion! Since each subcommand follows a convention, you can get autocompletion for arguments without having to know any shell scripting trickery.
Here’s an example of autocompletion in action from 37, with some commands omitted. Tabbing after invoking 37
yields a list of available commands, which comes by default with every sub you prepare:
After that, invoking a specific subcommand allows for more autocompletion arguments to be given, if that subcommand has opted in:
It’s really refreshing to be able to wire up autocompletion without any fuss. Right now all subs support bash and zsh autocompletion. I’ve written up a detailed guide for hooking this up yourself.
Self-documenting subcommands
Applying some convention to how commands are documented allows sub help
to extract helpful information without you having to do extra coding to support it. This is another opt-in feature, enabled by magic comments. Here’s an example from 37 console
:
When you run 37
itself, it will print out the Summary line for each command:
And when you run 37 help console
:
37 future
I’m really happy to see how far 37 has come: what started as a whipped up pile of shell scripts evolved into an orderly, helpful convention for making our jobs easier. We’ve also hooked up several of our awesome support crew with 37, and it’s been an absolute win for helping our customers faster.
We’d love to get feedback on sub, and the pattern in general. Check out the README to see how to prepare your own!
Jan Habermann
on 28 Sep 12So if you run “37 console basecamp production” you end up connecting through ssh to a server and basically fireing up script/rails console production, right? That’s a brilliant timesaver…
I’m wondering: you probably have a dozen servers or so. On which one do you start the rails console on, then?? Can you still pick the one you like, or is it an instance that doesn’t really serve requests through unicorn, just a “console instance”.
Great little project, will start hacking and organizing my own little helper scripts. Thanks for taking the effort and making this public.
Nick
on 28 Sep 12Jan: Yep, exactly! We have an entire separate script in 37 that takes a product name as input, and spits out a server name to connect to. Usually this server is not an application or database server, it’s a separate background job server that isn’t under heavy load.
And thanks! I’m excited to see what kind of subs are whipped up.
cowardbigtime
on 28 Sep 12cool post – thanks for sharing – refining drudgery (autocompletion like this with scripting) is important to limit errors and to improve productivity, but also…happiness on the clock!
Justin McGuire
on 28 Sep 12Autocompletion is one of those very small things that makes a very large difference on the command line. I once had a coworker who created a complex autocompletion setup for git, that would autocomplete branch names, and it was a blessing.
Also, why can’t I click on your name and see more posts by you?
Brett
on 28 Sep 12Congrats, you reinvented Git’s command architecture. :)
Kasey
on 28 Sep 12Great stuff, already started using it and think it is great. Encourages everyone to start cleaning up and sharing their little scripts.
One thing that is not clear to me though: If all I want to support for autocomplete is just directory/file tab auto complete how do I do that?
IE if I have sub my-command how do I write my-command so that tab completion works to get the filename?
Mikael
on 28 Sep 12Cool cool… but what monospace font is that you’re using?
Nick
on 28 Sep 12Brett: Yes, this was by design. rbenv’s command structure was inspired by git…the subcommand pattern is now available for anyone!
Kasey: Interesting question! I’ve added an issue for that: https://github.com/37signals/sub/issues/5
Mikael: I’ve been trying out Adobe’s Source Code Pro this week. http://blogs.adobe.com/typblography/2012/09/source-code-pro.html
Jeff
on 28 Sep 12My command line skills are somewhat lacking. I really like the idea of console-ing directly, but I can’t quite get there all the way.
exec %Q(ssh -t #{app[‘server’]} “cd #{app[‘path’]};bundle exec rails c RAILS_ENV=#{app[‘environment’]} ; bash”)
the ‘cd’ after SSHing in works, but it complains about the bundle command not being found. Any tips?
Antoine
on 29 Sep 12Thanks for the blog post. Can’t wait to check out sub
Leonid
on 29 Sep 12Why not Chef or Puppet or other provisioning tool?
Joey
on 30 Sep 12vega
Mario Rodriguez
on 30 Sep 12Not sure what all this stuff is for (I’m more of a web designer) but I’m sure that if it comes from 37Signal it’s great! :)
Neil
on 30 Sep 12Do you have a convention for ensuring everyone in the team is using the same version the scripts?
Nick
on 30 Sep 12Neil: there’s an rbenv-version script that we ported into 37 that basically just reads the latest git sha of the repo. I decided to punt on it to start for sub.
Servomated
on 01 Oct 12Yes, if anyone could share some helper scripts, that would be great! Thanks! Brad R
Jeffrey R.
on 01 Oct 12It seems at first glance that a shared /37/bin/ directory (with conventions) would do this and get you your features:
- tab auto-completion
- man pages
- usage (—? or -help)
and it would for the first level. But the usefulness really kicks in at the lower levels. I would still use existing conventions/standards for man[ual] pages and usage, but I like the idea of enabling a newbie to be able to navigate this “menu” of commands w/o needing to know much about scripting.
Jeffrey L. Roberts
on 01 Oct 12I started this exact project about 3 weeks ago. My co-worker showed me this post this morning, and the last comment made was by someone with my first name and last initial. Unbelievable =]
I would like to contribute to this project, who should I talk to about getting involved?
Brian
on 02 Oct 12This is good timing for us. Our small development team has a growing number of disparate scripts for sysadmin work and web/db management. It’s more complicated than it should be for new members to learn what tools are available and how to use them.
Unifying our scripts under one command with autocompletion and help built in will be a big improvement. Thanks for posting this for the community!
Vincent Murphy
on 03 Oct 12The README should link to this blog post. Google “bash subcommands” took me there. Thank you for releasing this.
Duff OMelia
on 03 Oct 12Sub works wonderfully. Thanks!
This discussion is closed.