Earlier last year, I gave myself two challenges:
- write automation scripts in Ruby (instead of giving up on writing them in shell)
- use system debugging tools (strace, lsof, gdb, etc.) more often to figure out why programs are behaving some way
Of course, I was almost immediately stymied on the second one:
sudo dtruss -t write ruby -e "puts 'hi!'" Password: dtrace: failed to execute ruby: dtrace cannot control executables signed with restricted entitlements
dtruss is the dtrace-powered macOS-equivalent of
strace. It is very cool when it works. But. It turns out Apple has a thing that protects users from code injection hijinks, which makes dtrace not work. You can turn it off but that requires hijinks of its own.
I did end up troubleshooting some production problems via
lsof. That was fun, very educational, and slightly helpful. Would do again.
I did not end up using
gdb to poke inside any Ruby programs. On the whole, this is probably for the better.
I was more successful in using Ruby as a gasp scripting language. I gave myself some principles for writing Ruby automation:
- only use core/standard library; no gem requires, no bundles, etc.
- thus, shell out to programs likely to be available, e.g.
- if a script starts to get involved, add subcommands
- don’t worry about Ruby’s (weird-to-me) flags for emulating sed and awk; stick to the IRB-friendly stuff I’m used to
These were good principles.
At first I tried writing Ruby scripts as command suites via sub.
sub is a really cool idea, very easy to start with, makes discovery of functionality easy for others, and Just Works. You should try it some time!
That said, often I didn’t need anything fancy. Just run a few commands. Sometimes I even wrote those with bash!
But if I needed to do something less straightforward, I used template like this:
#!/usr/bin/env ruby HELP = <<-HELP test # Run the test suite test ci # Run test with CI options enabled test acceptance # Run acceptance tests (grab a coffee....) HELP module Test module_function def test `rspec spec` end # ... more methods for each subcommand end if __FILE == $0 cmd = ARGV.first case cmd when "ci" Test.ci # ... a block for each subcommand else Test.test end end
This was a good, friction-eliminating starting skeleton.
The template I settled on eliminated the friction of starting something new. I’d write down the subcommands or workflow I imagined I needed and get started. I wrote several scripts, delete or consolidated a few of them after a while, and still use a few of them daily.
If you’re using a “scripting” language to build apps and have never tried using it to “script” things I “recommend” you try it!