How to specify a file path using '~' in Ruby? - ruby

If I do:
require 'inifile'
# read an existing file
file = IniFile.load('~/.config')
data = file['profile'] # error here
puts data['region']
I get an error here:
t.rb:6:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
It goes away if I specify an absolute path:
file = IniFile.load('/User/demo1/.config')
But I do not want to hardcode the location. How can I resolve ~ to a path in Ruby?

Ruby has a method for this case. It is File::expand_path.
Converts a pathname to an absolute pathname. Relative paths are referenced from the current working directory of the process unless dir_string is given, in which case it will be used as the starting point. The given pathname may start with a “~”, which expands to the process owner’s home directory (the environment variable HOME must be set correctly). “~user” expands to the named user’s home directory.
require 'inifile'
# read an existing file
file = IniFile.load(File.expand_path('~/.config'))

When given ~ in a path at the command line, the shell converts ~ to the user's home directory. Ruby doesn't do that.
You could replace ~ using something like:
'~/.config'.sub('~', ENV['HOME'])
=> "/Users/ttm/.config"
or just reference the file as:
File.join(ENV['HOME'], '.config')
=> "/Users/ttm/.config"
or:
File.realpath('.config', ENV['HOME'])
=> "/Users/ttm/.config"

Related

Dir.chdir(File.dirname(__FILE__)) throws Errno::ENOENT

I got a method where I use Dir.chdir(File.dirname(__FILE__)) inside. I am using it so that I can run the ruby file from anywhere and not get this error: No such file or directory # rb_sysopen - 'filename' (Errno::ENOENT).
Using the method for the first time works fine but using it for the second time throws an error. See method and exact error below.
def meth(string)
Dir.chdir(File.dirname(__FILE__))
hash = JSON.parse(File.read("file.json"))
# do something with hash and string
# return some value
end
meth("first string") # this returns what is expected
meth("second string") # this second usage of the method throws the error
Error sample pinpointing the line where I used Dir.chdir(File.dirname(__FILE__)):
dir/decoder.rb:44:in `chdir': No such file or directory # dir_s_chdir - lib (Errno::ENOENT)
Not sure if OS plays a role here, I am using an m1 BigSur on version 11.2.3.
Why is this happening?
What needs to be done so that the method` can be used as much as needed without running into the error?
Your problem here seems to be that __FILE__ is a relative path like dir/decoder.rb and that path becomes invalid after the first time Dir.chdir is used, because that command changes the working directory of your entire Ruby process. I think the solution would be to do something like this in your decoder.rb file:
DecoderDir = File.realpath(File.dirname(__FILE__))
def meth
Dir.chdir(DecoderDir)
# ...
end
I'm guessing that the first time the Ruby interpreter processes the file, that is early enough that the relative path in __FILE__ still refers to the right place. So, at that time, we generate an absolute path for future use.
By the way, a well-behaved library should not run Dir.chdir because it will affect all use of relative paths throughout your entire Ruby process. I pretty much only run Dir.chdir once and I run it near the beginning of my top-level script. If you're making a reusable library, you might want to do something like this to calculate the absolute path of the file you want to open:
DecoderJson = File.join(DecoderDir, 'file.json')

What does it mean $: in Ruby

I was reading the following tutorial.
It talked about including files in a Ruby file like require :
require(string) => true or false
Ruby tries to load the library named string, returning true if
successful. If the filename does not resolve to an absolute path, it
will be searched for in the directories listed in $:. If the file has
the extension ".rb", it is loaded as a source file; if the extension
is ".so", ".o", or ".dll", or whatever the default shared library
extension is on the current platform, Ruby loads the shared library as
a Ruby extension. Otherwise, Ruby tries adding ".rb", ".so", and so on
to the name. The name of the loaded feature is added to the array in
$:.
I just want to know what is $: in Ruby and what does $: means.
The variable $: is one of the execution environment variables, which is an array of places to search for loaded files.
The initial value is the value of the arguments passed via the -I command-line option, followed by an installation-defined standard library location.
See Pre-defined variables, $LOAD_PATH is its alias.
Its the load path
Just open in irb terminal and type this $:
This is what you would get. Ofcourse that depends on the ruby ur using.
2.1.1 :009 > $:
=> ["/Users/mac/.rvm/rubies/ruby-2.1.1/lib/ruby/site_ruby/2.1.0", "/Users/mac/.rvm/rubies/ruby-2.1.1/lib/ruby/site_ruby/2.1.0/x86_64-darwin12.0", "/Users/mac/.rvm/rubies/ruby-2.1.1/lib/ruby/site_ruby", "/Users/mac/.rvm/rubies/ruby-2.1.1/lib/ruby/vendor_ruby/2.1.0", "/Users/mac/.rvm/rubies/ruby-2.1.1/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin12.0", "/Users/mac/.rvm/rubies/ruby-2.1.1/lib/ruby/vendor_ruby", "/Users/mac/.rvm/rubies/ruby-2.1.1/lib/ruby/2.1.0", "/Users/mac/.rvm/rubies/ruby-2.1.1/lib/ruby/2.1.0/x86_64-darwin12.0"]
2.1.1 :010 >
In ruby $ refers to a predefined variable.
In this case, $: is short-hand for $LOAD_PATH. This is the list of directories you can require files from while giving a relative path. In other words, Ruby searches the directories listed in $:
Hope this helps.

how to read file using path in ruby by function IO.readlines("path")[0]

