Will Ashley Will Ashley - 1 year ago 62
Ruby Question

Ruby cant find txt file in same directory

The location of both the script and the txt is /Users/*****/names

I am trying to read in a list of baby names from the year 1880 in CSV format. My program, when run in the terminal (on OS X) still returns an error indicating the yob1880.txt doesnt exist.

No such file or directory @ rb_sysopen - /names/yob1880.txt (Errno::ENOENT)
from names.rb:2:in `<main>'

lines = []

File.expand_path('../yob1880.txt', __FILE__)
IO.foreach('../yob1880.txt') do |line|
lines << line
if lines.size >= 1000
lines = FasterCSV.parse(lines.join) rescue next
store lines
lines = []
store lines

Answer Source

If you're running the script from the /Users/*****/names directory, and the files also exist there, you should simply remove the "../" from your pathnames to prevent looking in /Users/***** for the files.

Use this approach to referencing your files, instead:

File.expand_path('yob1880.txt', __FILE__)
IO.foreach('yob1880.txt') do |line|

Note that the File.expand_path is doing nothing at the moment, as the return value is not captured or used for any purpose; it simply consumes resources when it executes. Depending on your actual intent, it could realistically be removed.

Going deeper on this topic, it may be better for the script to be explicit about which directory in which it locates files. Consider these approaches:

Change to the directory in which the script exists, prior to opening files

IO.foreach('yob1880.txt') do |line|

This explicitly requires that the script and the data be stored relative to one another; in this case, they would be stored in the same directory.

Provide a specific path to the files

# do not use Dir.chdir or File.expand_path
IO.foreach('/Users/****/yob1880.txt') do |line|

This can work if the script is used in a small, contained environment, such as your own machine, but will be brittle if it data is moved to another directory or to another machine. Generally, this approach is not useful, except for short-lived scripts for personal use.

Never put a script using this approach into production use.

Work only with files in the current directory

# do not use Dir.chdir or File.expand_path
IO.foreach('yob1880.txt') do |line|

This will work if you run the script from the directory in which the data exists, but will fail if run from another directory. This approach typically works better when the script detects the contents of the directory, rather than requiring certain files to already exist there.

Many Linux/Unix utilities, such as cat and grep use this approach, if the command-line options do not override such behavior.

Accept a command-line option to find data files

require 'optparse'

base_directory = "."

OptionParser.new do |opts|
  opts.banner = "Usage: example.rb [options]"

  opts.on('-d', '--dir NAME', 'Directory name') {|v| base_directory = Dir.chdir(File.dirname(File.expand_path(v))) }

IO.foreach(File.join(base_directory, 'yob1880.txt')) do |line|
  # do lines

This will give your script a -d or --dir option in which to specify the directory in which to find files.

Use a configuration file to find data files

This code would allow you to use a YAML configuration file to define where the files are located:

require 'yaml'

config_filename = File.expand_path("~/yob/config.yml")
config = {}
name = nil

config = YAML.load_file(config_filename)
base_directory = config["base"]

IO.foreach(File.join(base_directory, 'yob1880.txt')) do |line|
  # do lines

This doesn't include any error handling related to finding and loading the config file, but it gets the point across. For additional information on using a YAML config file with error handling, see my answer on Asking user for information, and never having to ask again.

Final thoughts

You have the tools to establish ways to locate your data files. You can even mix-and-match solutions for a more sophisticated solution. For instance, you could default to the current directory (or the script directory) when no config file exists, and allow the command-line option to manually override the directory, when necessary.