Introduction to Command-line Brace Expansion

The command-line is a versatile interface that provides numerous tools for managing files and directories. Due to its flexibility, renaming and copying files can be tedious and error-prone, especially when files are nested deep within a file system. Luckily we can solve this problem with a helpful feature called brace (or bracket) expansion.

Brace expansion, {}, is a method to create arbitrary strings. When combined with other commands, it can be a very powerful addition to a developer's toolbox.

Copying Files

The first example we'll look at is for copying files. Let's start by creating an empty file called a.txt that we'll copy to a file called b.txt.

touch a.txt

Just to confirm we have a single file named a.txt, we can use ls -lh and see the follow output:

-rw-r--r--  1 peterbrown  staff  0 Jul  3 19:04 a.txt

When copying files, the cp command requires two arguments (source and destination), and looks like this:

cp a.txt b.txt

The result from that command will look something like:

-rw-r--r--  1 peterbrown  staff     0B Jul  3 19:04 a.txt
-rw-r--r--  1 peterbrown  staff     0B Jul  3 20:18 b.txt

Now let's look at the same example, but this time using brace expansion:

cp {a,b}.txt

Aside from the timestamp, the result from this command will be nearly identical to the one before:

-rw-r--r--  1 peterbrown  staff     0B Jul  3 19:04 a.txt
-rw-r--r--  1 peterbrown  staff     0B Jul  3 20:19 b.txt

While the results are identical, instead of specifying two separate file names, we have used curly braces to copy the file with a single argument. The first item in the braces, a, is the part of the original file name that we want to match, and the second item, b, is what we want to replace it with in the new file. Any text immediately surrounding the braces (in this case .txt) will be combined with the expansion.

How Does it Work?

At first glance this syntax may seem foreign, so let's use the echo command to inspect what is happening behind the scenes:

echo {a,b}.txt

This results in the following output:

a.txt b.txt

In the output above, you can see that the comma-separated values within the braces are combined with the surrounding text, expanding it into two separate file names - exactly the input used previously to copy the a.txt file to b.txt.

If we combine the output from echo with cp, we have a command that is identical to the following:

cp a.txt b.txt

Creating Backup Files

Brace expansion can be used with any commands that accept multiple arguments such as cp, mv, or even git mv. One use case for it is to quickly create backup files by adding an extension such as .bak.

Instead of having to type the same file name twice, and potentially spelling it wrong, we can use braces to add the extension:

mv a.txt{,.bak}

Which results in:

-rw-r--r--  1 peterbrown  staff     0B Jul  3 19:04 a.txt.bak
-rw-r--r--  1 peterbrown  staff     0B Jul  3 20:19 b.txt

Notice that in this example there is no value before the comma. This is because we are not replacing existing parts of the file name, and are just adding a new extension to it.

Renaming Files in Git

Another common use is to rename files that are versioned with Git. Let's create a quick Git repo and add our files to it:

git init
git add a.txt.bak b.txt

With git status we can see that both files have been staged:

On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   a.txt.bak
    new file:   b.txt

Now we can use git mv with brace expansion to rename one of the files. This time there is no value after the comma because we are removing part of the file name.

git mv a.txt{.bak,}

Using git status again, we can see that a.txt.bak is now named a.txt.

On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   a.txt
    new file:   b.txt

Wrap Up

We've just barely scratched the surface of what is possible with brace expansions. As illustrated, this syntax can be combined with many of the common commands you use on a daily basis. I encourage you to explore them further and see if you can come up with a a clever use case on your own.

The History of a Four-Year-Old Ruby Gem

This is a story about learning, taking pride in your code, and tastefully sprinkling profanity into otherwise boring blog posts. It started out as an introduction to ClassyEnum, but I hate writing code examples, so this is what you get instead.

The Early Days

Four years ago I was working on a rewrite of a PHP application in Rails. I had no idea what I was doing. Subversion was still something startups used (I keep telling myself that).

We were building software for monitoring commercial-scale solar power plants, and I was working on a feature related to detecting data anomalies like low power conditions and equipment failure. When an anomaly was detected, some sort of action would be taken depending on what kind of alarm it was. Each type of alarm could be individually configured with different settings for which action should occur when the alarm was triggered. Some alarms would just be logged to the database, others would send an email, and a few even required manual verification from a user.

