Making subdirectories while copying files - ruby

I have simple script that goes through txt file and copies files according to line in txt file
Here is it
require 'fileutils'
File.open("files.txt", "r") do |f|
f.each_line do |line|
line = line.chop #need to remove \n symbol from like
system("cp #{line} new/#{line}")
end
end
In my txt files - there are file path in each like like:
app/helpers/api/v1/application_helper.rb
However when i run script it fails if there is no such directory inside my new folder. So either i have to create them manually to reflect folder structure as in my txt file, or create with script.
Is there any way how can i do this in my script?

There are many ways to solve this. Here's one method:
require 'fileutils'
File.open("files.txt", "r") do |f|
f.each_line do |line|
line = line.chop
system("mkdir -p new/#{File.dirname(line)}")
system("cp #{line} new/#{line}")
end
end

I see you're requiring fileutils but not using any of its methods. You can use it like this
require 'fileutils'
File.open("files.txt", "r") do |f|
f.each_line do |line|
line = line.chop #need to remove \n symbol from like
FileUtils.mkdir_p(File.dirname(line))
FileUtils.cp(line, "new/#{line}")
end
end

Related

output lines to separate line in ruby

I have a file where I search for specific lines, like this:
<ClCompile Include="..\..\..\Source\fileA.c" />
<ClCompile Include="..\..\..\Tests\fileB.c" />
In my script I can find this lines and extract only the path string between the double qoutes . When I find them, I save it to an array (which I use later in my code). It looks like this:
source_path_array = []
File.open(file_name) do |f|
f.each_line {|line|
if line =~ /<ClCompile Include="..\\/
source_path = line.scan(/".*.c"/)
###### Add path to array ######
source_path_array << source_path
end
}
end
So far, everything OK. Later in my script I output the array within an other file to a line "Source Files":
f.puts "Source Files= #{source_path_array.flatten.join(" ")}"
The result is than like this:
Source Files= "..\..\..\Source\fileA.c" "..\..\..\Tests\fileB.c"
I would like to have the output in this form:
Source Files=..\..\..\Source\fileA.c
Source Files=..\..\..\Tests\fileB.c
As you can see, each path in an separate line with the string "Source Files" before and also without double quotes. Any idea? Maybe my concept with the array is also not the best.
Don't use #join, then. Use #each or #map. Also, you can use #gsub to remove the quotes:
source_path_array.flatten.each do |path|
f.puts "Source Files=#{path.gsub(/(^"|")$/, '')}"
end
or
f.puts source_path_array.flatten.map do |path|
"Source Files=#{path.gsub(/(^"|")$/, '')}"
end.join("\n")
The second version is probably more I/O efficient.
For this to work (and as an answer to the second part of your question), the source_path_array should contain strings. Here's a way to obtain this:
regex = /<ClCompile Include="(\.\.\\[^"]+)/
File.open(file_name) do |f|
f.each_line do |line|
regex.match(line) do |matches|
source_path_array << matches[1]
end
end
end
If you don't mind reading the entire file in memory at once, this is slightly shorter:
regex = /<ClCompile Include="(\.\.\\[^"]+)/
File.read(file_name).split(/(\r?\n)+/).each do |line|
regex.match(line) do |matches|
source_path_array << matches[1]
end
end
Finally, here's an example using Nokogiri:
require 'nokogiri'
source_path_array = File.open(file_name) do |f|
Nokogiri::XML(f)
end.css('ClCompile[Include^=..\\]').map{|el| el['Include']}
All of these parse out the quotes, so you can remove the #gsub from the first portion.
All together now:
require 'nokogiri'
f.puts File.open(file_name) do |source|
Nokogiri::XML(source)
end.css('ClCompile[Include^=..\\]').map do |el|
"Source Files=#{el['Include']}"
end.join("\n")
and let's not loop twice (#map then #join) when once (a single #reduce) is doable:
require 'nokogiri'
f.puts File.open(file_name) do |source|
Nokogiri::XML(source)
end.css('ClCompile[Include^=..\\]').reduce('') do |memo, el|
memo += "Source Files=#{el['Include']}\n"
end.chomp
Thanks to #Félix Saparelli:
The following worked for me:
source_path_array.flatten.each do |path|
f.puts "Source Files=#{path.delete('"')}"
end

How to take the result from another method