i want to read first line of file by using following function in ruby
IO.readlines("path")[0]
But file is not in current directory, so i use path there
puts IO.readlines("Home/Documents/vikas/SHIF.doc")
but it is giving error as
a1.rb:1:in `readlines': No such file or directory # rb_sysopen - Home/Documents/vikas/SHIF.doc (Errno::ENOENT)
from a1.rb:1:in `<main>'
You can also open a file and read only the first line instead of the entire file
File.open("Home/Documents/vikas/SHIF.doc").readline
You can use File.expand_path:
puts IO.readlines(File.expand_path("Home/Documents/vikas/SHIF.doc", __FILE__))
Note however that it will create path relatively to a file directory, not to a root directory.
If you are using rails, you could use:
puts IO.readlines(Rails.root.join 'Home', 'Documents', 'vikas', 'SHIF.doc')

Confusing behavior of File.dirname

I have written a couple of small Ruby scripts for system administration using Ruby 1.9.3. In one script I use:
File.dirname(__FILE__)
to get the directory of the script file. This returns a relative path, however when I call the script from a second script File.dirname returns an absolute path.
Ruby Doc lists an absolute return path in its example whereas I found a discussion on Ruby Forum where a user says dirname should only return a relative path.
I am using the suggested solution from Ruby Forums to use File.expand_path to always get the absolute path like this:
File.expand_path(File.dirname(__FILE__))
but is there a way to make the behaviour of dirname consistent?
UPDATE:
To expand on Janathan Cairs answer, I made two scripts:
s1.rb:
puts "External script __FILE__: #{File.dirname(__FILE__)}"
s0.rb:
puts "Local script __FILE__: #{File.dirname(__FILE__)}"
require './s1.rb'
Running ./s0.rb gives the following output:
Local script __FILE__: .
External script __FILE__: /home/kenneth/Pictures/wp/rip_vault
File.dirname should return an absolute path if given an absolute path, and a relative path if given a relative path:
File.dirname('/home/jon/test.rb') # => '/home/jon'
File.dirname('test.rb') # => '.'
__FILE__ returns the name of the current script, which is therefore a relative path from the current directory. That means you should always use expand_path if you want to get the absolute path with File.dirname(__FILE__).
NB Ruby 2.0.0 introduces the __dir__ constant
If you already upgraded to Ruby 2.0, you can use the new constant
__dir__
otherwise you can use
File.expand_path('..', __FILE__)
which is shorter than
File.expand_path(File.dirname(__FILE__))
File.expand_path documentation

What does $:<< "." do to Ruby's require path?

I don't understand the meaning of $:<< "." in Ruby.
I upgraded Ruby to 1.9.1, but a program was not working. My classmate told me that I am supposed to add $:<< "."
What does $:<< "." do?
$: is the variable that holds an array of paths that make up your Ruby's load path
<< appends an item to the end of the array
. refers to the current directory
1 2 3
| | |
V V V
$: << "."
So you are adding the current directory to Ruby's load path
References:
Can be found in the Execution Environment Variables section of of this page from The Pragmatic Programmers Guide
An array of strings, where each string specifies a directory to be searched for Ruby scripts and binary extensions used by the load and require methods. The initial value is the value of the arguments passed via the -I command-line option, followed by an installation-defined standard library location, followed by the current directory (“.”)[Obviously this link is for an older version of Ruby as this is still in there]. This variable may be set from within a program to alter the default search path; typically, programs use $: << dir to append dir to the path.
Can be found in the docs for array at ruby-doc.org.
Append—Pushes the given object on to the end of this array. This expression returns the array itself, so several appends may be chained together.
Since version 1.9, Ruby doesn't look for required files in the current working directory AKA .. The $LOAD_PATH or $: global variable is an array of paths where Ruby looks for files you require.
By adding $:<< "." to your files, you are actually telling Ruby to include your current directory in the search paths. That overrides new Ruby behavior.
In your example you add working directory (".") to ruby load path ($:).
Working directory (".") was removed from load path (global variable $: or $-I or $LOAD_PATH) in Ruby 1.9 because it was considered a security risk:
Your working directory may be any folder, and your script will require files from this folder if these files have appropriate names. For example you have 2 files in Project1 folder main.rb and init.rb:
==Project1/main1.rb:
$: << "."
require 'init'
==Project1/init.rb:
puts 'init 1'
And you have alike project:
==Project2/main2.rb:
$: << "."
require 'init'
==Project2/init.rb:
puts 'init 2'
If you run Project1 from Project2 folder, then main1.rb will require Project2/init.rb, not Project1/init.rb:
~/Projects/Project2$ ruby ../Project1/main1.rb
init 2 # may be unexpected an dangerous
~/Projects/Project2$ ruby main2.rb
init 2
You can change your working directory in your code, e.g. using Dir.chdir:
ruby-1.9.2-p290 :002 > puts File.expand_path('.')
=> /home/alex/Projects
ruby-1.9.2-p290 :003 > Dir.chdir('..')
ruby-1.9.2-p290 :004 > puts File.expand_path('.')
=> /home/alex
I recommend you to use the following techniques instead of $: << '.':
require_relative (Ruby 1.9 only)
Add folder of the file to the working directory (common approach because it is compatible with Ruby 1.8): $: << File.expand_path('..', __FILE__) etc.. __FILE__ is a reference to the current file name. File.expand_path converts a pathname to an absolute pathname.

Resources