I defined a custom instance method in the String class that I want to use in my other ruby files. I can do it by require-ing the file (that I defined my custom method in), but I want to use it naturally (without having to require).
For example: I defined this in my 'custom_string.rb' file:
class String
def set_style(style)
puts "\n#{self}"
self.size.times do
print style
end
end
end
Then, to use my set_style method in my 'test.rb' file, I have to do this:
require 'custom_string'
puts "hello".set_style("*")
I'm not using a Rails project. Is there a way to include my file into ruby by default (from the ruby command line) so it is available to all files in Ruby?
If you do not require 'custom_string' but found away for this to be automatically included, what happens when you run your program at another location, different server, share the code on github etc. The code would no longer execute as expected. The results that you post when asking for help would no longer match up with other peoples. This sounds like a bad idea to change Ruby behaviour in an untraceable manor.
If you just want irb to have this behaviour then you can add the require to your ~/.irbrc.
#Hauleth solution of adding it the command line allows the change of behaviour to be traced. An alias can be added to .bashrc or other shell rc to give this behaviour by default.
Ruby 1.9 help:
$ ruby --help
Usage: ruby [switches] [--] [programfile] [arguments]
-0[octal] specify record separator (\0, if no argument)
-a autosplit mode with -n or -p (splits $_ into $F)
-c check syntax only
-Cdirectory cd to directory, before executing your script
-d set debugging flags (set $DEBUG to true)
-e 'command' one line of script. Several -e's allowed. Omit [programfile]
-Eex[:in] specify the default external and internal character encodings
-Fpattern split() pattern for autosplit (-a)
-i[extension] edit ARGV files in place (make backup if extension supplied)
-Idirectory specify $LOAD_PATH directory (may be used more than once)
-l enable line ending processing
-n assume 'while gets(); ... end' loop around your script
-p assume loop like -n but print line also like sed
-rlibrary require the library, before executing your script
-s enable some switch parsing for switches after script name
-S look for the script using PATH environment variable
-T[level=1] turn on tainting checks
-v print version number, then turn on verbose mode
-w turn warnings on for your script
-W[level=2] set warning level; 0=silence, 1=medium, 2=verbose
-x[directory] strip off text before #!ruby line and perhaps cd to directory
--copyright print the copyright
--version print the version
Take a look on:
-rlibrary require the library, before executing your script
Related
I want to use the content of a file.txt as part of a bash command.
Suppose that the bash command with its options that I want to execute is:
my_command -a first value --b_long_version_option="second value" -c third_value
but the first 2 options (-a and --b_long_version_option ) are very verbose so instead of inserting directly on the command line (or bash script) I wrote them in a file.txt like this:
-a first value \
--b_long_version_option="second value"
Now I expect to call the command "my_command" with the following syntax (where "path_to/file.txt" is the path to file.txt, expressed in relative or absolute form):
my_command "$(cat path_to/file.txt)" -c third_value
This however is not the right syntax, as my command is breaking and complaining.
How should I write the new version of the command and/or the file.txt so that it is equivalent to its native bash usage?
Thanks in advance!
The quotes are preserving the newlines. Take them off.
You also don't need the cat unless you're running an old bash parser.
my_command $(<path_to/file.txt) -c third_value
You'll need to take the backslashes at the ends of lines out.
Be careful doing things like this, though. It's probably better to just put the whole command in the file, rather than just pieces of it. If you really just want arguments, maybe define them a little more carefully in an array, source the file and then apply them, like this:
in file:
myArgs=( "-a" "first value"
"--b_long_version_option=second value"
)
Note the quoting. Then run with
. file
my_command "${myArgs[#]" -c third_value
e.g.,
$: printf "[%s] " "${myArgs[#]}" -c=foo
[-a] [first value] [--b_long_version_option=second value] [-c=foo]
I haven't seen any example of what you're trying. But, there are simpler ways to achieve your goal.
Bash Alias
ll for example is a bash alias for ls -al. It usually is defined in .bash_profile or .bashrc as follows :
alias ll='ls -al'
So, what you can do is to set another alias for your shorthand command.
alias mycmd='mycommand -a first value --b_long_version_option="second value"'
then you can use it as follows :
mycmd -c third_value
Config file
You can also define a mycommand.json file or mycommand.ini file for default arguments. Then, you will need to check for config file in your software, and assign arguments from it.
Using config file is more advanced solution. You can define multiple config files. You can set default config file in /etc/mycommand/config.ini for example. When running on different directories, you should check ${cwd}/mycommand.ini to check local config file exists or not. You can even add a --config-file argument to your command.
Using alias is more convenient for small tasks, or thing that won't change much. If your command's behavior should be different in some other project, the using a config file would be a better solution.
I'm trying to pass command line arguments into a Ruby script that's being called with Traveling Ruby and having trouble making it work. I'm just using their standard wrapper.sh file for scripts with gems:
#!/bin/bash
set -e
# Figure out where this script is located.
SELFDIR="`dirname \"$0\"`"
SELFDIR="`cd \"$SELFDIR\" && pwd`"
# Tell Bundler where the Gemfile and gems are.
export BUNDLE_GEMFILE="$SELFDIR/lib/vendor/Gemfile"
unset BUNDLE_IGNORE_CONFIG
# Run the actual app using the bundled Ruby interpreter, with Bundler activated.
exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/test.rb"
When I run it, my Ruby script doesn't see any command line arguments. I've tried changing the last line to:
exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/test.rb $#"
which I thought would work, but when I test it with an arg "arg1" it's giving me the error:
/[pathtofile]/test-1.0.0-osx/lib/ruby/bin.real/ruby: No such file or directory -- /[pathtofile]/test-1.0.0-osx/lib/app/test.rb arg1 (LoadError)
So it seems like it's treating the command line argument as part of the filename.
Is there a way to modify this script to properly pass in arguments?
Thanks!
Obviously, I'm an idiot. Of course it was treating the argument as part of the filename because the $# was inside the quotes. The correct modification to the last line to make everything work is:
exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/test.rb" $#
I have a series of bash files that I would like to source into the current Ruby environment. Here's an example:
$ echo "export FOO=bar" > foo.sh
$ irb
> `source $(pwd)/foo.sh`
> puts ENV['FOO']
=> nil
Is there a way to source foo.sh into the parent environment without having to manually parse it?
All shell invocations in Ruby run in a subprocess, whether you use system() or backticks or Process or any other mechanism to execute them. It is not possible for a subprocess like this to effect the current Ruby process.
If you want to source a shell script prior to executing some Ruby code, you can create a wrapper:
#!/bin/sh
source foo.sh
ruby some_ruby_file.rb
If you really want to, you can try to parse out variable exports from the shell script and then set Ruby's ENV hash directly, but that's almost certainly a bad idea. It'd be hard to write, error-prone, and unmaintainable.
Either use a wrapper as above, or come up with a different way to save your environment config, such as a YAML file or some other conventional configuration solution.
TL;DR
You can't change the environment of a parent process from a child process or subshell, but you can certainly source or parse the file into the current process.
"Source" a Ruby Script
You can source a Ruby script with the Kernel#load or Kernel#require methods. That will import the contents of the file into your current process.
Parsing a Shell Script Into Ruby
If your source file is a shell script, you can't simply load it as Ruby; you will need to perform some kind of parsing of the file. This may be a security risk, unless you trust the contents, format, and source of the file you're reading in.
Assuming that you trust your input sources, and given a sample file like:
#!/usr/bin/bash
export FOO='bar'
echo FOO
echo bar
echo "FOO=$FOO"
you could do something like this:
# Find variables in the general form of "export x=y"
env_vars = File.read('/tmp/file.txt').scan /export\s+(\S+)=(\S+)/
#=> [["FOO", "'bar'"]]
# Parse each variable into the Ruby ENV key/value pair, removing
# outer quotes on the value if present.
env_vars.each { |v| ENV[v.first] = v.last.gsub /\A['"]|['"]\Z/, '' }
# Verify you have the value you expect.
ENV['FOO']
#=> "bar"
This will add each variable found via the String#scan method into ENV, where it can then be accessed by its key.
Caveats for Parsing
This works fine in casual testing, but you may need to modify your regular expression if you are not exporting the variable on the same line where you define it.
In addition, it is up to you to sanitize or validate input.
I strongly recommend only setting environment variables you're expecting to see (e.g. use Array#select to whitelist acceptable ENV keys), and ensuring the values for each variable you set are sane and safe for your particular use case.
A Better Option for Options
In general, if you're trying to set configuration options from inside a script, you'd be better off loading a YAML file or using OptionParser. YAML files in particular are easy to read, easy to parse, human editable, and (relatively) easy to sanitize. Your mileage may vary.
Got a few almost answers, none that worked entirely for my case here's what I ended up doing:
file = Pathname.new "tmp-variables.txt"
`env -i /bin/sh -c 'source <myfile-here.sh> && env > #{file}'`
file.each_line do |line|
line.match(/(?<key>[^=]+)=(?<value>.+)/) {|match| ENV[match[:key]] = match[:value] }
end
Here's an example script that shows it in action:
require 'pathname'
file = Pathname.new "tmp-variables.txt"
`mkdir profile.d`
`echo "export FOO=${FOO:-'bar'}" > profile.d/node`
`touch #{file}`
`env -i /bin/sh -c 'source profile.d/node && env > #{file}'`
file.each_line do |line|
line.match(/(?<key>[^=]+)=(?<value>.+)/) {|match| ENV[match[:key]] = match[:value] }
end
puts ENV['FOO']
Some tricks here env -i executes the command (-c) without any environment variables. I tried using this trick How do I source environment variables for a command shell in a Ruby script? but set gives you all the environment variables, i just want the ones from that file.
Jim's idea was good, but couldn't use due to my constraints. CodeGnome was on the right path, but we cannot read the file wholesale without evaluating otherwise we mis things like file = Pathname.new "tmp-variables.txt". Thanks all, this was quite a team effort. I've given you all up-votes.
You can import .env files using the dotenv gem.
https://github.com/bkeepers/dotenv
So, I made a simply ruby script,
#!/usr/bin/env ruby
puts "Hello!"
When I try to run it in terminal it doesn't put "Hello!" on the screen. I have tried entering chmod +x test.rb (test.rb is the name of my file). When I run it, it doesn't give me an error, it just doesn't display "Hello!". Any help will be much appreciated. I have looked everywhere for a possible answer, and I have found nothing so far.
I'd guess that you're trying to run it as just test like this:
$ test
But test is a bash builtin command that doesn't produce any output, it just sets a return value. If you run your script properly:
$ ./test.rb
then you'll see something. Note the explicit ./ path, the current directory is rarely (and hopefully never) in your PATH so you need to say ./ to run something in the current directory (unless of course you're in /bin, /usr/bin, etc.).
In the comments you say that there are some Ctrl+M characters in your script:
$ cat -e test.rb
#!/usr/bin/env ruby^M^Mputs "Hello!"
I don't see any $s in that cat -e output so you don't have any actual end-of-line markers, just some carriage-return characters (that's the ^M). A single CR is an old MacOS end-of-line, Windows uses a CR-LF pair, and Unix (including OSX) uses just a single LF to mark the end of a line of text. Since you don't have any EOLs, the shell just sees a single line that looks like:
#!/usr/bin/env ruby ...
without an actual script for ruby to run, the shell just sees the shebang comment and nothing else. The result is that nothing noticeable happens when you run your script. Fix your EOLs and your script will start working sensibly. You might also want to look at your editor's settings so that it starts writing proper EOLs.
How are you calling this method. You would want to call this out by something like ruby test.rb if you are in the directory with the test.rb file. Another general tip for trying something out that doesn't work would be to go into irb on command line and try your program, like puts "Hello! to see if it is that particular code that is the problem.
If I'm writing a shell script and I want to "source" some external (c-)shell scripts to set up my environment, I can just make calls like this:
source /file/I/want/to/source.csh
I want to replace a shell script that does this with a ruby script. Can I do a similar thing in the ruby script?
Update:
Just tried it with test_script.csh:
#!/bin/csh
setenv HAPPYTIMES True
...and test_script.rb:
#!/usr/bin/env ruby
system "~/test_script.csh"
system "echo $HAPPYTIMES"
Sadly, no HAPPYTIMES as of yet.
Given the following Ruby
# Read in the bash environment, after an optional command.
# Returns Array of key/value pairs.
def bash_env(cmd=nil)
env = `#{cmd + ';' if cmd} printenv`
env.split(/\n/).map {|l| l.split(/=/)}
end
# Source a given file, and compare environment before and after.
# Returns Hash of any keys that have changed.
def bash_source(file)
Hash[ bash_env(". #{File.realpath file}") - bash_env() ]
end
# Find variables changed as a result of sourcing the given file,
# and update in ENV.
def source_env_from(file)
bash_source(file).each {|k,v| ENV[k] = v }
end
and the following test.sh:
#!/usr/bin/env bash
export FOO='bar'
you should get:
irb(main):019:0> source_env_from('test.sh')
=> {"FOO"=>"bar"}
irb(main):020:0> ENV['FOO']
=> "bar"
Enjoy!
The reason this isn't working for you is b/c ruby runs its system commands in separate shells. So when one system command finishes, the shell that had sourced your file closes, and any environment variables set in that shell are forgotten.
If you don't know the name of the sourced file until runtime, then Roboprog's answer is a good approach. However, if you know the name of the sourced file ahead of time, you can do a quick hack with the hashbang line.
% echo sourcer.rb
#!/usr/bin/env ruby
exec "csh -c 'source #{ARGV[0]} && /usr/bin/env ruby #{ARGV[1]}'"
% echo my-script.rb
#!/usr/bin/env ruby sourcer.rb /path/to/file/I/want/to/source.csh
puts "HAPPYTIMES = #{ENV['HAPPYTIMES']}"
% ./my-script.rb
HAPPYTIMES = True
All of these will only help you use the set enviroment variables in your ruby script, not set them in your shell (since they're forgotten as soon as the ruby process completes). For that, you're stuck with the source command.
Improving a little on #takeccho's answer... Checks, and a few whistles. First, the sourced environment is cleaned via env -i, which is a safety measure but might be not desired in some cases. Second, via set -a, all variables set in the file are "exported" from the shell and thus imported into ruby. This is useful for simulating/overriding behavior found in environment files used with init scripts and systemd env files.
def ShSource(filename)
# Inspired by user takeccho at http://stackoverflow.com/a/26381374/3849157
# Sources sh-script or env file and imports resulting environment
fail(ArgumentError,"File #{filename} invalid or doesn't exist.") \
unless File.exist?(filename)
_newhashstr=`env -i sh -c 'set -a;source #{filename} && ruby -e "p ENV"'`
fail(ArgumentError,"Failure to parse or process #{filename} environment")\
unless _newhashstr.match(/^\{("[^"]+"=>".*?",\s*)*("[^"]+"=>".*?")\}$/)
_newhash=eval(_newhashstr)
%w[ SHLVL PWD _ ].each{|k|_newhash.delete(k) }
_newhash.each{|k,v| ENV[k]=v } # ENV does not have #merge!
end
Theory of operation: When ruby outputs the ENV object using p, it does so in a way that ruby can read it back in as an object. So we use the shell to source the target file, and ruby (in a sub-shell) to output the environment in that serializable form. We then capture ruby's output and eval it within our ruby-process. Clearly this is not without some risk, so to mitigate the risk, we (1) validate the filename that is passed in, and (2) validate using a regexp that the thing we get back from the ruby-subshell is, in fact, a serializable hash-string. Once we're sure of that, we do the eval which creates a new hash. We then "manually" merge the hash with ENV, which is an Object and not a regular Hash. If it were a Hash, we could have used the #merge! method.
EDIT: sh -a exported things like PATH. We must also remove SHLVL and PWD before the hash-merge.
I had have same probrem. and I resolve like below.
#!/usr/local/bin/ruby
def source(filename)
ENV.replace(eval(`tcsh -c 'source #{filename} && ruby -e "p ENV"'`))
end
p "***old env*****************************"
p ENV
source "/file/I/want/to/source.csh"
p "+++new env+++++++++++++++++++++++++++++"
p ENV
'eval' is a very powerful method.
It jump over the process easily.
You are going to have to write a function to run something like the following, and capture the output ("backtick" operation):
/bin/csh -e '. my_script ; env'
Loop on each line, match against something like
/^(\w+)=(.*)$/
Then use the first match capture as the var name, and the second capture as the var value.
(yes, I'm hedging on the fact that I know Perl way better than Ruby, but the approach would be the same)
system 'source /file/I/want/to/source.sh'
Not sure that this will do what you want though. It will execute the source command in a subshell. Try it and see it it does what you're after.