In a Ruby gem, is it possible to have executables written in other languages? - ruby

I have a gem which has an executable in bin/. A wildcard is used in the gemspec:
s.executables = Dir["bin/*"].map &File.method(:basename)
If I have a ruby file at bin/my_file which had chmod a+x run, then it's found in the PATH in bash:
#!/usr/bin/env ruby
puts "working ruby script"
However I have another executable written in coffeescript at bin/my_file.coffee:
#!/usr/bin/env coffee
console.dir "printing from coffee file"
When I run my_file.coffee in shell, it tries to read this script with ruby instead of coffeescript.
How can I run this executable with coffeescript as intended?
As it stands, I'm using a workaround which is to wrap my coffee code as a string in my ruby app and then passing it to coffee -e and capturing the output.
Also, looking at this RubyGems issue shows the official explanation:
RubyGems is not meant to support arbitrary executables.
OK. So is there an alternative besides wrapping code in heredoc?
Is there a good reason this restriction is in place?

I guess it's simply in the specification of RubyGems:
https://github.com/zzak/rubygems/commit/709c5aae7ffd9958cc2ea89dc2caf6b7e02c56b7
+ # Executables included may only be ruby scripts, not scripts for other
+ # languages or compiled binaries.
+ #

Related

How to write a Ruby gem that can be invoked without the word 'ruby' preceding it

I would like to write a CLI application wrapped into a Gem that can be invoked the same way git commands are invoked, or gem commands. Eg when running say "git clone " you don't need to precede it with 'ruby'. However, the tutorials and articles I've seen so far about writing gems, don't show this. The examples either require you to run your gem through irb, with appropriate requires, or you run it like 'ruby '. This is not what I want. If you know of any tutorials that cover this, then that would be great.
Thanks.
The "#!" line at the start of a script tells your shell which executable to execute the script with. In this case, it tells it to find the Ruby executable from the environment and give the script to it for execution.
By means of example, I have a file called "hi", with the following:
#!/usr/bin/env ruby
puts "hi!"
I make it executable:
$ chmod a+x hi
Then I can execute it directly, without explicitly invoking the Ruby interpreter:
$ ./hi
hi!
Per the tutuorial you would simply provide such a file which requires your gem and whatnot, and provide it in the executables property of your gemspec:
Gem::Specification.new do |s|
# ...
s.executables << 'hi'
When the gem is installed, the hi script would be installed into a location discoverable on the path, so you could then invoke it.

Ruby script: shebang with absolute path to ruby not working

I am using Mac OS X. I have two versions (2.1.5 and 2.0.0) of Ruby installed. The former installed at /another/.path/to/ruby (there is a dot before "path" to mimic the fact that the path contains a dot-headed directory in between), in addition to the default system one (version 2.0.0) at /usr/bin/ruby. I used rbenv to install Ruby.
After I manually set the PATH environment variable so the default ruby command will be found in another directory: /another/.path/to/ruby. Now I check
which -a ruby
It is using correct ruby first, as output.
/another/.path/to/ruby
/usr/bin/ruby
Now I create a script, rbs, with the first line of shebang specifying the ruby to use.
#!/usr/bin/env ruby
puts 'hey there'
Then I run
./rbs
it outputs 'hey there'. Good. Meanwhile, the Ruby is using the correct version.
/usr/bin/env ruby --version
as well as
ruby --version
both output 2.1.5. So it does great job to use the new version.
However, here is where the problem occurs: now I update rbs file to be:
#!/another/.path/to/ruby
puts 'hey there'
Note that I updated the shebang to use the absolute path to the desired ruby. then I run
./rbs
It outputs:
./rbs: line 2: puts: command not found
which is too weird;
but if I run
ruby ./rbs
it outputs 'hey there' as normal. It looks like the shebang works perfect using /usr/bin/env ruby, but not for absolute path for the newly install ruby?
Why is this? Is there a way to fix it so the updated script can still work by typing the following?
./rbs
Thanks!
The puts: command not found message indicates that your script is not being run by Ruby, but by the shell instead. So first, I would double-check your shebang line's syntax and path.
Second, note that rbenv uses shims that dynamically find and run the right version of ruby (and associated programs like gem, etc). But the shims are scripts, and scripts can't themselves be shebang interpreters; you have to find and use the actual path to the ruby executable (as output by rbenv which ruby).
On the other hand, since /usr/bin/env is an executable, you can always use something like #!/usr/bin/env ruby, which will work even if the ruby it finds in the path is itself a script.
I can't comment, (otherwise I'd add as a comment) but I think its worthwhile to add that the
#!/usr/bin/env ruby
MUST be the first line of the file. This tripped me up for a while.
source

