ruby how to pipe arguements to a bin - ruby

I have a ruby bin I'd like to pass information to in this fashion:
some_text | ./bin/my_ruby_bin
where some_text will be accessible by ARGV
is this possible with ruby + shell or am I taking the wrong approach here?

Here is simple solution that works for my cause, but it appears there are many ways to do this:
# ./bin/my_ruby_bin
#!/usr/bin/env ruby -n
puts "hello: #{$_}"
notice the -n flag
from command line:
echo 'world' | ./bin/my_ruby_bin
# => hello world
More on ruby -n
ruby -h
-n assume 'while gets(); ... end' loop around your script

Related

How to detect if a Ruby script is running through a shell pipe?

My question is similar to this one: How to detect if my shell script is running through a pipe?. The difference is that the script I’m working on is written in Ruby.
Let’s say I run:
./test.rb
I expect text on stdout with color, but
./test.rb | cat
I expect the color codes to be stripped out.
Use $stdout.isatty or more idiomatically, $stdout.tty?. I created a little test.rb file to demonstrate, contents:
puts $stdout.isatty
Results:
$ ruby test.rb
true
$ ruby test.rb | cat
false
Reference: https://ruby-doc.org/core/IO.html#method-i-isatty
Use IO#stat.pipe?.
IO#tty? returns true only on a TTY device.
Returns false for UNIX-style pipes (see "man 2 pipe").
$ echo "something" | ruby -e 'puts $stdin.stat.pipe?'
true
$ echo "something" | ruby -e 'puts $stdin.tty?'
false
$ ruby -e 'puts $stdin.tty?'
true

How to shirk print / puts in Ruby oneliners?

Consider oneliner
$ ruby -e 'puts 1 + 1'
which uses ruby as a command-line calculator. I would like to write the expression without puts. Is there a switch for it in ruby command?
It is impossible with ruby command line switches, but it’s easily achievable with shell:
⮀ cat /usr/local/bin/rubyoneliner
#!/bin/sh
ruby -e "puts $#"
⮀ rubyoneliner '1 + 1'
2
or with bash/zsh function.
There is no way to have an implicit print in ruby. However, you can shrink your oneliner a little more -> replacing puts by p and including the require in the shell command :
$ ruby -rsy -e 'p 42.kWh.in :MJ'
-rsy whould replace the require 'sy'.
Otherwise you can use the fact that the option -p, implicitely puts the $_ variable with something like (much longer however) :
$ printf whatever | ruby -rsy -pe '$_ = 42.kWh.in :MJ'
or uglier
$ printf whatever | ruby -rsy -pe 'sub("whatever", 42.kWh.in :MJ)'

String interpolation while running shell command through ruby?

Struggling with string interpolation while executing a shell command in ruby. Can you someone please help me identify what I'm missing here?
My one-liner ruby code follows redirects of any shortURL and returns the final URL. For example, this ruby code works perfectly fine.
curl -I https://bit.ly/1mJk8X7 | perl -n -e '/^Location: (.*)$/ && print "$1\n"'
It prints out the final URL.
I have a .txt file with a series of short URLs from which I'd like to derive a list of the final URLs. Say, it's called shortURLs.txt. I'm using IO.foreach to loop through each line in the file, but I don't know what I'm doing wrong to bring the variable 'x' into the ruby command. This is my first time working with string interpolation, and I've tried various combinations of it, but no luck yet.
IO.foreach("shortURLs.txt") { |x| system "curl -I #{x} | perl -n -e '/^Location: (.*)$/ && print \"$1\n\"' >> finalURLs.txt" }
I get an error message around the pipe '|' symbol:
sh: -c: line 1: syntax error near unexpected token|'
sh: -c: line 1: | perl -n -e '/^Location: (.*)$/ && print "https://bit.ly/1mJk8X7'
Other threads have been useful about string interpolation and running shell commands through ruby.
In order to pass two commands to a shell, you should run the system command twice (check method 8 in this post)
require 'shell'
sh = Shell.new
IO.foreach("shortURLs.txt") { |x| sh.system("curl -I #{x}") | sh.system("perl -n -e '/^Location: (.*)$/ && print \"$1\n\" ' ") >> "finalURLs.txt" }
IO.foreach yields the lines including the new line at the end so you're actually executing
curl -I https://bit.ly/1mJk8X7
| perl -n -e ...
Which is why you get the syntax error. You could use strip to remove the new line from. I think the \n in the call to print will also get substituted before the string is passed to system. You may be interested in shellwords which has functions for escaping strings before passing them to a shell.
You could of course dodge the issue entirely and use ruby to get the redirect locations
require 'net/http'
require 'uri'
IO.foreach("shortURLs.txt") do |url|
puts Net::HTTP.get_response(URI.parse(url))["Location"]
end

Is it possible to get the uninterpreted command line used to invoke a ruby script?