At the time, I was a total noob to application architecture and had been taught in school that redundancy in a relational database was a bad thing and that all data should be as normalized as possible. One of the techniques I learned was to use a "lookup" table in order to keep modifications limited to a single table. In theory this sounds like a good idea because it allows you to make changes in one place and have it update everywhere. In practice, obsessing over database normalization is a giant turd of an idea that academics and DBAs preach because that's what expensive textbooks say to do. Suck it normal forms.

Here's a simplified example of what our tables looked like:

We had the alarms table to define the various types of anomalies being monitored for a particular project, and the alarm_actions table defining what to do when an alarm was activated. The alarm_actions table consisted of four or five records with a few combinations of the different settings.

We had hired a contractor at this time with real-world software development expertise who basically told us our design sucked. He brought up the concept of enums in Java and how they would be better than dealing with join tables and managing application logic. My first reaction was "no fucking way am I writing Java code in Ruby", but I'm a rational person so I listened to him. After all if it weren't for Java we wouldn't have the Eclipse™ IDE.

(╯°□°)╯︵ ┻━┻

He put together an initial proof of concept to show us how we could replace the lookup table with an enum class. He also showed us how we could delegate logic to this class (ala polymorphism) instead of having conditionals scattered throughout our codebase. This changed everything. I took his example, put it into a module, and got rid of the lookup table.

Here's a quick survey about your application and database architecture to figure out whether you really need a lookup table:

  1. How often do you CRUD records in the lookup table?
  2. How much of your application logic depends on the state of that table?
  3. If you removed the data in this table, would your application still run?
  4. How many records are in this table?
  5. Is it hairballs or hair balls?

If you answered "never" or "rarely" to the first two questions, "fuck no" to the third and "less than ten" to the fourth, you've got a great candidate for denormalizing that table and replacing with ClassyEnum (or a perhaps a state machine). I threw the last one in there just to see if you were paying attention. I'd recommend checking out the README for an overview on how to get started with this.

For the sake of this being a complete history lesson, here's a gist of my first iteration which would later become ClassyEnum. I removed the application specific code so it's not 100% complete, but you can tell how much I sucked at Ruby because the ActiveEnumValue class explicitly inherits from Object. In my defense, the contractor had it that way in his concept code. In his defense, he was a Java developer.

After going through a few iterations in our application, ClassyEnum 0.0.1 was released on September 21, 2010 (a Tuesday if you were wondering).

I originally named it ActiveEnum, but when I extracted it to a gem, there was already a gem with the same name. Ironically that one never made it to version 1.0. Also ironically, there's another gem called enum which was released exactly 11 days before ClassyEnum, based on the exact same Java enum concept. History lessons are fun.

The Mid-life Crisis

Fast forward to the spring of 2012. Our application was in production and I was a much more seasoned Ruby developer than I had been two years prior. I knew that all classes inherit from Object and you don't need to do this explicity. I was also much more knowledgable in the subject of object oriented programming in general. As controversial a topic as test driven development is these days in the Ruby community, I have to credit it with my understanding of OOP and specifically concepts like encapsulation and designing interfaces. I was also very interested in DSLs at the time and what makes one "better" than another.

With my new found skills, I decided to rewrite ClassyEnum from the ground up and think about the experience from the perspective of someone who is using it for the first time. There were a few goals I had in mind: 1) It had to be simpler and more intuitive, 2) compatible with every major version of Rails, 3) future proof to require less changes when new versions of Rails came out, and 4) I looked it up, and it is indeed 'hairballs'.

At a time when my favorite gems were getting more and more bloated (ahem), I wanted to simplify. I cut out features such as built-in Formtastic support, and reduced the DSL to a single model declaration. I even declined pull requests adding new features I didn't want, despite the hard work that people put into their code. Telling someone why their idea sucks in a courteous way is not always easy.

I made the mistake of releasing 2.0 too soon and was not happy with the results. I ended up cutting even more out and releasing 3.0 three months later. I take semantic versioning seriously, and was not experienced enough to know when I was satisfied with the functionality.

The Latter Days