Ruby Script to Command Line Tool

I am trying to make a command line tool. I already have a ruby script (one file). But I want to project it as a normal command line command.
Right now I have to go into the directory where the script is and type ruby script.rb for it to function but i want to make a command such as script [option] from directory and it should process the required option in the script.
Do I need to make an independent ruby gem for this? I have read about some gems like thor and commander but I am not able to use them properly.
How can I make this command line tool?
PS: An example can be the twitter gem and a command line tool 't' which is also a gem.
Ruby, because it's a general-programming language, makes it easy to create command-line scripts. Here's a basic script you can build upon:
#!/usr/bin/env ruby
require 'optparse'
args = {}
OptionParser.new do |opt|
opt.on('-i', '--input FILE', 'File to read') { |o| args[:file_in] = o }
opt.on('-o', '--output FILE', 'File to write') { |o| args[:file_out] = o }
end.parse!
abort "Missing input or output file" unless (args[:file_in] && args[:file_out])
File.write(args[:file_out], File.read(args[:file_in]))
Here's what's happening:
#!/usr/bin/env ruby is commonly called a "bang line". The shell will look for this line at the top of a file to determine what application can read the file and execute/interpret it. env is an application that will look through the user's PATH environment variable and return the first Ruby found as the Ruby to execute the script. Using this makes the script work with Rubies in the normal /usr/bin, /usr/local/bin or when managed by rbenv or RVM.
require 'opt parse' pulls in Ruby's command-line parser class OptionParser, which makes it easy to set up traditional flags, such as -i path/to/file/to/read, -o path/to/file/to/write, or long parameters, like --input or --output. It also automatically supplies the -h and --help flags to return formatted help text for the script. OptionParser is a bit of a learning-curve, so play with the complete example and you'll figure it out.
The rest should be pretty self-explanatory.
Traditionally, executables that are installed by the system go in /usr/bin. Executables we write, or add, go in /usr/local/bin, and I highly recommend sticking with that.
Some OSes don't automatically supply an entry for /usr/local/bin in the PATH, so you might need to modify your PATH setting in your ~/.bashrc, ~/.bash_profile or ~/.profile to allow the shell to locate the script.
Executable scripts need to have their executable flag set: chmod +x /path/to/executable is the basic command. See man chmod for more information.
I tend to leave the script's extension in place; Ruby scripts are "foo.rb", Python are "bar.py", etc. I do that because I prefer to have that extension as a hint of the language it's written in, but YMMV. The extension isn't necessary so go with what works for you.
Beyond all that, you might want to provide logging output, or output to the system's syslog. In the first case use Ruby's built-in Logger class, or the Syslog class in the second case.
Actually there's two great gems for command line apps in Ruby.
First is methadone which is for simpler command line apps.
Another is gli which is for apps with multiple commands, for example something like bundler.
If you want to know more, you can check out book about creating command line apps build awesome command-line apps in ruby by author of these gems.
You do not need to make it a gem, the following suffices:
Change its name from script.rb to script
Add #!/usr/bin/env ruby as the first line of script
Put it somewhere in PATH (e.g. $HOME/bin, making sure it is in PATH), or execute by giving path explicitly, e.g. $HOME/myscriptdir/script

ZSH Ruby command not found