As per the title, is it possible to get the raw command line used to invoke a ruby script?
The exact behaviour I'm after is similar to SSH when invoking a command directly:
ssh somehost -- ls -l
SSH will run "ls -l" on the server. It needs to be unparsed because if the shell has already interpreted quotes and performed expansions etc the command may not work correctly (if it contains quotes and such). This is why ARGV is no good; quotes are stripped.
Consider the following example:
my-command -- sed -e"s/something/something else/g"
The ARGV for this contains the following:
--
sed
-es/something/something else/g
The sed command will fail as the quotes will have been stripped and the space in the substitution command means that sed will not see "else/g".
So, to re-iterate, is it possible to get the raw command line used to invoke a ruby script?
No, this is at the OS level.
You could try simply quoting the entire input:
my-command -- "sed -e\"s/something/something else/g\""
In Ruby, this could be used like this:
ruby -e "puts ARGV[0]" -- "sed -e\"s/something/something else/g\""
(output) sed -e"s/something/something else/g"
Or, in a file putsargv1.rb (with the contents puts ARGV[1]):
ruby -- "putsargv1.rb" "sed -e\"s/something/something else/g\""
(output) sed -e"s/something/something else/g"
Your example is misguided. ssh somehost -- ls * will expand * on localhost (into e.g. ls localfile1 localfile2 localfile3), then execute that on the remote host, with the result of lots and lots of ls: cannot access xxx: No such file or directory errors. ssh does not see the uninterpreted command line.
As you said, you would get -es/something/something else/g as a single parameter. That is exactly what sed would get, too. This is, in fact, identical to what you get if you write -e"s/something/something else/g" and to "-es/something/something else/g", and to -es/something/something\ else.
Using this fact, you can use Shellwords.shellescape to "protect" the spaces and other unmentionables before handing them off to an external process. You can't get the original line, but you can make sure that you preserve the semantics.
Shellescape on the args worked but didn't quite mimic SSH. Take the following example (see below for test.rb contents):
ruby test.rb -- ls -l / \| sed -e's/root/a b c/g'
This will fail using the shellescape approach but succeed with SSH. I opted for manually escaping quotes and spaces. There may be some edge cases this doesn't capture but it seems to work for the majority of cases.
require 'shellwords'
unparsed = if ARGV.index('--')
ARGV.slice(ARGV.index('--') + 1, ARGV.length)
end || []
puts "Unparsed args: #{unparsed}"
exit if unparsed.empty?
shellescaped = unparsed.map(&Shellwords.method(:shellescape)).join(" ")
quoted = unparsed.map do |arg|
arg.gsub(/(["' ])/) { '\\' + $1 }
end.join(" ")
puts "Shellescaped: #{shellescaped}"
puts `bash -c #{shellescaped.shellescape}`
puts "Quoted: #{quoted}"
puts `bash -c #{quoted.shellescape}`
Thanks for your answers :)

Ruby: execute shell command 'echo' with '-n' option

I'd like to run the following shell command from Ruby, which copies a string into the clipboard (on OS X), 'n' is suppressing the line break after the string caused by echo:
echo -n foobar | pbcopy
—> works, fine, now the clipboard contains "foobar"
I've tried the following, but all of them always copy the option '-n' as well into the clipboard:
%x[echo -n 'foobar' | pbcopy]
%x[echo -n foobar | pbcopy]
system "echo -n 'foobar' | pbcopy"
system "echo -n foobar | pbcopy"
exec 'echo -n "foobar" | pbcopy'
`echo -n "foobar" | pbcopy`
IO.popen "echo -n 'foobar' | pbcopy"
What is the proper way to achieve this?
Your problem is that -n is only understood by the bash built-in echo command; when you say %x[...] (or any of your other variations on it), the command is fed to /bin/sh which will act like a POSIX shell even if it really is /bin/bash. The solution is to explicitly feed your shell commands to bash:
%x[/bin/bash -c 'echo -n foobar' | pbcopy]
You will, of course, need to be careful with your quoting on whatever foobar really is. The -c switch essentially tells /bin/bash that you're giving it an inlined script:
-c string
If the -c option is present, then commands are read from string.
If there are arguments after the string, they are assigned to the positional
parameters, starting with $0.
Because echo behaves differently in different shells and in /bin/echo, it's recommended that you use printf instead.
No newline:
%x[printf '%s' 'foobar' | pbcopy]
With a newline:
%x[printf '%s\n' 'foobar' | pbcopy]
You might be reinventing a wheel.
IRB_Tools and Utility_Belt, which are both used to tweak IRB, provide an ability to use the clipboard. Both are collections of existing gems, so I did a quick search using gem clipboard -r and came up with:
clipboard (0.9.7)
win32-clipboard (0.5.2)
Looking at RubyDoc.info for clipboard reveals:
clipboard
Access the clipboard and do not care if the OS is Linux, MacOS or Windows.
Usage
You have Clipboard.copy,
Clipboard.paste and
Clipboard.clear
Have fun ;)
EDIT: If you check the source on the linked page, for the Mac you'll see for copy:
def copy(data)
Open3.popen3( 'pbcopy' ){ |input,_,_| input << data }
paste
end
and for paste you'll see:
def paste(_ = nil)
`pbpaste`
end
and clear is simply:
def clear
copy ''
end
Those should get you pointed in the right direction.
This might look like an ugly workaround, but I'm pretty sure it'll work:
Create an executable file called myfoobar.sh containing the line you want to execute.
#! /bin/sh
echo -n foobar | pbcopy
Then invoke that file from ruby.
Use the sutil for correct setup:
$ ssh-copy-id user#host

Resources