Given two paths, one parent and a child folder(or grandchild, grand-grandchild, etc), find the relative depth the child folder from the parent folder. It is always guaranteed that child folder is always present inside the parent folder and the paths always end with slash.
Example:
parent = "/home/user/path/parent/"
child = "/home/user/path/parent/g/g/child/"
#ans = 3
The most simple way to solve this will look like:
parent = '/home/user/path/parent/'
child = '/home/user/path/parent/g/g/child/'
# Split the paths
child_chunks = child.split(File::SEPARATOR).reject(&:empty?)
parent_chunks = parent.split(File::SEPARATOR).reject(&:empty?)
# Find the diff between the two lists
puts (child_chunks - parent_chunks).length # => 3
The better way though would be to use Pathname helper:
require 'pathname'
parent = Pathname.new('/home/user/path/parent/')
child = Pathname.new('/home/user/path/parent/g/g/child/')
puts child.relative_path_from(parent).to_s.split(File::SEPARATOR).length # => 3
def relative_depth(path1, path2)
path2.count(File::SEPARATOR) - path1.count(File::SEPARATOR)
end
path1 = 'a/b/c/'
path2 = 'a/b/c/d/e/f/'
relative_depth(path1, path2)
#=> 3
path3 = 'C:a\\b\\c\\'
path4 = 'C:a\\b\\c\\d\\e\\f\\'
File::SEPARATOR = "\\" # simulate Windows
relative_depth(path3, path4)
#=> 3
See String#count.
Demo
Related
Okay, so I want to take a file path that I have, remove a known root path, and append a new one.
I will attempt to make an example:
# This one is a path object
original_path = '/home/foo/bar/path/to/file.txt'
# This one is a string
root_path = '/home/foo/bar/'
# This is also a string
new_root = '/home/new/root/'
So, I have original_path, which is a path object. And I want to remove root_path from this, and apply new_root to the front of it. How can I do this?
EDIT:
This is my real problem, sorry for the poor explaination before:
require 'pathname'
# This one is a path object
original_path = Pathname.new('/home/foo/bar/path/to/file.txt')
# This one is a string
root_path = '/home/foo/bar/'
# This is also a string
new_root = '/home/new/root/'
Now how do you substitute those?
If you are just trying to get a new string, you can do this
# This one is a path object
original_path = '/home/foo/bar/path/to/file.txt'
# This one is a string
root_path = '/home/foo/bar/'
# This is also a string
new_root = '/home/new/root/'
new_path = original_path.gsub(root_path, new_root)
Edit
You can still use sub instead of gsub if original_path is a Pathname
new_path = original_path.sub(root_path, new_root)
I am writing a program where I cycle through all files in sub-folders of a target folder and do stuff with what's writen in it. So my local folder looks like that
Folder
--Subfolder
---File
---File
---File
--Subfolder
---File
.
.
.
So I have a each loop to cycle through all subfolders and for each subfolder I am calling a method that basically do the same thing but in the subfolder and call for each file another method (parsing it a file argument which I obtained through a Dir.foreach(folder){ |file| method(file)} command).
So it looks like this :
Dir.foreach(Dir.pwd){ |folder| call_method(folder) }
def call_method(folder) Dir.foreach(folder){|file| reading_method(file) } end
That last called method (reading_method) should open called a C method and parse as an argument the full path of the file (so that the C program can open it) so I'm using File.absolute_path(file) in the reading_method but instead of returning C:/folder/subfolder/file as I want it to, it returns C:/folder/file skipping the subfolder (and thus the C program fail to execute).
Is there a way to get the full path of that file ?
Thanks for your help
EDIT : Here is the full code as asked
## Module
module GBK_Reader
PATH = "Z:/Folder/"
SAFETY = true
SAFETY_COUNT = 10
end
## Methods definitions
def read_file(file)
path = File.absolute_path(file)
c_string = `C:/Code/GBK_Reader/bin/Debug/GBK_Reader.exe #{path}`
return c_string.split(/ /).collect!{|spec| spec.to_i}
end
def read_folder(folder)
Dir.foreach(folder){ |file|
next if File.extname(file) != ".gbk"
temp = read_file(file)
#$bacteria_specs[0] += temp[0]
#$bacteria_specs[1] += temp[1]
}
return $bacteria_specs
end
## Main
# Look for folder
Dir.chdir(GBK_Reader::PATH)
puts "Directory found"
# Cycle through all sub-folders
$high_gc = {} #Hash to store high GC content bacterias
$count = 0
puts "Array variable set"
Dir.foreach(Dir.pwd){ |file|
next if file == "." || file == ".."
break if $count >= GBK_Reader::SAFETY_COUNT
$count += 1 if GBK_Reader::SAFETY
$bacteria_specs = [0.00, 0.00, 0.00]
$path = File.expand_path(file)
if File.directory?(file)
# Cycle through all .gbk files in sub-folder and call C program
read_folder(file)
else
# Call C program to directly evaluate GC content
c_string = read_file(file) if File.extname(file) == ".gbk"
$bacteria_specs[0] = c_string[0].to_i
$bacteria_specs[1] = c_string[1].to_i
end
# Evaluate GC content and store suitable entries
$bacteria_specs[2] = ($bacteria_specs[0]/$bacteria_specs[1])*100.00
$high_gc[file] = $bacteria_specs if $bacteria_specs[2] > 60
}
# Display suitable entries
puts "\n\n\n"
puts $high_gc
gets.chomp
Ok, I may have found something but it seems ugly so if anyone has a better solution by all means go ahead.
I edited my read_folder method to parse the full path to the read_file method as follow :
def read_folder(folder)
Dir.foreach(folder){ |file|
next if File.extname(file) != ".gbk"
path = File.absolute_path(folder)+'/'+File.basename(file)
temp = read_file(path)
$bacteria_specs[0] += temp[0]
$bacteria_specs[1] += temp[1]
}
return $bacteria_specs
end
And I do get the path I expect. (though my calling the C program still fails so I'll have to check somewhere else :D)
I have two paths: application root path and target path. What is the simplest way to ensure that the target path is the children of application root path?
Basically the target path provided by the user is to be displayed by my server. But I want to constrain my server so only the files under the application root path are displayable. So I want to check that the target path is under the root path.
The root path can contain nested directories.
Another way:
def child?(root, target)
raise ArgumentError, "target.size=#{target.size} < #{root.size} = root.size"\
if target.size < root.size
target[0...root.size] == root &&
(target.size == root.size || target[root.size] == ?/)
end
root = "/root/app"
p child?(root, "/root/app/some/path") # => true
p child?(root, "/root/apple") # => false
p child?(root, "/root/app") # => true
p child?(root, "/root") # => ArgumentError: target.size = 5 < 9 = root.size
Perhaps a bit inefficient, but foolproof
require 'find'
Find.find(root).include?(target)
You haven't provided any use case, thus I assume the following variables apply to your case
root = "/var/www/"
target = "/var/www/logs/something/else.log"
You can use a regular expression or even more simple String#start_with?
target.start_with?(root)
How about a regular expression:
root = "/root/app/"
target = "/root/app/some/path"
target =~ /^#{root}/
You could use Pathname#realdirpath to convert relative paths to absolute paths if needed.
I like Pathname#ascend for this:
ancestor = Pathnew.new(path1).realdirpath
descendant = Pathnew.new(path2).realdirpath
descendant.ascend { |path| break true if path == ancestor } || false
(Pathname#== works by string comparison, so it helps to sanitize your inputs with #realdirpath first.)
To avoid path injection you can use File.path method:
[21] pry(main)> path_1 = File.path(Rails.root.join('public', '../config/routes.rb') )
=> "/project_path/config/routes.rb"
[22] pry(main)> path_2 = File.path(Rails.root.join('public', 'robots.txt') )
=> "/project_path/public/robots.txt"
[24] pry(main)> path_1.include?(Rails.root.join('public').to_s )
=> false
[25] pry(main)> path_2.include?(Rails.root.join('public').to_s )
=> true
I'm very new to Ruby and branching out past first scripts asking what my favorite color is and repeating it back to me. I'm doing what I thought was a relatively simple task, moving files and changing the names.
I have a bunch of files in subdirectories that I need to move to a single directory and then append the file names of all of them. Specifically need to keep the original name and add onto the end, IE AAB701.jpg -> AAB701_01.jpg.
I have managed to find the files and move them (probably inefficiently) but I'm having no luck appending to the file name. Google search, stackoverflow, etc, no luck.
This is the code that I have now.
require 'find'
require "fileutils"
file_paths = []
Find.find('../../../Downloads') do |path|
file_paths << path if path =~ /.*\.jpg$/
end
file_paths.each do |filename|
name = File.basename('filename')
dest_folder = "../../../Desktop/Testing/"
FileUtils.cp(filename, dest_folder)
end
file_paths.each do |fullname|
append_txt = '_01'
filename = "*.jpg"
fullname = File.join(filename, append_txt)
end
The actual paths are pretty inconsequential, but I'm not familiar enough with File.join or gsub to figure out what is wrong/best.
First I'd extract some work into a small method:
def new_name(fn, dest = '../../../Desktop/Testing/', append = '_01')
ext = File.extname(fn)
File.join( dest, File.basename(fn, ext) + append + ext )
end
Then I'd apply a more functional style to your directory traversal and processing:
Dir[ '../../../Downloads/**/*.jpg' ].
select { |fn| File.file? fn }.
each { |fn| FileUtils.cp fn, new_name(fn) }
Also, I don't see what the Find module buys you over Dir#[] and the dir glob let's you filter to jpgs for free.
A simpler answer is for a file:
require 'pathname'
new_name =Pathname(orig_fn).sub_ext("01#{Pathname(orig_fn).extname}").to_s
I would modify your call to FileUtils.cp.
append_txt = '_01'
file_paths.each do |filename|
name = File.basename('filename')
newname = name + append_txt # + File.extension()
dest_folder = "../../../Desktop/Testing/"
FileUtils.cp(filename, dest_folder + newname)
end
Note that this code is not safe against malicious filenames; you should search the file handling docs for another way to do this.
I have two classes in my REST API wrapeer: Wrapper::Folder and Wrapper::File. Each folder can contain numerous folders and files. The contents of folder can be retrived by .list. class method.
I want to implement .all class method for Wrapper::File which would return an array of all files in all folders.
The following method doesn't work but shows something like I want.
class Wrapper::File
def self.all
folders = Wrapper::Folder.list('/')
files = []
while folders.size > 0
folders.each do |object|
if object.is_a?(Wrapper::Folder)
folders = Wrapper::Folder.list('/')
else
files << object
end
end
end
end
end
Untested, but this would be the basic gist of the recursive solution. Would return an array of file names (with paths) and without directories.
def getFilesRecursive(path)
# create our directory object and file list storage
d = Dir.new(path)
l = Array.new
# iterate over our given directory
d.each do |f|
# exclude . and ..
if !(f =~ /$[\.]{1,2}^/)
# recurse on a directory
if File.directory?(f)
l += getFilesRecursive(path + f)
# store on a file
else
l.push(path + f)
end
end # if not . or ..
end # Dir.each
# return our list of files
return l
end # getFlesRecursive()
# let's get some files!
files = getFilesRecursive("/")