Rename specific files depending on a diferent file in same directory - ruby

I'm practising some programming and I'm now faced with the following issue. I have a folder with multiple subfolders inside. Each subfolder contains two files: an .xlsx and a .doc file. I want to rename the .xlsx depending on the name of the .doc file. For example, in directory documents\main_folder\folder_1 there are two files: test_file.xlsx and final_file.doc. After running my code, result should be final_file.xlsx and final_file.doc. This must happen with all subfolders.
My code so far:
require 'FileUtils'
filename = nil
files = Dir.glob('**/*.doc')
files.each do |rename|
filename = File.basename(rename, File.extname(rename))
puts "working with file: #{filename}"
end
subs = Dir.glob('**/*.xlsx')
subs.each do |renaming|
File.rename(renaming, filename)
end
Two issues with this code: firstly, the .xlsx is moved where the .rb file is located. Secondly, renaming is partially achieved, only that the extension is not kept, but completely removed. Any help?

Dir.glob('**/*.doc').each do |doc_file|
# extract folder path e.g. "./foo" from "./foo/bar.doc"
dir = File.dirname(doc_file)
# extract filename without extension e.g. "bar" from "./foo/bar.doc"
basename = File.basename(doc_file, File.extname(doc_file))
# find the xlsx file in the same folder
xlsx_file = Dir.glob("#{dir}/*.xlsx")[0]
# perform the replacement
File.rename(xlsx_file, "#{dir}/#{basename}.xlsx")
end
edit
the validation step you requested:
# first, get all the directories
dirs = Dir.glob("**/*").select { |path| File.directory?(path) }
# then validate each of them
dirs.each do |dir|
[".doc", ".xlxs"].each do |ext|
# raise an error unless the extension has exactly 1 file
unless Dir.glob("#{dir}/*#{ext}").count == 1
raise "#{dir} doesn't have exactly 1 #{ext} file"
end
end
end
You can also bunch up the errors into one combined message if you prefer ... just push the error message into an errors array instead of raising them as soon as they come up

Related

Collect all the zip files having same name and zip it again Ruby

I have a directory which contains multiple logs for same script with timestamp. I want to collect all the zip files and make a new zip.
Directory Structure:
Test_1_Run_Logs_06-12-2018_10_15_35.zip
Test_1_Integration_Logs_06-12-2018_10_15_35.zip
Test_1_Interface_Logs_06-12-2018_10_15_35.zip
Test_2_Run_Logs_06-12-2018_10_30_35.zip
Test_2_Integration_Logs_06-12-2018_10_30_35.zip
Test_2_Interface_Logs_06-12-2018_10_30_35.zip
I have separated all the files having same name. The zip file is not moving all the zip files. How to do it in ruby
Code
require 'fileUtils'
require 'zip'
scriptNameArr = []
logFolder = 'C:/Users/Desktop/logs/'
copyFolder = "C:/Users/admin/Desktop/Test/Ruby Test/copyFolder/"
# Collect all the files present in logFolder separating by timestamp
Dir.entries("#{logFolder}/").each do |fName|
unless (File.directory? "#{logFolder}#{fName}")
scriptNameArr << fName.split("/").last.split(/_\d+-\d+-/)[0]
end
end
scriptNameArr.uniq!
# Create a new zip into copy
scriptNameArr.each do |scriptName|
zipName = "#{copyFolder}#{scriptName}.zip"
Dir.mkdir(copyFolder) unless (Dir.exist?(copyFolder))
FileUtils.rm(zipName) if File.exist? (zipName)
Zip::File.open(zipName, Zip::File::CREATE) do |zip|
Dir.glob("#{logFolder}#{scriptName}*") { |file|
fileName = file.split("/").last
zip.add(fileName, logFolder)
}
end
end
It is creating empty zip everytime. What should i do to copy the zip file and paste in new location?
I think you miss filename when zip.add(fileName, logFolder)
Correct:
zip.add(fileName, File.join(logFolder, fileName))
Happy Coding! :)

Error while rewriting from temp file