Fast forward another two years to today. I'm no longer working on the monolithic application I started back in 2009. These days I am contracting and typically work on one or two different Rails applications a month. However, for the last year nearly every single application I have worked on has included ClassyEnum.

According to rubygems.org it has been installed over 80,000 times and had 45 releases. Yet despite the history and popularity, only 38 issues have been opened in four years (all but two are closed). The reason I mention this is because I take pride in releasing stable code that is well tested and documented. I want it to be easy for new comers to pick up, and I take every bug report seriously.

I believe my strict policy of only adding features that have inherent value and cutting ones that do not has helped me sustain development on it. While new releases have slowed in recent years, its usage has only increased, and I see this as a sign of maturity and stability. I don't want to add features just for the sake of adding them because the person who has to maintain them is me.

I really like this quote from the Semantic Versioning 2.0 FAQ:

Having to bump major versions to release incompatible changes means you'll think through the impact of your changes, and evaluate the cost/benefit ratio involved.

I now spend a lot more time letting releases "ferment" before making them final. This is why we have betas and release candidates.

I encourage you to think about a project you're working on, whether it be open source or otherwise, and ask yourself how you can make the experience better for yourself and your fellow developers today, tomorrow, and well beyond its expected life.

My Year in Review

I've seen a few great posts today from people reflecting on some of their accomplishments and favorite things from 2013, so I was inspired to put something together myself. Instead of just listing things off, I thought it would be fun to go through my Twitter feed and pull out some of my favorite tweets. Some of them were accomplishments, others were fun things I did, and some were just ridiculous things that popped into my head. Hope you enjoy!

On Quitting My Job

The end of 2012 was not a great time for me. Lots of things were going well in my life, but I knew I was not happy at my current job. I wasn't learning anything, I was constantly frustrated, and to be honest, I was pretty depressed. I decided to quit my job and form a partnership with some of my coworkers doing contract web development through our own company.

This was exactly the change that I needed. Working remotely has had a hugely positive impact on my life. It's given me freedom and flexibility, challenged me with new and exciting projects, and just made me an overall happier person. I live in VT for the quality of life, and now I feel like I am able to enjoy it more than ever.

On Having a Sick Dog

The beginning of 2013 was an exciting time for me with the new job, however, my dog got really sick the week I started. We didn't know what was wrong with him for a few days. Then finally...

The poor guy had to spend the evening at the emergency vet recovering, making sure he didn't have any real damage from the pine cone. He did make a swift recovery though, and was back on his feet in no time.

On Feeling Better

Speaking of not feeling well, I gave up dairy in the fall of 2012. It wasn't something I wanted to do, but was something I needed to do. I had been feeling sick on and off for a number of years, and finally decided enough was enough, and gave it up entirely. 2013 was the first full year on a dairy free diet and I can truly say I've never felt this well in my entire life. I've lost a ton of weight and am cold pretty much constantly, but it's a small price to pay to actually feel well.

On Holding Babies

I feel like everywhere I look, someone is having a baby, and for whatever reason, people really like holding them. I had never held one before, so I wanted to see what all the fuss was about. No one told me what to do with it, and I figured you can't pet it like a cat, so I just tried my hardest not to drop it.

On Being Sad (Panda)

A friend/neighbor/former coworker of mine lost his dog in March. And by lost, I mean he died. I was sitting by Gordie's side during the final moments of his life, and it was one of the saddest things I've ever seen. This experience made me appreciate the things I have more than ever.

On Taking Vacations

My wife and I went on some fun vacations this year. Despite my immense fear of flying and rattlesnakes, my wife convinced me to fly out to Big Sur, CA where they apparently have rattlesnakes everywhere. It was like snakes on a plane, except the snakes weren't on the plane (that I knew of).

We also went to Bar Harbor, ME in the fall. We've been going there every year since our honeymoon, and plan to go back again in 2014. My wife and I are both originally from Cape Cod, so I think what attracts us here is the combination of mountains and ocean. Acadia National park a beautiful place!

On Selling Drugs

The series finale of Breaking Bad wouldn't be for another 4 months, but my neighbors were cooking up a storm of meth right down the street from me. I live in a quiet residential neighborhood just North of downtown Burlington, and never would have thought to see something like this. Turns out they had been cooking meth with their child in the apartment. Pretty fucked up shit.

On Receiving Tweet of the Week

