
Show HN: Rb – Turns Ruby into a command line utility - redka
https://github.com/thisredone/rb
======
Ajedi32
Interesting, so it's reading text from STDIN into an Array, then executing its
arguments as Ruby code in the context of that Array (or if you pass -l, in the
context of each line individually). So all the standard methods on the
[Array][1] (or [String][2]) class become accessible as top-level methods.

A few examples:

Capitalizing a line:

    
    
       echo hello world | rb -l capitalize
    

Printing only unique lines:

    
    
        printf 'hello\nworld\nhello\n' | rb uniq
    

Computing the sum of numbers in a table:

    
    
       printf '1\n2\n3\n' | rb 'map(&:to_i).sum'
    

Personally I think it's a really cool idea.

[1]: [https://ruby-doc.org/core/Array.html](https://ruby-
doc.org/core/Array.html)

[2]: [https://ruby-doc.org/core/String.html](https://ruby-
doc.org/core/String.html)

~~~
star-techate

        echo hello world | ruby -pe '$_.capitalize!'
    
        printf 'hello\nworld\nhello\n' | ruby -le 'puts STDIN.to_a.uniq
    
        ps | ruby -lane 'BEGIN { b = 0 }; b += $F[0].to_i; END { print "sum of PIDs: #{b}" }
    

select whatever column you want, to sum up.

Really what Ruby needs is a flag that adds some capabilities to NilClass, so
that these BEGIN blocks aren't needed.

~~~
ricardobeat
If this was an attempt at showing this tool is unnecessary, you just did the
opposite.

Each of your examples use different flags, inputs and print methods. The last
is particularly good at proving the point!

~~~
ashelmire
Each of those examples is doing different things. This rb script locks you
into two of the options.

------
star-techate
Command line tools in _10 lines of ruby_ (using this script):

    
    
        docker ps | rb drop 1 | rb -l split[1]
        docker ps -a | rb grep /Exited/ | rb -l 'split.last.ljust(20) + " => " + split(/ {2,}/)[-2]'
        df -h | rb 'drop(1).sort_by { |l| l.split[-2].to_f }'
    

Command line tools in _zero lines of ruby_ (using ruby):

    
    
        docker ps | ruby -lane 'next if $. == 1; print $F[1]
        docker ps -a | ruby -lne 'print $1 if /(Exited .*? ago)/'
        df | ruby -lane 'BEGIN { lines = [] }; lines.push [$F[4].to_i, $_] if $. > 0; END { lines.sort { |a,b|b[0] <=> a[0] }.each{|k| print k[1] } }
    

Bit of a pain as ++ is lacking, as is autovivication. a /bin/sort at the end
usually beats `<=>` for terseness.

~~~
shawn
In case it’s helpful, as someone who doesn't know ruby, it's hard to
understand. Not too readable. Split.last.ljust I just barely understood (left-
justify the last match), but I have no idea what the next part is even trying
to accomplish.

It's a cool script though.

~~~
star-techate
That's the author's oneliner. I don't know what that code is for either, which
is part of I rewrote it in the second set of examples.

It's normal for a oneliner to be somewhat inscrutable though, since they're
often extremely dependent on their inputs. Like if it's important to fetch the
3rd, 5th, and 7th column--why? You can't tell without the input.

------
pulisse
The command should process stdin in streaming fashion rather than slurping it
all at once:

    
    
      code = ARGV[0]
      STDIN.each_line do |l|
        puts l.instance_eval(code)
        STDOUT.flush
      end

~~~
DarkWiiPlayer
OP apparently wants to process an array of lines, not just each line
separately.

~~~
karmakaze
We need to be using lazy coolections.

~~~
dragonwriter
So:

    
    
      execute(STDIN.each_line, code)
    

which gives you a lazy enumerator, in place of:

    
    
      execute(STDIN.readlines, code)
    

which gives you an Array.

------
KeyboardFire
It makes me a little uncomfortable that they're using curl|bash for something
as simple as "put this 10-line script somewhere in your $PATH," especially
when the script involves sudo (to move into /usr/local/bin). Sure, it's easy
to inspect the script and see that it's not doing anything malicious, but it
makes install processes like this, where it'd be incredibly easy to, seem
normal.

~~~
Ajedi32
Yeah, and it's not even getting the code from an official source; it's coming
from a random bit.ly link [created by a contributor][1].

I've [submitted a PR][2] to inline the install script in the README.