I have a directory structure with sub-directories:
../../../../../MY_PROJECT/TEST_A/cats/
../../../../../MY_PROJECT/TEST_B/dogs/
../../../../../MY_PROJECT/TEST_A/tigers/
../../../../../MY_PROJECT/TEST_A/elephants/
each of which has a file that ends with ".sln":
../../../../../MY_PROJECT/TEST_A/cats/cats.sln
../../../../../MY_PROJECT/TEST_B/dogs/dogs.sln
...
These files contain information specific to their directory. I would like to do the following:
Create a file "myfile.txt" within each sub-directory, and write some strings to them:
../../../../../MY_PROJECT/TEST_A/cats/myfile.txt
../../../../../MY_PROJECT/TEST_B/dogs/myfile.txt
../../../../../MY_PROJECT/TEST_A/tigers/myfile.txt
../../../../../MY_PROJECT/TEST_A/elephants/myfile.txt
Copy a specific string in the ".sln" files to the myfile.txt of certain directories using the following method:
def parse_sln_files
sln_files = Dir["../../../../../MY_PROJECT/TEST_*/**/*.sln"]
sln_files.each do |file_name|
File.open(file_name) do |f|
f.each_line { |line|
if line =~ /C Source files ="..\\/ #"
path = line.scan(/".*.c"/)
puts path
end
}
end
end
end
I would like to do something like this:
def create_myfile
Dir['../../../../../MY_PROJECT/TEST_*/*/'].each do |dir|
File.new File.join(dir, 'myfile.txt'), 'w+'
Dir['../../../../../TEST/TEST_*/*/myfile.txt'].each do |path|
File.open(path,'w+') do |f|
f.puts "some text...."
f.puts "some text..."
f.puts # here I would like to return the result of parse_sln_files
end
end
end
end
Any suggestions on how to express this?
It seems like you want to read list of C file names from a Visual C++ Solution file, and store in a separate file in the same directory. You may have to merge the two loops that you have shown in your code, and do something like this:
def parse_sln_and_store_source_files
sln_files = Dir["../../../../../MY_PROJECT/TEST_*/**/*.sln"]
sln_files.each do |file_name|
#### Lets collect source file names in this array
source_file_names = []
File.open(file_name) do |f|
f.each_line { |line|
if line =~ /C Source files ="..\\/ #"
path = line.scan(/".*.c"/)
############ Add path to array ############
source_file_names << path
end
}
end
#### lets create `myfile.txt` in same dir as that of .sln
test_file = File.expand_path(File.dirname(file_name)) + "/myfile.txt"
File.open(test_file,'w+') do |f|
f.puts "some text...."
f.puts "some text..."
##### Iterate over source file names & write to file
source_file_names.each { |n| f.puts n }
end
end
end
This can be done bit more elegantly with few more refactoring. Also note that this is not tested code, hopefully, you get the gist of what I am suggesting.

Printing lines from a file in ruby

When I start printing lines from a file, I get this error
#<File:0x007ff65ee297b0>
Here is the code
require 'rubygems'
File.open("sample.txt", 'r') do |f|
puts f
end
You are printing the file object. To get the contents line by line, you can use File.foreach
File.foreach('sample.txt', 'r') do |line|
puts line # called for every line
end
To process the whole file at once, you can use the read method on the file object:
File.open('sample.txt', 'r') do |file|
puts file.read # called only once
end
This is not an error. It prints correctly one line which is your File object.
Here your create a file object and you did not ask it to fetch lines or anything else for that matter.
Several good answers already. But here is another way to do it with minimal change to your code:
File.open("sample.txt", 'r').each_line do |f|
puts f
end
Another way :
IO.foreach("sample.txt") {|line| line }
Or
File.foreach('sample.txt') {|line| line }
File::open returns file handle (which apparently is being printed out as #<File:0x007ff65ee297b0>.) If you need the file content line by line you might want to use IO::readlines:
IO.readlines("sample.txt").each do |line|
puts line
end

How to add new line in a file

I want to add newline character below.
But the result is wrong.
Teach me what is wrong.
test.txt(before)
------------------
2014-09
2014-10
2014-11
------------------
test.txt(after)
------------------
2014-09
2014-10
2014-11
------------------
I make a ruby script below, but the result is wrong.
f = File.open("test.txt","r+")
f.each{|line|
if line.include?("2014-10")
f.puts nil
end
}
f.close
the result
------------------
2014-09
2014-10
014-11
------------------
To solve your problem, the easiest way is to create a new file to output your new text into. To do you'll need to open the input file and the output file and iterate each line of the file check the condition and put desired line into the output file.
Example
require 'fileutils'
File.open("text-output.txt", "w") do |output|
File.foreach("text.txt") do |line|
if line.include?("2014-10")
output.puts line + "\n"
else
output.puts line
end
end
end
FileUtils.mv("text-output.txt", "text.txt")
Easy way
File.write(f = "text.txt", File.read(f).gsub(/2014-10/,"2014-10\n"))
Reading and writing a file at the same time can get messy, same thing with other data structures like arrays. You should build a new file as you go along.
Some notes:
you should use the block form of File.open because it will stop you from forgetting to call f.close
puts nil is the same as puts without arguments
single quotes are preferred over double quotes when you don’t need string interpolation
you should use do ... end instead of { ... } for multi-line blocks
File.open(...).each can be replaced with File.foreach
the intermediate result can be stored in a StringIO object which will respond to puts etc.
Example:
require 'stringio'
file = 'test.txt'
output = StringIO.new
File.foreach(file) do |line|
if line.include? '2014-10'
output.puts
else
output << line
end
end
output.rewind
File.open(file, 'w') do |f|
f.write output.read
end

Line replacement in directories clears the whole files?

I am trying to recursively replace a whole line from index.html files into a directory with sub-directories.
The code above puts the right lines I'm searching with the var "pattern", but when I run it, it removes everything form my index.html files.
pattern = "Keyword"
replacement = "<td width=\"30\"><img src=\"styles/img/trans.gif\" width=\"30\"></td>"
Dir.glob('/Users/root/Desktop/directory/test/**/index.html') do |item|
next unless File.file?(item)
File.open(item, "w+:ASCII-8BIT") do |f|
f.each_line do |line|
if line.match(pattern)
my_line = line
line.sub(my_line, replacement)
end
end
end
end
What am I doing wrong ?
You need to read the file first, build the expected output, and then write it:
Dir.glob('/Users/root/Desktop/directory/test/**/index.html') do |item|
next unless File.file?(item)
output = IO.readlines(item).map do |line|
if line.match(pattern)
replacement
else
line
end
end
File.open(item, "w+:ASCII-8BIT") do |f|
f.write output.join
end
end
end
You use File.open with open mode w+ which, according to Ruby documentation, is:
"w+" Read-write, truncates existing file to zero length or creates a new file for reading and writing.
To read the file and put some lines use r:
File.open(item, "r:ASCII-8BIT")

Resources