The optimist in me got "tweet of the week" in Seven Days last summer. It wasn't my favorite tweet of the year, but I think it sums up the weather in VT pretty well. As I'm typing this, we're looking at a low of -14°F this week. I also thought up "ForeverAloneSquare", which was my response to people who tweet FourSquare checkins. Please stop doing this folks. If I wanted to know where you were, I'd use FourSquare.

On My Dog Turning 5

I love my dog. I mean the cats are great and all, but bonding with a dog has been one of the most rewarding experiences of my life. Sebastian turned 5 this summer and I'm hoping he makes it past my 40th birthday to see what I look like when I'm fatter and balder.

On Making Friends

I joke a lot about not having friends and being alone, but in reality I have a lot of really awesome people in my life. It wasn't a resolution of mine to make new friends or be more outgoing, but that was definitely one of the themes for me this year. The conference I organized was a huge part of that. I've also been involved in organizing many of the local software user groups and have met people through those as well. I am really looking forward to doing the conference in 2014, and can't wait to share the experience with everyone again.

Laughter is the Best Medicine (besides Percocet)

Life is too short to take seriously all the time. Here are a few of my favorite tweets that still make me chuckle.

Happy New Year and thanks for reading!

Have You Ever Had a Bill and Ted Coding Experience?

Have you ever had an experience where you started to write some code only to discover that the code already existed? "Woah" you think to yourself as you fire off "git blame" to figure out which one of your coworkers deserves a full-force high five. Low and behold, your own name shows up as the author, and your mind is blown because you have no memory of writing it.

You, my friend, just had a Bill and Ted Experience.

A Reflection on Organizing the Burlington Ruby Conference

Burlington Ruby Conference Banner

"Why are you organizing a conference?" - Someone asked me this question when we were just starting to plan for this year's Burlington Ruby Conference. I don't remember my exact response, but it was probably a long the lines of "Uhhhh I don't know, it's just something I want to do". I remember thinking "why wouldn't you want to do it?". I was really excited about it and it felt like one of those things I was supposed to do.

It turns out that doing it well takes a lot of hard work. I'm estimating that I spent over 200 hours on this year's event, not including the nights I dreamt about it. I'm not sure where that stands compared to other web developers who moonlight as conference organizers, but I'm guessing it's on the higher end of the spectrum. I can't picture many developers devoting that much time to something software-related that isn't coding. For me though, the payoff has been huge. While I haven't benefited financially, I've learned a lot about planning and budgeting, made some awesome friends, and had what I would consider one of the best experiences of my life.

It has been almost two weeks since the conference ended, so I wanted to jot down a few of my thoughts on it before they faded.

Goals for this year

This was the 2nd year for the Burlington Ruby Conference. I was not very involved last year, other than helping with a last minute scramble to try and find attendees. This year I wanted to be more involved. When we started planning back in January, we knew there were a few things that needed to change in order to make it an event we could continue to do. To understand what I mean, let's take a look at last year's conference by the numbers:

Last year:

  • Attendees: 75
  • Speakers: 7
  • Women: < 10%
  • Sponsors/supporters: 1
  • Budget: $12K

A conference with only 75 attendees and almost no sponsors is simply not sustainable. Including sponsors, our budget for last year's event was just over $12K. We did not plan on having a conference this small, but as most first-year organizers know all too well, things rarely go as planned. We had to cut back on a lot of things at the last minute, such as the venue size, and it was stressful for everyone involved. In order to make the conference sustainable, we knew we needed to increase our budget and attendance. We set a few goals for this year that we knew were ambitious, but would allow us to run the conference without having to forgo the things we felt were essential.

Our Goals for this year:

  • Attendees: 150
  • Speakers: 10
  • Increase female attendance + speakers
  • Sponsors: MOAR
  • Video recordings
  • Keep ticket prices low
  • Budget: $20K

We knew that increasing our attendance and budget by 2x was an ambitious goal. How were we going to get 150 people to come to Vermont for a software conference? How were we going to raise enough money to double our budget and be able to afford some of the nice to haves such as video recordings? How could we increase diversity?

It took a lot of hard work and creativity to reach these goals, and here are a few of the things we did to achieve them.

MOAR Speakers