[1]:
[https://github.com/thisredone/rb/pull/5](https://github.com/thisredone/rb/pull/5)

[2]:
[https://github.com/thisredone/rb/pull/8](https://github.com/thisredone/rb/pull/8)

~~~
redka
Thanks, merged

------
vinceguidry
The second I start wanting a bash pipeline, probably around the third pipe, I
scrap it immediately and move to using a text editor to write a script.
Because if I'm wanting a pipeline, I'm also going to want to store it, and
transmit it over the network, and manage it, and put it down and come back to
it later.

All things perfectly manageable inside a PORO. Bundler even has an inline
mode, so you can put everything in one file, close to your data, Bundler just
handles dep management under the hood like a boss. Check out bundler-inline.

Sure, you _can_ do all that with bash. But you can also make system() calls
with Ruby, with full string interpolation power tools. If you're a Rubyist,
and you want to do data analysis, this is the workflow you want.

~~~
barrkel
xargs -P and other ad-hoc parallelism tools usually stop me from leaving the
command line too quickly (e.g. <(), fork / join).

The other thing that keeps me back is scale. If everything fits in memory,
cool, but sometimes I need to sort or uniq something that won't.

~~~
derefr
sort(1) is a merge sort (specifically, an "external R-way merge")—and, as a
merge sort, it actually isn't constrained by memory. It spills its sorted
intermediates to disk as temp files! (And, in fact, it even _compresses_ each
intermediate, for the same reason a column store like Cassandra does.) It's
pretty optimal!

If you're wondering how this is at-all fast (and it is), in Linux at least
/tmp is a tmpfs, meaning that these tempfiles are actually ending up as memory
after all. They get "spilled to disk" in the sense that the memory associated
with the files spills to swap; but, importantly, tmpfs pages know that they're
from files, so they have much better swapping semantics than regular memory
for the case where you're writing the file as a stream, and then reading it as
a stream. (Basically, juggling streams under a tmpfs is equivalent in overhead
to managing memory when you've got it, and managing files when you don't, but
without any of the dev-time overhead of having to write code for both cases.)

------
perlperson

      $ docker ps | rb drop 1 | rb -l split[1]
      $ docker ps | perl -anE 'say $F[1] if $.>1'
    
    

perl solved this problem a long time ago, people

~~~
oblio
Yeah, just like eating dirt solved the problem of world hunger.

Look again at your code. I'm not 100% sure what the Ruby version does, but
once I do figure out the exact semantics of drop and split, it's going to be
waaaaaaaaaaaaaaaaay easier to remember, understand and modify the Ruby version
than the Perl one.

Your post comes off as something from reddit's /r/nottheonion, you're just
making the case against Perl for Perl-haters :)

~~~
shadowphex
The perl here is not really that crazy to remember.

-a autosplits each line by whitespace and puts each element into the array F (this was inspired by AWK).

-n loops through each line of the file, and -E executes the perl.

$. (NR in AWK) is the line number.

As others have noted, you can write the same thing in ruby on the command line
already with `ruby -ane 'puts $F[1] if $.>1'`

(Notice the similarities?)

If you write one liners in AWK, Perl, or Ruby often the "odd" variables look
more like useful shortcuts.

*edit You could also write the perl without any of the special variables, but it would be much more verbose, hence the special characters and flags.

------
meow_mix
What does this have over the default ruby?

cat your-file.txt | ruby -ne '$_.your_ruby_stuff'

Just syntactic sugar? (it does look cleaner!)

~~~
hboon
I've always found it quite painful to do string escaping when using `ruby -e`

------
sametmax
"pyped" does the same for python.

I though it was great, knowing python better than bash, plus it was portable
to windows.

But eventually I always end up using built in GNU tools.

Don't know why. It just rolls better.

------
code_duck
“With 10 lines of Ruby replace most of the command line tools that you use to
process text inside of the terminal.”

Well, it’s these 10 lines, plus an unlimited amount of Ruby that you have to
compose on the fly for each operation.

------
nerdponx
I have no idea what this is doing, but it sure looks interesting.

~~~
tptacek
It's using Ruby to replace Awk, the way Perl once tried to.

~~~
Annatar
Why the hell would anybody want to replace AWK? It’s super fast, extremely
light on resources, doesn’t have any dependencies, does auto memory management
and type inference, and is extremely powerful for large dataset processing,
not to mention easy to learn and program.

~~~
damagednoob
> easy to learn and program

I think that's relative and it certainly hasn't been my experience. If I
program in Ruby, Node, Python, etc for 8 hours of every day, it makes sense
that I would reach for that over a command line tool with a syntax that looks
a bit arcane. The best tool for the job sometimes is the tool you know best.

~~~
Annatar
It’s arcane only if you don’t know C. If you’re on a UNIX-like or a true UNIX
system, not knowing C will come to haunt you with a vengeance sooner or later.

------
dorianm
That's similiar to the ruby-each-line gem I created
[https://github.com/Dorian/ruby-each-line](https://github.com/Dorian/ruby-
each-line)

------
tobyhinloopen
First I thought: This is stupid. But then I watched the examples on github and
here in the comments, and I changed my mind: It's pretty neat :) I like it.

------
binaryphile
A bashtardization:

    
    
        rb () {
          [[ $1 == -l ]] && shift
          case $? in
            0 ) ruby -e "STDIN.each_line { |l| puts l.chomp.instance_eval(&eval('Proc.new { $* }')) }";;
            * ) ruby -e "puts STDIN.each_line.instance_eval(&eval('Proc.new { $* }'))";;
          esac
        }

------
robax
I'd love this for python as I'm not a big fan of Ruby. Does such a thing
exist?

~~~
schappim
Out of interest, why are you not a fan of Ruby?

~~~
bpanon
Right tool for the job; don't be a fan boy.

~~~
repsilat
As someone who prefers Python to Ruby, Ruby is better for this job because
there are so many more methods on Array/Enumerable than on Python list (or
Python's global functions.) And them all being methods makes life simpler too.

------
kimat
I found the last example hard to read so I made this fork :
[https://github.com/kimat/rb](https://github.com/kimat/rb)

------
codetheory
Cool.

------
codetheory
Nice

------
jaequery
its a pretty nifty idea

------
tigrezno
\- Reading all the lines into an array is a big error.

\- It's a bit unintiuitive because you don't really know what strings to
escape on the bash command line. In the example, some are escaped and others
aren't.

\- In the end I think this could be achieved with a simple bash function:

    
    
        $ rb() { ruby -lane "print \$_.instance_eval(\"$@\")"; }
        $ echo hello | rb capitalize
        $ Hello

