Ruby: A great language for shell scripts
Ruby is praised for shell scripting due to its features like calling external commands, handling status codes, using types, functional constructs, regex matching, threading, and file operations. It's recommended for complex scripts alongside Bash.
Read original articleRuby is highlighted as a powerful language for writing shell scripts, offering features that make it a compelling alternative to Bash and Python. The article emphasizes Ruby's ability to call external commands easily using backticks, handle status codes effectively, utilize types for safety, implement functional constructions like map and reduce, perform regex matching seamlessly, manage threads effortlessly, and conduct file and directory operations intuitively. The author suggests considering Ruby for complex scripts while acknowledging the continued relevance of Bash. The article concludes by advocating for Ruby as a comprehensive and user-friendly option for shell scripting tasks.
Related
Smalltalk syntax in 7 minutes [video]
The YouTube video explains Smalltalk syntax, emphasizing readability and message-based object interaction. It covers keywords, arrays, closures, and method execution in Pharo Smalltalk, providing a practical example and additional learning resources.
Lessons Learned from Scaling to Multi-Terabyte Datasets
Insights on scaling to multi-terabyte datasets, emphasizing algorithm evaluation before scaling. Tools like Joblib and GNU Parallel for single machine scaling, transitioning to multiple machines, and comparing performance/cost implications. Recommendations for parallel workloads and analytical tasks using AWS Batch, Dask, and Spark. Considerations for tool selection based on team size and workload.
As you learn Forth, it learns from you (1981)
The Forth programming language is highlighted for its unique features like extensibility, speed, and efficiency. Contrasted with Basic, Forth's threaded code system and data handling methods make it versatile.
Elixir Gotchas
The article highlights common pitfalls in Elixir programming, including confusion between charlists and strings, differences in pattern matching, struct behavior, accessing struct fields, handling keyword lists, and unique data type comparisons.
Start all of your commands with a comma (2009)
The article discusses creating a ~/bin/ directory in Unix to store custom commands, avoiding name collisions with system commands by prefixing custom commands with a comma. This technique ensures unique, easily accessible commands.
Also ruby is great in allowing complexity to grow smoothly, no sudden hiccups. You start with just one line (everything goes into module main implicitly), extend it to a single-file script, require some built-in libraries, then add a module or helper class in the same file, and only then maybe extract those files to required files, add gems, whatever. No boilerplate whatsoever, no jumps, no big rewrites.
meanwhile, a lot of tooling nowadays is written in Go, and I have no idea why, it's not friendly for os manipulation at all, and number crunching power is not needed in many, many tasks of that sort.
a) put a `binding.irb` (or `binding.pry`) in any rescue block you may have in your script - it'll allow you to jump in and see what went wrong in an interactive way. (You'll need a `require 'irb'` in your script too, ofc)
b) I always use `Pathname` instead of `File` - it's part of the standard library, is a drop in replacement for `File` (and `Dir`) and generally has a much more natural API.
c) Often backticks are all you need, but when you need something a little stronger (e.g. when handling filenames with spaces in them, or potentially hostile user input, etc), Ruby has a plethora of tools in its stdlib to handle any scenario. First step would be `system` which escapes inputs for you (but doesn't return stdout).
d) Threads in Ruby are super easy, but using `Parallel` (not part of the stdlib) can make it even easier! A contrived example: `Parallel.map(url_list) { |url| Benchmark.measure { system('wget', url) }.real }.sum` to download a bunch of files in parallel and get the total time.
MacOS has Ruby 2.6 installed by default which is perfectly serviceable, but it's EOL and there are plenty of features in 3+ that make the jump more than worthwhile.
I've mostly been in the Python ecosystem for the past few years and the LSP investment from Microsoft has really shown. Rich Python support in VSCode is seamless and simple. Coming back to Ruby after that caught me off guard - it feels like I'm writing syntax-highlighted plain text. There's an LSP extension from Shopify, but it's temperamental and I have trouble getting it working.
Editor support isn't everything (the actual language design is still the most important), but it definitely affects how eager I am to use it. I basically never choose Ruby over Python given the option, which is too bad. Ruby's a cool language!
What's between the backticks is not even portable; the commands rely on an operating-system-specific command interpreter.
> puts `ls`.lines.map { |name| name.strip.length } # prints the lengths of the filenames
Fantastic example, except for the commandment violation: "thou shalt not parse the output of 'ls'"!
You really want to read the directory and map over the stat function (or equivalent) to get the length.
2> (flow "."
open-directory
get-lines
(mapcar [chain stat .size]))
(727 2466 21 4096 643 16612 5724 163 707 319 352135 140 51 0 4096
114898 1172428 1328258 4096 4096 4096 29953 4096 4096 0 27 4096
4096 35585 8450 968 40960 14610 4096 14 755128 1325258 4096 17283
218 471 104 4096 99707 1255 4096 129 4096 721 9625 401 15658
4096 235 98 1861 664 23944 4286 4096 1024 0)
Another common pattern I see is people using embedded ruby in a shell script. It does make it a little harder to read/understand at a glance, but it's nice for being able to do most simple things in sh but drop into ruby for things that sh/bash suck at.
That said, I get a feeling that the people that joined once we'd added stuff outside of the Rails monolith and don't know/use Ruby are...not big fans.
Small nit: your note in Feature 4 is actually supposed to be in Feature 5, I assume.
So for me, "is it installed in the base distribution" is the difference between being able to start immediately no matter which box I'm concerned with, and spending months trying to upstream a new program to be installed with our OS image team.
I took a look around a vanilla Debian 12 box, and didn't see Ruby in there [1]. So, sadly, although I really like the way Ruby looks, I'm going to have to stick with Bash and Python 3 for the hard stuff.
[1]: https://hiandrewquinn.github.io/til-site/posts/what-programm...
For me, when Bash ain't enough I upgrade to the Project language (PHP, Python, JS, etc). For compiled project I reach for LSB language (Perl, Python) before introducing a new dependency.
Luckily, I've found that Perl has most of the best features of Ruby, and it's installed everywhere. It's time to Make Perl Great Again.
You mention using threads and regex match global variables in the same write up. Please use the regex match method response instead of the $1 variables to save yourself the potential awful debugging session. It even lets you access named capture groups in the match response using the already familiar Hash access API. Example: https://stackoverflow.com/a/18825787
In general, just don’t use global variables in Ruby. It’s already SO easy to move “but it has to be global” functionality to static class methods or constants that I’ve encountered exactly zero cases where I have NEEDED global variables. Even if you need a “stateful constant” Ruby had a very convenient Singleton mixin that provides for a quick and easy solution.
Besides, if you actually WERE to take advantage of them being global VARIABLES (reassigning the value) I would confidently bet that your downstream code would break, because I’m guessing said downstream code assumes the global value is constant. Just avoid them, there’s no point, use constants. This applies to any language TBH, but here we’re talking about Ruby :)
People use it a lot less these days, for a lot of reasons, some better than others. I myself do simple stuff in bash, and pull out some Python for more complex scripting. My Perl chops have withered, last time I was paid to use it was 21 years ago, but it really is a great scripting language, and you'll find it installed on a great deal more systems than Ruby.
One of these days I'll give Raku a spin, just for old time's sake.
For example: Ruby has no built-in for "call a subprocess and convert a nonzero exit status into an exception", ala bash `set -e`. So in many of my Ruby scripts there lives this little helper:
def system!(*args, **kwargs)
r = system(*args, **kwargs)
fail "subprocess failed" unless $?.success?
r
end
And I can't ask "is this command installed" in an efficient built-in way, so I end up throwing this one in frequently too (in this instance, whimsically attached to the metaclass of the ENV object): class << ENV
def path
@path ||= self['PATH'].split(':').map{ |d| Pathname.new(d) }
end
def which(cmd)
cmd = cmd.to_s
self.path.lazy.map{ |d| d + cmd }.find{ |e| e.file? && e.executable? }
end
end
I have other little snippets like this for:• implicitly logging process-spawns (ala bash `set -x`)
• High-level wrapper methods like `Pathname#readable_when_elevated?` that run elevated through IO.popen(['sudo', ...]) — the same way you'd use `sudo` in a bash script for least-privilege purposes
• Recursive path helpers, e.g. a `Pathname#collapse_tree` method that recursively deletes empty subdirectories, with the option to consider directories that only contain OS errata files like `.DS_Store` "empty" (in other words, what you'd get back from of a git checkout, if you checked in the directory with a sensible .gitignore in play)
...and so forth. It really does end up adding up, to the point that I feel like what I really want is a Ruby-like language or a Ruby-based standalone DSL processor that's been optimized for sysadmin tasks.
Unbelievably easy to read, and, with rspec, it is stupid easy to write tests for. No need to fuss with interfaces like you do with Golang; yes, that is the right thing to do, but when you need to ship _now_, it becomes a pain and generates serious boilerplate quickly.
I've switched to Golang for most things these days, as it is a much safer language overall, but when shell scripts get too hard, Ruby's a great language to turn to.
irb
to bring up the ruby interpreter and try out the code in the article.This is probably easier.
A scripting language needs a way to declare dependencies in a locked-down way, inside of the script that requires them. They must be portable across platforms.
Nice calling syntax though.
One can mitigate the problem somewhat using the `--disable-gems` flag, but that's not a good general solution.
As a Python guy I found the setup for this sort of CLI too really refreshing!
Too many ways to do the same thing.
Not packaged by default on most Linux.
Monkey patching makes things even harder to debug.
Python > Lua > PHP > Ruby > JavaScript > Perl > Bash
If you enjoy it, more power to you. However, Python is everyone's second favorite or least favorite language, and it runs laps around Ruby any day. Then there's Go if you need some extra oomph!
Language is just a tool, anything and everything works if you love it enough
Related
Smalltalk syntax in 7 minutes [video]
The YouTube video explains Smalltalk syntax, emphasizing readability and message-based object interaction. It covers keywords, arrays, closures, and method execution in Pharo Smalltalk, providing a practical example and additional learning resources.
Lessons Learned from Scaling to Multi-Terabyte Datasets
Insights on scaling to multi-terabyte datasets, emphasizing algorithm evaluation before scaling. Tools like Joblib and GNU Parallel for single machine scaling, transitioning to multiple machines, and comparing performance/cost implications. Recommendations for parallel workloads and analytical tasks using AWS Batch, Dask, and Spark. Considerations for tool selection based on team size and workload.
As you learn Forth, it learns from you (1981)
The Forth programming language is highlighted for its unique features like extensibility, speed, and efficiency. Contrasted with Basic, Forth's threaded code system and data handling methods make it versatile.
Elixir Gotchas
The article highlights common pitfalls in Elixir programming, including confusion between charlists and strings, differences in pattern matching, struct behavior, accessing struct fields, handling keyword lists, and unique data type comparisons.
Start all of your commands with a comma (2009)
The article discusses creating a ~/bin/ directory in Unix to store custom commands, avoiding name collisions with system commands by prefixing custom commands with a comma. This technique ensures unique, easily accessible commands.