I'm writing to a file from a temp file, when I try to read the file that has been written from the temp file, it seems to be adding an extra character to the directory called tmp. (file is passed in through optparse)
Source:
require 'tempfile'
PATH = Dir.pwd
def format_file
puts 'Writing to temporary file..'
if File.exists?(OPTIONS[:file])
file = Tempfile.new('file')
IO.read(OPTIONS[:file]).each_line do |s|
File.open(file, 'a+') { |format| format.puts(s) unless s.chomp.empty? }
end
IO.read(file).each_line do |file|
File.open("#{PATH}/tmp/#sites.txt", 'a+') { |line| line.puts(file) }
end
puts "File: #{OPTIONS[:file]}, has been formatted and saved as #sites.txt in the tmp directory."
else
puts <<-_END_
Woah now my friend! I know you're eager to get those vulns;
But file: #{OPTIONS[:file]} doesn't exist or in this directory at least!
What I'm gonna need you to do is go move that file over here.
It's okay, you're forgiven, I'll wait until you return..
_END_
end
end
Example:
ruby whitewidow.rb -f sites.txt
[12:40:43 INFO]Formatting file
[12:40:43 INFO]Writing to temporary file..
[12:40:43 INFO]File: tmp/sites.txt, has been formatted and saved as #sites.txt in the tmp directory.
[12:40:43 INFO]Let's check out this file real quick like..
whitewidow.rb:224:in `read': No such file or directory # rb_sysopen - C:/Users/Justin/MyScripts/RubySQL/whitewidow/#tmp/#sites.txt (Errno::ENOENT)
#<= Correct path but the '#' in tmp shouldn't be there..
What it does is format the file to remove any empty lines within it (this program doesn't like empty lines) from there it should write to a temp file, rewrite from the temp file back to the original directory (whitewidow/tmp/) and delete the temp file (I know how to do this part).
It seems to me like while rewriting back to the original directory it's adding a # to the directory name (#tmp is actually tmp) is there a reason that it's adding this?
I fixed it, for some reason the program was adding a # to the path, so I gsubed out the # and it works.

How do I open each file in a directory with Ruby?

I need to open each file inside a directory. My attempt at this looks like:
Dir.foreach('path/to/directory') do |filename|
next if filename == '.' || filename == '..'
puts "working on #{filename}"
# this is where it crashes
file = File.open(filename, 'r')
#some code
file.close
# more code
end
My code keeps crashing at File.open(filename, 'r'). I'm not sure what filename should be.
The filename should include the path to the file when the file is not in the same directory than the Ruby file itself:
path = 'path/to/directory'
Dir.foreach(path) do |filename|
next if filename == '.' || filename == '..'
puts "working on #{filename}"
file = File.open("#{path}/#{filename}", 'r')
#some code
file.close
# more code
end
I recommend using Find.find.
While we can use various methods from the Dir class, it will look and retrieve the list of files before returning, which can be costly if we're recursively searching multiple directories or have a huge number of files embedded in the directories.
Instead, Find.find will walk the directories, returning both the directories and files as each is found. A simple check lets us decide which we want to continue processing or whether we want to skip it. The documentation has this example which should be easy to understand:
The Find module supports the top-down traversal of a set of file paths.
For example, to total the size of all files under your home directory, ignoring anything in a “dot” directory (e.g. $HOME/.ssh):
require 'find'
total_size = 0
Find.find(ENV["HOME"]) do |path|
if FileTest.directory?(path)
if File.basename(path)[0] == ?.
Find.prune # Don't look any further into this directory.
else
next
end
else
total_size += FileTest.size(path)
end
end
I'd go for Dir.glob or File.find. But not Dir.foreach as it returns . and .. which you don't want.
Dir.glob('something/*').each do |filename|
next if File.directory?(filename)
do_something_with_the_file(filename)
end

How do I copy .htaccess files using Rake?

I am in the process of creating some build scripts, using Rake, that will be used as part of the overall process of deploying our web services to the cloud via Docker containers. In order to accomplish this we combine resources from several repos using Rake to "assemble" the directory/file layout. This all work well save for one item, .htaccess files.
Here is the copy function that I've created:
require 'fileutils'
EXT_ALLOWED = ["html", "css", "js", "svg", "otf", "eot", "ttf", "woff", "jpeg", "map", "ico", "map", "png", "db", "php", "conf"]
def copy_to(dest, src, trim="")
files = FileList.new()
EXT_ALLOWED.each {|ext| files.include "#{src}/**/*.#{ext}"}
files.each do |file|
dir = File.dirname(file)
filename = File.basename(file)
trimming = "/shared/" + trim + "(.*)"
path = dir.match(trimming)
if path == nil || dest == path[1] + '/'
bin = dest
else
bin = File.join(dest, path[1] + '/')
end
puts "copying #{file} to #{bin}"
FileUtils.mkdir_p(bin)
FileUtils.cp file, bin
end
end
The usage for this would be:
desc 'copies from shared/admin to the base server directory'
task :admin do
# Copy admin over
dest = 'www-server/'
src = '../shared/admin'
trim = "admin/"
copy_to dest, src, trim
end
The trim variable is there to make sure files are copied to the appropriate directories. In this case files in admin are copied directly to www-server without an admin subdirectory.
I, naively, tried adding "htaccess" to the EXT_ALLOWED array, but that failed.
I have also followed some items online, but most have to do with Octopress which does not solve the problem.
The .htaccess file is in ../shared/admin and needs to end up in www-server/, can I make that happen within this function? Or do I need to write something specifically for file names beginning with dots?
In this case, looking for a quick and dirty (yes...I feel dirty doing it this way!) option, I wrote a function which specifically looks for the .htaccess file in a particular directory:
def copy_htaccess(src, dest)
files = Dir.glob("#{src}/.*")
files.each do |file|
filename = File.basename(file)
if filename == ".htaccess"
puts "copying #{file} to #{dest}"
FileUtils.mkdir_p(dest)
FileUtils.cp file, dest
end
end
end
With the usage being performed this way:
desc 'copies the .htaccess file from one root to the web root'
task :htaccess do
src = '../shared/admin'
dest = 'www-server/'
copy_htaccess src, dest
end
Here I am able to use Dir.glob() to list all files starting with a ., then test for the .htaccess file and perform the copying.
I will be looking into ways to modifying the single copy function to make this cleaner, if possible. Perhaps this can be done by globbing the directory and adding the files starting with . to the files array.
EDIT: Rather than creating an additional function I found that I could just push the .htaccess file's information onto the end of the files array in the original copying function, after first checking if it exists in the source directory:
if File.file?("#{src}/.htaccess")
files.push("#{src}/.htaccess")
end
Making the whole function as shown below:
def copy_to(dest, src, trim="")
files = FileList.new()
EXT_ALLOWED.each {|ext| files.include "#{src}/**/*.#{ext}"}
if File.file?("#{src}/.htaccess")
files.push("#{src}/.htaccess")
end
files.each do |file|
dir = File.dirname(file)
filename = File.basename(file)
trimming = "/shared/" + trim + "(.*)"
path = dir.match(trimming)
if path == nil || dest == path[1] + '/'
bin = dest
else
bin = File.join(dest, path[1] + '/')
end
puts "copying #{file} to #{bin}"
FileUtils.mkdir_p(bin)
FileUtils.cp file, bin
end
end
Note that I am using .file? to test for an actual file where .exists? can return a directories truthiness. In the end you can use either method depending on your situation.

Search in current dir only

Im using
Find.find("c:\\test")
to search for files in a dir. I just want to search the dir at this level though, so any dir within c:\test does not get searched.
Is there another method I can use ?
Thanks
# Temporarily make c:\test your current directory
Dir.chdir('c:/test') do
# Get a list of file names just in this directory as an array of strings
Dir['*'].each do |filename|
# ...
end
end
Alternatively:
# Get a list of paths like "c:/test/foo.txt"
Dir['c:/test/*'] do |absolute|
# Get just the filename, e.g. "foo.txt"
filename = File.basename(absolute)
# ...
end
With both you can get just the filenames into an array, if you like:
files = Dir.chdir('c:/text'){ Dir['*'] }
files = Dir['c:/text/*'].map{ |f| File.basename(f) }
Find's prune method allows you to skip a current file or directory:
Skips the current file or directory,
restarting the loop with the next
entry. If the current file is a
directory, that directory will not be
recursively entered. Meaningful only
within the block associated with
Find::find.
Find.find("c:\\test") do |path|
if FileTest.directory?(path)
Find.prune # Don't look any further into this directory.
else
# path is not a directory, so must be file under c:\\test
# do something with file
end
end
You may use Dir.foreach(), for example, to list all the files under c:\test
Dir.foreach("c:\\test") {|x| puts "#{x}" }

Resources