Last year's conference did not have an official call for proposals. All of the speakers were personally invited. When we started thinking about getting speakers at this year's conference, we were sort of naive and wanted to avoid a formal CFP and just do invite-only again. We compiled a list of people that we wanted to speak at the conference and began reaching out to them. We had a couple 'Yes's, but most of them either said they were busy or just didn't reply. We were a little discouraged, so I tweeted at @devchix asking for help getting the word out. 15 minutes later, Ashe Dryden replied saying that she wanted to help us make the process more friendly to diverse groups. Ashe had written a blog post on how to increase diversity at your conference that I would consider the bible on this subject. She encouraged us to publish a diversity statement and code of conduct, organize a blind speaker selection process, as well as helped us organize a few Google hangouts to meet potential speakers.

The Google hangouts proved to be invaluable for finding great speakers. Two of my favorite speakers this year both happened to be in those hangouts. They seemed very excited about the conference, and since they made it through the blind proposal selection and into the final round, we decided to pick both of them. We ended up with 52 submissions and increased the total number of speakers from 10 to 12, though it didn't make the selection process any easier. For those who submitted and were not selected, I spent a few hours sending personalized emails delivering the bad news. A few people replied asking how they could improve their proposals, but unfortunately with 52 submissions and only 9 slots, it was really not about quality. This was definitely one of the hardest parts of the entire experience for me.

MOAR Sponsors

"Let's just get more sponsors". This was probably the most naive thought I had going into planning this year's conference. The thing about sponsors is that unless you have a direct connection or are an established event, it's pretty much impossible to find them. There are three reasons I can think of why someone would sponsor a conference. 1) They're hiring, 2) they're marketing a product, and/or 3) to support their community. As I mentioned in my previous blog post, we didn't have much traction from our local community in terms of financial support, but I thought it would be different if we reached out beyond our local community. Sadly this was not the case. If a company is selling a product or hiring, they want to know that their target audience will be in attendance. They need to know the conference is worth it for them. If they were basing their response on last year's conference, 75 attendees is not a lot of people to get in front of. After "cold emailing" a TON of companies and getting zero 'Yes's, it became apparent that we were wasting our time and would need to focus our attention on ticket sales. More on sponsorship in a bit...

MOAR Attendees

Going from 75 to 150 attendees is kind of a big deal. After making the decision to grow, I started to wonder, "is this too many people for our second year?". How do you even reach that many people? There are now close to 20 Ruby conferences in the U.S each year, and that number is still increasing. While having a high number of these regional conferences is great for the community, it puts a lot of pressure on the organizers to provide a unique experience. Why would someone fly across the country for a conference when they have one in their own region or state, or even their own city?

We needed to give people a reason to want to come. One of the nice things about Vermont is that it's a beautiful state and people love to vacation here. We recognized that in order to bring people to the conference, we'd need to focus on why we believed people should come. Our goal for Burlington Ruby is to build community and have fun. We see it as an excuse for people to come to Vermont for a weekend vacation, away from their normal, busy lives. Oh, and there just so happens to be a Ruby conference taking place.

I live in Vermont for its slower pace and quality of life. This laid back nature is evident in our schedule (long breaks) and the types of talks we had. We decided to focus our marketing on Vermont's beauty and not try and be something we weren't. We're a small conference focused on having a fun, relaxing weekend and making friends.

People seemed to take notice:

The history of the cat sponsors and Ruby toys

We also wanted to put on a conference that matched our personalities and felt like it belonged here in Burlington.

The cat sponsors and Ruby catnip toys were never something we set out to do. Early in the planning, we decided we wanted to do some sort of workshop to try and get people excited about Ruby and want to come to the conference. I reached out to Maureen McElaney who had just started a Burlington chapter of Girl Develop It. She was excited to meet with us and organize a workshop via GDI. During our initial conversation, the topic of sponsors came up, and Brett and I were discussing the lack of support we were getting from our local community. We started joking about how we were just going to have cats sponsor us, and two days later, the first official conference cats were born.

One month after the cats went live, and still no traction with sponsors, I was sitting there thinking about how great cats are, and decided we needed cat toys to go with the cat sponsors. I reached out to a local artist who had an Etsy store and asked if she would be interested in making Ruby shaped catnip toys that we could sell at the conference. I felt like an idiot and thought she would think I was crazy, but she got back to me literally 5 minutes later and said she was interested in making them. She went ahead and made a trial run, and after a week we had the first official Ruby conference catnip toys.

