No such file or directory # rb_sysopen ruby - ruby

Facing below issue eventhough the file is present in the folder.
H:\Ruby_test_works>ruby hurrah.rb
hurrah.rb:7:in `read': No such file or directory # rb_sysopen - H:/Ruby_
test_works/SVNFolders.txt (Errno::ENOENT)
from hurrah.rb:7:in `block in <main>'
from hurrah.rb:4:in `each_line'
from hurrah.rb:4:in `<main>'
Input file (input.txt) Columns are tab separated.
10.3.2.021.asd 10.3.2.041.def SVNFolders.txt
SubversionNotify Subversionweelta post-commit.bat
Commit message still rake customemail.txt
mckechney.com yahoo.in ReadMe.txt
Code :
dir = 'H:/Ruby_test_works'
file = File.open("#{dir}/input.txt", "r")
file.each_line do |line|
initial, final, file_name = line.split("\t")
#puts file_name
old_value = File.read("#{dir}/#{file_name}")
replace = old_value.gsub( /#{Regexp.escape(initial)}, #{Regexp.escape(final)}/)
File.open("#{dir}/#{file_name}", "w") { |fi| fi.puts replace }
end
I have tried using both forward and backward slashes but no luck. What I'm missing, not sure. Thanks.
puts file_name gives the below values
SVNFolders.txt
post-commit.bat
customemail.txt
ReadMe.txt

The file_name contains the newline character \n at the end, which won't get printed but messes up the path. You can fix the issue by stripping the line first:
initial, final, file_name = line.strip.split("\t")
When debugging code, be careful with puts. Quoting its documentation reveals an ugly truth:
Writes the given object(s) to ios. Writes a newline after any that do not already end with a newline sequence.
Another way to put this is to say it ignores (potential) newlines at the end of the object(s). Which is why you never saw that the file name actually was SVNFolders.txt\n.
Instead of using puts, you can use p when troubleshooting issues. The very short comparison between the two is that puts calls to_s and adds a newline, while p calls inspect on the object. Here is a bit more details about the differences: http://www.garethrees.co.uk/2013/05/04/p-vs-puts-vs-print-in-ruby/

Sometimes the issue is not the file, but the path to the file. Consider compare the file path with what you think the file path is with something like:
File.expand_path('my_file.rb')

Related

Ruby read file_name with gets

I have a beginners coding task, first step is my program "should prompt the user to enter a filename of a file that contains the following information:" There's already pre-made code to work on, a "music_player.rb" (where I have to write the code) and "albums.text" (which is the file I want to read from)
I know a_file = File.new("mydata.txt", "r") is to read from file. I'm trying to do:
file_name = gets()
a_file = File.new("#{file_name}" , "r") # (line 13)
I keep getting error
music_player_with_menu.rb:13:in `initialize': No such file or directory # rb_sysopen - albums.txt (Errno::ENOENT)
when I enter albums.txt. If I just remove gets and have File.new("albums.txt" , "r") it works. I'm not sure what I'm doing wrong.
Trying to read from mydata.txt\n is going to raise an exception unless the filename actually ends in \n, which is rarely the case. This is because you're using #gets, which includes the newline character(s) from the user pressing RETURN or ENTER.
When you read from STDIN, you will get a line-ending (e.g. \n on *nix, and \r\n on Windows). So, when you call #gets, you almost always need to call String#chomp on the result.
file_name = gets
#=> "foo\n"
file_name = gets.chomp
#=> "foo"

Move files to a target directory using Ruby

I'm trying to read a file(input.txt) containing file names and move the corresponding files present in the input.txt to the target location (d:/target)
(script, input.txt and the files to be moved are all in the same directory)
below is what I have tried
require 'fileutils'
target = "D://target/"
file='input.txt'
File.readlines(file).each do |line|
puts line
FileUtils.mv(line, target)
end
but Im facing below error and I have checked other options but unable to fix it. Any ideas, thanks.
C:/Ruby23/lib/ruby/2.3.0/fileutils.rb:1329:in `stat': Invalid argument # rb_file
_s_stat - D://targer/north_af.txt (Errno::EINVAL)
input.txt
north_af.txt
south_af.txt
midd_cji.txt
fg_poi.txt
and so on....
I think every line has extra character "\n" at the end. You need to strip it before.
FileUtils.mv(line.strip, target)

Changing information in a CSV file

I'm trying to write a ruby script that will read through a CSV file and prepend information to certain cells (for instance adding a path to a file). I am able to open and mutate the text just fine, but am having issues writing back to the CSV without overriding everything. This is a sample of what I have so far:
CSV.foreach(path) { |row|
text = row[0].to_s
new_text = "test:#{text}"
}
I would like to add something within that block that would then write new_textback to the same reference cell(row) in the file. The only way I have to found to write to a file is
CSV.open(path, "wb") { |row|
row << new_text
}
But I think that is bad practice since you are reopening the file within the file block already. Is there a better way I could do this?
EX: I have a CSV file that looks something like:
file,destination
test.txt,A101
and need it to be:
file,destination
path/test.txt,id:A101
Hope that makes sense. Thanks in advance!
Depending on the size if the file, you might consider loading the contents of the file into a local variable and then manipulating that, overwriting the original file.
lines = CSV.read(path)
File.open(path, "wb") do |file|
lines.each do |line|
text = line[0].to_s
line[0] = "test:#{text}" # Replace this with your editing logic
file.write CSV.generate_line(line)
end
end
Alternately, if the file is big, you could write each modified line to a new file along the way and then replace the old file with the new one at the end.
Given that you don't appear to be doing anything that draws on CSV capabilities, I'd recommend using Ruby's "in-place" option variable $-i.
Some of the stats software I use wants just the data, and can't deal with a header line. Here's a script I wrote a while back to (appear to) strip the first line out of one or more data files specified on the command-line.
#! /usr/bin/env ruby -w
#
# User supplies the name of one or more files to be "stripped"
# on the command-line.
#
# This script ignores the first line of each file.
# Subsequent lines of the file are copied to the new version.
#
# The operation saves each original input file with a suffix of
# ".orig" and then operates in-place on the specified files.
$-i = ".orig" # specify backup suffix
oldfilename = ""
ARGF.each do |line|
if ARGF.filename == oldfilename # If it's an old file
puts line # copy lines through.
else # If it's a new file remember it
oldfilename = ARGF.filename # but don't copy the first line.
end
end
Obviously you'd want to change the puts line pass-through to whatever edit operations you want to perform.
I like this solution because even if you screw it up, you've preserved your original file as its original name with .orig (or whatever suffix you choose) appended.

Ruby saying file doesnt exist

I'm new to Ruby, and I am writing a test program just to get some of the features down. Here is the program
#!/usr/bin/env ruby
class FileManager
def read_file(filename)
return nil unless File.exist?(filename)
File.read(filename)
end
end
if __FILE__ == $0
fm = FileManager.new
puts "What file would you like to open?"
fname = gets
puts fm.read_file fname
end
As you can see, it is very simple. If I comment the first line of the read_file method, I get this error
No such file or directory - /Users/macuser/Projects/Aptana\ Studio\ 3\ Workspace/Ruby\ Test/text (Errno::ENOENT)
from /Users/macuser/Projects/Aptana Studio 3 Workspace/Ruby Test/ruby.rb:6:in `read_file'
from /Users/macuser/Projects/Aptana Studio 3 Workspace/Ruby Test/ruby.rb:15:in `<main>'
when I run the program and use this file: /Users/macuser/Projects/Aptana\ Studio\ 3\ Workspace/Ruby\ Test/text
However, if I run cat /Users/macuser/Projects/Aptana\ Studio\ 3\ Workspace/Ruby\ Test/text, it outputs Hello, world!, as it should.
I don't believe it's a permissions issue because I own the folder, but just in case I've tried running the program as root. Also, I have made sure that fname is the actual name of the file, not nil. I've tried both escaped and unescaped versions of the path, along with just text or the full path. I know for a fact the file exists, so why is Ruby giving me this error?
With gets filename the filename includes a newline \n.
You have to remove it in your filename:
gets filename
p filename #"test.rb\n"
p File.exist?(filename) #false
p File.exist?(filename.chomp) #true
(And you don't need to mask the spaces)
It looks like you're shell-escaping your spaces even though gets does not go through a shell. You want to enter "/Users/macuser/Projects/Aptana Studio 3 Workspace/Ruby Test/text" instead of "/Users/macuser/Projects/Aptana\ Studio\ 3\ Workspace/Ruby\ Test/text".

Loop from contents of a file in ruby

Okay, so I am new to Ruby and I have a strong background in bash/ksh/sh.
What I am trying to do is use a simple for loop to run a command across several servers. In bash I would do it like:
for SERVER in `cat etc/SERVER_LIST`
do
ssh -q ${SERVER} "ls -l /etc"
done
etc/SERVER_LIST is just a file that looks like:
server1
server2
server3
etc
I can't seem to get this right in Ruby. This is what I have so far:
#!/usr/bin/ruby
### SSH testing
#
#
require 'net/ssh'
File.open("etc/SERVER_LIST") do |f|
f.each_line do |line|
Net::SSH.start(line, 'andex') do |ssh|
result = ssh.exec!("ls -l")
puts result
end
end
end
I'm getting these errors now:
andex#master:~/sysauto> ./ssh2.rb
/usr/lib64/ruby/gems/1.8/gems/net-ssh-2.0.23/lib/net/ssh/transport/session.rb:65:in `initialize': newline at the end of hostname (SocketError)
from /usr/lib64/ruby/gems/1.8/gems/net-ssh-2.0.23/lib/net/ssh/transport/session.rb:65:in `open'
from /usr/lib64/ruby/gems/1.8/gems/net-ssh-2.0.23/lib/net/ssh/transport/session.rb:65:in `initialize'
from /usr/lib64/ruby/1.8/timeout.rb:53:in `timeout'
from /usr/lib64/ruby/1.8/timeout.rb:93:in `timeout'
from /usr/lib64/ruby/gems/1.8/gems/net-ssh-2.0.23/lib/net/ssh/transport/session.rb:65:in `initialize'
from /usr/lib64/ruby/gems/1.8/gems/net-ssh-2.0.23/lib/net/ssh.rb:179:in `new'
from /usr/lib64/ruby/gems/1.8/gems/net-ssh-2.0.23/lib/net/ssh.rb:179:in `start'
from ./ssh2.rb:10
from ./ssh2.rb:9:in `each_line'
from ./ssh2.rb:9
from ./ssh2.rb:8:in `open'
from ./ssh2.rb:8
The file is sourced correctly, I am using the relative path, as I am sitting in the directory under etc/ (not /etc, I'm running this out of a scripting directory where I keep the file in a subdirectory called etc.)
File.open("/etc/SERVER_LIST", "r") do |file_handle|
file_handle.each_line do |server|
# do stuff to server here
end
end
The first line opens the file for reading and immediately goes into a block. (The block is the code between do and end. You can also surround blocks with just { and }. The rule of thumb is do..end for multi-line blocks and {...} for single-line blocks.) Blocks are very common in Ruby. Far more idiomatic than a while or for loop.) The call to open receives the filehandle automatically, and you give it a name in the pipes.
Once you have a hold of that, so to speak, you can call each_line on it, and iterate over it as if it were an array. Again, each iteration automatically passes you a line, which you call what you like in the pipes.
The nice thing about this method is that it saves you the trouble of closing the file when you're finished with it. A file opened this way will automatically get closed as you leave the outer block.
One other thing: The file is almost certainly named /etc/SERVER_LIST. You need the initial / to indicate the root of the file system (unless you are intentionally using a relative value for the path to the file, which I doubt). That alone may have kept you from getting the file open.
Update for new error: Net::SSH is barfing up over the newline. Where you have this:
Net::SSH.start(line, 'andex') do |ssh|
make it this:
Net::SSH.start(line.chomp, 'andex') do |ssh|
The chomp method removes any final newline character from a string.
Use File.foreach:
require 'net/ssh'
File.foreach('etc/SERVER_LIST', "\n") do |line|
Net::SSH.start(line, 'andex') do |ssh|
result = ssh.exec!("ls -l")
puts result
end
end
The most common construct I see when doing by-line iteration of a file is:
File.open("etc/SERVER_LIST") do |f|
f.each_line do |line|
# do something here
end
end
To expand on the above with some more general Ruby info... this syntax is equivalent to:
File.open("etc/SERVER_LIST") { |f|
f.each_line { |line|
# do something here
}
}
When I was first introduced to Ruby, I had no idea what the |f| and |line| syntax meant. I knew when to use it, and how it worked, but not why they choose that syntax. It is, in my opinion, one of the magical things about Ruby. That simple syntax above is actually hiding a very advanced programming concept right under your nose. The code nested inside of the "do"/"end" or { } is called a block. And you can consider it an anonymous function or lambda. The |f| and |line| syntax is in fact just the handle to the parameter passed to the block of code by the executing parent.
In the case of File.open(), the anonymous function takes a single argument, which is the handle to the underyling File IO object.
In the case of each_line, this is an interator function which gets called once for every line. The |line| is simply a variable handle to the data that gets passed with each iteration of the function.
Oh, and one nice thing about do/end with File.open is it automatically closes the file at the end.
Edit:
The error you're getting now suggests the SSH call doesn't appreciate the extra whitespace (newline) at the end of the string. To fix this, simply do a
Net::SSH.start(line.strip, 'andex') do |ssh|
end
Reading in lines from a file is a common operation, and Ruby has an easy way to do this:
servers = File.readlines('/etc/SERVER_LIST')
The readlines method will open the file, read the file into an array, and close the file for you (so you don't have to worry about any of that). The variable servers will be an array of strings; each string will be a line from the file. You can use the Array::each method to iterate through this array and use the code you already have. Try this:
servers = File.readlines('/etc/SERVER_LIST')
servers.each {|s|
Net::SSH.start(s, 'andex') {|ssh| puts ssh.exec!("ls -l") }
}
I think this is what you want for the in 'initialize': newline at the end of hostname (SocketError) error:
Net::SSH.start(line.chomp, 'andex')
The each_line method includes the "\n", and the chomp will remove it.

Resources