I'm pretty inexperienced with terminal type stuff besides the most basic commands, I recently switched from Bash to ZSH with in oh-my-zsh. I'm trying to make an executable ruby script at usr/bin/test.rb. For what it's worth, I never tried this with bash so I have no idea if its zsh specific.
$~ test.rb
/usr/bin/test.rb: line 2: puts: command not found
$~ ruby test.rb
ruby: No such file or directory -- test.rb (LoadError)
and my .zshrc file:
export ZSH=$HOME/.oh-my-zsh
source $ZSH/oh-my-zsh.sh
export PATH="/Users/jason/.rvm/gems/ruby-2.0.0-p247/bin:/Users/jason/.rvm/gems/ruby- 2.0.0-p247#global/bin:/Users/jason/.rvm/rubies/ruby-2.0.0-p247/bin:/Users/jason/.rvm/bin:.git/safe/../../bin:.git/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11/bin:/Users/jason/.rvm/bin"
.
~ which ruby
/Users/jason/.rvm/rubies/ruby-2.0.0-p247/bin/ruby
test.rb
#!/usr/bin/env ruby
puts 'test!'
$~ test.rb
/usr/bin/test.rb: line 2: puts: command not found
This is probably because your script is missing an essential line, often called a "pound-bang line" or, more simply, a "bang line", which tells the operating system what program to use to execute the rest of the file. Typically, for Ruby scripts, it looks like:
#!/usr/bin/ruby
or
#!/usr/bin/env ruby
and MUST be the first line in the file. When the OS opens the file, it looks for #! and, if it sees those, launches the executable at the path given, and passes the script to it. That's basic script execution on a *nix system, and applies to sh/Bash/Perl/Python/Ruby and any number of other executable applications on a *nix system.
ruby test.rb
ruby: No such file or directory -- test.rb (LoadError)
I suspect the second failed because you weren't in the /usr/bin/ directory when you executed that command. Ruby tried to run the script but couldn't find it in the local/current directory.
I'm not trying to be cruel, but, as a programmer, you'll spend a huge amount of time at the command-line, especially so if you are programming in C/C++, Perl, Ruby, Python, or any non-IDE based language. You have to learn how the OS works otherwise disasters of varying sizes and shapes await you, so, in parallel to learning a language you need to learn how to use, and administer, your OS. You don't have to be a power-user or administrator, but you have to know enough to understand good instructions from ones that don't apply, or are just plain-wrong.
Well I was facing the same problem, I had ruby & rails installed but I couldn't run them on ZSH
The answer is so simple
Just Add the following lines to .zshrc
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
Then run
source ~/.zshrc
After that to check if ruby & rails are found by ZSH run
ruby --version
rails --verison

Launching Ruby without the prefix "Ruby"

I'm on OS X (with bash) and a newbie at unix. I want to know if it's possible to amend some file such that to run a ruby program, I don't need "ruby file.rb", but instead can just run "ruby.rb".
Is there a reason NOT to do this?
Thanks!
Yes you can do this.
Assuming ruby.rb has something like this in it:
#!/usr/bin/env ruby
puts 'Hello world'
At the command line: chmod +x ruby.rb
This makes it executable.
Then you can execute it like this:
./ruby.rb
For more details see wikibooks.
EDIT (Jörg W Mittag): Using #!/usr/bin/env ruby instead of #!/usr/bin/ruby as the shebang line is more portable, because on every Unix produced in the last 20 years, the env command is known to live in /usr/bin, whereas Ruby installations are typically all over the place. (E.g., mine lives in /home/joerg/jruby-1.2.0/bin/ruby.)
As others have mentioned, you want to have a shebang (#!) line at the beginning, and change the permissions to executable.
I would recommend using #!/usr/bin/env ruby instead of the path to Ruby directly, since it will make your script more portable to systems that may have Ruby installed in different directories; env will search in your search path, and so it will find the same Ruby that you would execute if you ran ruby on the command line. Of course, this will have problems if env is in a different location, but it is much more common for env to be at /usr/bin/env than for Ruby to be at /usr/bin/ruby (it may be in /usr/local/bin/ruby, /opt/bin/ruby, /opt/local/bin/ruby, etc)
#!/usr/bin/env ruby
puts "Hello!"
And make it executable:
chmod +x file.rb
chmod +x /path/to/file
No reason not to do it, as long as you prefix the interpreter with a shebang (#!/usr/local/ruby or whatever the path is on OSX). The shell doesn't care.
Place the correct shebang as the first line of your file. ex:
#!/usr/bin/ruby
in the shell, make the file executable
chmod +x file
If you want to do anything more complicated with running this application, you can always create a shell script:
#! /bin/sh
ruby ruby.rb
If you save it to run_script, you just have to chmod +x it as mentioned previously, then execute the following command:
$ ./run_script
I doubt this will be any more useful in your particular situation than the solutions already mentioned, but it's worth noting for completeness's sake.

Resources