Burlington Ruby Conference catnip cat toys

We mailed out some of the toys to people who had helped us out so far, and figured it would be a good way to get the word out. Soon pictures and videos started popping up on Twitter, and we were getting a lot more traffic to our website. We don't really have any way of knowing, but I'm curious how many people came to the conference either directly or indirectly because of the cats.

Dropping sponsorship prices

We sold nearly half the total tickets during our initial early bird ticket sale (aka "Fresh Tracks"). Then things slowed down to a crawl. The good news was that we had already sold more tickets than we did the previous year. The bad news was that we still had 75 to go in order to reach our goal. We knew that if we sold enough tickets, we would at least be able to afford to put on the event and not lose a ton of money. Since we didn't have any traction with sponsors, we decided to drop the sponsorship prices to the point where we were basically selling the tickets in bulk. For slightly more than the individual cost of a ticket, you got to be a sponsor. Not a bad deal. This definitely worked in our favor as we started to have a few sponsors actually reaching out to us.

Ruby Workshop

We felt strongly that the Ruby workshop was important to have in conjunction with the conference. Burlington has a very small developer community, and last year we had very few Vermonters in attendance. As I mentioned ealier, we met with Maureen to plan out a Ruby training/workshop, and she took the idea and ran with it. The workshop happened one week before the conference and had around 25 participants. Other than maybe a free class, this was probably one of the best introductions to programming and Ruby that someone could get for under $100. Maureen put together the entire class, including connecting with the teacher Alex Chaffee who did an AWESOME job teaching the workshop. If you ever have a chance to buy either of these folks a beer, please do so.

Girl Develop It Burlington Ruby workshop

Scholarships

The idea for offering scholarships came from Carina Zona during one of the Google hangouts. It was something that we wanted to do, but didn't think we'd be able to make happen. Later, I found out about a new program through Ruby Central called the Opportunity Scholarship that would match up to $1,500. As the months went by, we were still getting the occasional cat sponsor, and by July had raised over $1K. At this point, we had also sold enough tickets to cover the conference expenses, and were able to set aside tickets to give to scholars. We ended up giving away 11 scholarships, most of which went to women who attended the Girl Develop It workshop, and this was their first tech conference.

Side note: If you're organizing a conference, I highly recommend taking advantage of the scholarship funds from Ruby Central. It's a great way to increase diversity, get the word out about your conference, raise a little money, and most importantly, give people an opportunity to attend a conference who would not have been able to otherwise.

Selling Out

During the last week before the conference, our headcount was 149 attendees. This included reselling a handful of tickets that we refunded earlier on. I wanted nothing more than to sell this last ticket to say that we had sold out. Sure, we could have just said that we had sold out, but that would have been lame. So finally it was friday night, 15 minutes before the kick off party started, and I got an email saying the last ticket had been sold.

The guy who bought the last ticket became an instant celebrity:

Since we're planning on keeping next year's attendance at 150 (including speakers), I'm hoping the fact that we sold out this year will create demand and make it easier to sell tickets next year.

Mission Accomplished

In the end, we reached or exceeded every single one of our goals. We had 150 attendees, nearly doubled the number of speakers, increased female attendance to over 20%, had a considerable increase in sponsors, and were able to hire Confreaks to come and film the talks.

This year:

  • Attendees: 150
  • Speakers: 12
  • Women: > 20%
  • Sponsors/supporters: 12
  • Confreaks: [✓]
  • Budget: $24K

This post discussed just a few of the things we did for this year's Burlington Ruby Conference that I felt were either unique to our event or contributed to our success. I don't think people will notice too many drastic changes next year as we'll likely focus on polishing things up and continuing to make it an exceptional experience for everyone. If you're interested in hearing more or have specific questions about conferences or cats, please reach out!

Thank you so much to everyone who made the event happen, especially Brett, my supportive wife, my mom who provided unlimited phone support, and my partners at Agilion. I am incredibly grateful to have had the experience and cannot wait to start organizing it again next year. See you at Burlington Ruby in 2014!