This is part 2 of a five-part series:
- Part One has examples for common daily tasks in Git.
- Part Two, similar examples for Rails.
- Part Three, miscellaneous cases.
- Part Four dissects an example of a failed attempt at a useful script.
- Part Five concludes with a brief discussion of when to use Bash as opposed to some other scripting language.
TL;DR for Part 2:
- You don’t have to be a Bash or
sed
orawk
magician to make useful scripts; even knowing only one feature of each of these tools gives you a great deal of power basename
anddirname
are very handy- Prefer absolute paths to relative paths
- Use one-liners to make something easy to remember for things hard to remember
Rails
rails-server
At Beezwax, devs might have three or four projects they’re actively working on in a given month. When stopping and starting lots of different Rails servers, sometimes I wind up with orphan Spring processes that might block starting the server or cause other problems (amusing: note in the change history for the linked github issue, the original title was “You’re breaking my laptop”). Or I might have a Rails server running on 3000 in another terminal that I forgot about.
So I made a script that quickly checks (and if desired cleans up) those processes before trying to start the Rails server.
This script is given in its entirety but broken up with my comments between sections.
#!/bin/bash port="3000" if [[ ! -z "$1" ]]; then port="$1" fi ip="127.0.0.1" if [[ ! -z "$2" ]]; then ip="$2" fi
This is straightforward — the script can be called with optional anonymous arguments for port and IP address with reasonable defaults.
while read -r -u 3 proc; do pid="$(echo "$proc" | awk '{print $2;}')" echo "$proc" echo -n "kill $pid? [y/N] " read -r killit if [[ "$killit" == 'y' ]]; then kill -9 "$pid" else echo "port in use" exit 1 fi done 3< <(lsof -n -P -i:"${port}" | grep LISTEN)
Line 24 redirects lsof
output for any process listening on the requested port to file descriptor 3 which is read by the while
loop.
Why file descriptor 3, I couldn’t say, but there was probably a good reason when I wrote the script; maybe some interaction with the loop internally using 1 and 2?
Inside the loop, it awk
s out the pid, echo
s the entire lsof
line, and asks me if I want to kill that.
awk
is a programming language, but the only thing I know how to do with it is print individual columns from output. Many of these examples could be quite simpler if I were an awk
guru, and I’m okay with that — I’ll do the best I can with the tools I have, and learn new features as I need them (or, more likely, when a friend or workmate points something out).
If I respond to the prompt with 'y'
it kills the process.
This is a dev script, not a production distribution. I don’t need to test for ‘Y’ or ‘yes’ etc., ‘y’ is fine. There’s an example of more complete testing of input in Part 5.
On line 19, kill -9
is a little aggressive, but this is a dev environment; we can play fast and loose.
If I respond with a blank or enter anything other than 'y'
, I’m helpfully reminded that the port is in use and the script exits.
while read -r -u 3 proc; do pid="$(echo "$proc" | awk '{print $1;}')" echo "$proc" echo -n "kill $pid? [y/N] " read -r killit if [[ "$killit" == 'y' ]]; then kill -9 "$pid" fi done 3< <(ps -a | grep spring | grep -v grep | sed -e 's/[0-9]*://')
Next we do the same thing for anything that looks like a Spring process.
The grep -v
is so grep
doesn’t match itself, and the bit hacked out with sed
is the line number added to ps
output because my grep
is aliased to grep -n
:
$ ps -a | grep tail 5:18536 ttys000 0:00.01 grep -n tail 8:18512 ttys001 0:00.15 tail -f log/error_log
You probably want to keep the grep -v grep
but the substitution for the line numbers is likely just me.
TODO: refactor those loop bodies into a function (and maybe doing that would remove the need for the while
loop to read on file descriptor 3?)
cmd="rails server --port=${port} --binding=${ip}" echo "$cmd" $cmd
And finally, try to start the rails server. I like to save commands I’m going to run as a string and output the content of that string. It makes debugging easier.
link-configs
If you’re like me, you every now and then screw up a local repository to the point you just need a fresh checkout. But if you’re doing things right, database.yml
and secrets.yml
don’t live in version control (please tell me they don’t live in version control!).
I have a directory at ~/dev/beezwax/configs
where I store these instead. When I start a new project, I put those files there as ~/dev/beezwax/configs/my_project_database.yml
and ~/dev/beezwax/configs/my_project_secrets.yml
, then I can run this script any time I get so messed up that the easiest way forward is rm -dfr my_project && git clone ssh://git@gitlab.beezwax.net/my_project
.
#!/bin/bash app_name="$(basename "$(pwd)")" cmd="ln -s /Users/colin_l/dev/beezwax/configs/${app_name}_database.yml /Users/colin_l/dev/beezwax/${app_name}/config/database.yml" echo "$cmd" $cmd cmd="ln -s /Users/colin_l/dev/beezwax/configs/${app_name}_secrets.yml /Users/colin_l/dev/beezwax/${app_name}/config/secrets.yml" echo "$cmd" $cmd
basename
and its friend dirname
are super handy tools in your bash toolbox:
$ which rails /Users/colin_l/.rvm/gems/ruby-2.3.1/bin/rails $ basename $(which rails) rails $ dirname $(which rails) /Users/colin_l/.rvm/gems/ruby-2.3.1/bin
Line 2 gets the name of the project to use for the symlinks by getting basename
of the current working directory:
$ pwd /Users/colin_l/dev/beezwax/foo $ basename "$(pwd)" foo
Note the use of absolute paths in the ln
commands. It’s way harder to screw up absolute paths than relative paths, especially in cron jobs and with symlinks, so absolute paths are my default usage.
model-diagram
Sometimes it’s helpful to get a visual representation of your data model, and the railroady gem can do that for you. It has dependencies on dot
and neatographviz
; both available on Homebrew and MacPorts.
#!/bin/bash railroady -M | neato -Tjpg > model.jpg
Here’s some example output for a very small project:
Obviously for larger projects, they’re much bigger and more complicated. If your project uses the PaperTrail gem on a lot of models, you’ll get a lovely flower shape with PaperTrail in the center and your models as oddly-sized petals.
This is another example of making something easy to remember (model-diagram
) for something hard to remember (railroady -M | neato -Tjpg > model.jpg
)
On to Part 3: Other Examples
One thought on “Simple Bash Scripts for Lazy People | Part 2: Rails”