Getting a list of folders in a directory - ruby

How do I get a list of the folders that exist in a certain directory with ruby?
Dir.entries() looks close but I don't know how to limit to folders only.

I've found this more useful and easy to use:
Dir.chdir('/destination_directory')
Dir.glob('*').select {|f| File.directory? f}
it gets all folders in the current directory, excluded . and ...
To recurse folders simply use ** in place of *.
The Dir.glob line can also be passed to Dir.chdir as a block:
Dir.chdir('/destination directory') do
Dir.glob('*').select { |f| File.directory? f }
end

Jordan is close, but Dir.entries doesn't return the full path that File.directory? expects. Try this:
Dir.entries('/your_dir').select {|entry| File.directory? File.join('/your_dir',entry) and !(entry =='.' || entry == '..') }

In my opinion Pathname is much better suited for filenames than plain strings.
require "pathname"
Pathname.new(directory_name).children.select { |c| c.directory? }
This gives you an array of all directories in that directory as Pathname objects.
If you want to have strings
Pathname.new(directory_name).children.select { |c| c.directory? }.collect { |p| p.to_s }
If directory_name was absolute, these strings are absolute too.

Recursively find all folders under a certain directory:
Dir.glob 'certain_directory/**/*/'
Non-recursively version:
Dir.glob 'certain_directory/*/'
Note: Dir.[] works like Dir.glob.

With this one, you can get the array of a full path to your directories, subdirectories, subsubdirectories in a recursive way.
I used that code to eager load these files inside config/application file.
Dir.glob("path/to/your/dir/**/*").select { |entry| File.directory? entry }
In addition we don't need deal with the boring . and .. anymore. The accepted answer needed to deal with them.

directory = 'Folder'
puts Dir.entries(directory).select { |file| File.directory? File.join(directory, file)}

You can use File.directory? from the FileTest module to find out if a file is a directory. Combining this with Dir.entries makes for a nice one(ish)-liner:
directory = 'some_dir'
Dir.entries(directory).select { |file| File.directory?(File.join(directory, file)) }
Edit: Updated per ScottD's correction.

Dir.glob('/your_dir').reject {|e| !File.directory?(e)}

$dir_target = "/Users/david/Movies/Camtasia 2/AzureMobileServices.cmproj/media"
Dir.glob("#{$dir_target}/**/*").each do |f|
if File.directory?(f)
puts "#{f}\n"
end
end

For a generic solution you probably want to use
Dir.glob(File.expand_path(path))
This will work with paths like ~/*/ (all folders within your home directory).

We can combine Borh's answer and johannes' answer to get quite an elegant solution to getting the directory names in a folder.
# user globbing to get a list of directories for a path
base_dir_path = ''
directory_paths = Dir.glob(File.join(base_dir_path, '*', ''))
# or recursive version:
directory_paths = Dir.glob(File.join(base_dir_path, '**', '*', ''))
# cast to Pathname
directories = directory_paths.collect {|path| Pathname.new(path) }
# return the basename of the directories
directory_names = directories.collect {|dir| dir.basename.to_s }

Only folders ('.' and '..' are excluded):
Dir.glob(File.join(path, "*", File::SEPARATOR))
Folders and files:
Dir.glob(File.join(path, "*"))

I think you can test each file to see if it is a directory with FileTest.directory? (file_name). See the documentation for FileTest for more info.

Related

Recursively scan specific folders for a file in ruby

I'm trying to recursively scan specific folders and search a specific file.
In the root folder (e.g., C:\Users\Me), I would like to scan just the folders called my* (so, the folders that start with the letters 'my' + whatever), then see if there is files .txt and store the first line in a variable.
For the scan i'm trying this code, but without succeed
require 'find'
pdf_file_paths = []
path_to_search = ['C:\Users\Me'];
Find.find('path_to_search') do |path|
if path =~ /.*\.txt$/
#OPEN FILE
end
I'd do as below :
first_lines_of_each_file = []
Dir.glob("C:/Users/Me/**/my**/*.txt",File::FNM_CASEFOLD) do |filepath|
File.open(filepath,'rb') { |file| first_lines_of_each_file << file.gets }
end
File::FNM_CASEFOLD constant would search all the directories and files using case insensitive search. But if you want case sensitive search, then don't need use the second argument File::FNM_CASEFOLD.
If you have directories organized as
C:/Users/Me/
|- my_dir1/
|- a.txt
|- my_dir2/
|- foo.txt
|- baz.doc
|- my_dir3/
|- biz.txt
Dir.glob("C:/Users/Me/**/my**/*.txt" will give you all the .txt files. As the search is here recursive.
Dir.glob("C:/Users/Me/my**/*.txt" will give you only the .txt files, that resides inside the directory, which are direct children of C:/Users/Me/. That's only files you will get are a.txt, biz.txt.
This should do the job:
lines = Dir.glob("#{path}/**/my*/*.txt").map do |filename|
File.open(filename) do |f|
f.gets
end
end
Dir.glob is similar to the glob executable on a *nix machine. This also works on Windows. gets gets the first line. Ensure that you use a forward slash even for a Windows machine.
I am not sure whether this is the cleanest solution, but you can try:
def find_files(file_name, root_path, folder_pattern = nil)
root_path = File.join(root_path, '')
paths = Dir[File.join(root_path, '**', file_name)]
paths.keep_if! {|p| p.slice(path.size, p.size).split('/').all? {|s| s =~ folder_pattern}} if folder_pattern
end
find_files('C:/Users/Me', 'find_me.txt', /my.*/)

Appending filenames in Ruby?

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.

Empty directory (delete all files)

What would be a safe and efficient way to delete all files in a directory in pure Ruby? I wrote
Dir.foreach(dir_path) {|f| File.delete(f) if f != '.' && f != '..'}
but it gives me a No such file or directory error.
Thank you.
What about FileUtils.rm_rf("#{dir_path}/.", secure: true)?
FileUtils.rm_rf Dir.glob("#{dir_path}/*") if dir_path.present?
You're probably getting that error because your current working directory doesn't match dir_path -- File.delete(f) is being given just the filename for a file in dir_path. (I hope you didn't have any important files in the current working directory with same names in the dir_path directory.)
You need to use File.join(dir_path, f) to construct the filename you wish to delete. You also need to figure out how you want to handle directories:
Dir.foreach(dir_path) do |f|
fn = File.join(dir_path, f)
File.delete(fn) if f != '.' && f != '..'
end
Errno::EISDIR: Is a directory - /tmp/testing/blubber
from (irb):10:in `delete'
from (irb):10
from (irb):10:in `foreach'
from (irb):10
from :0
Everybody suggest rm_rf, but the safer way is to use rm_f, which is an alias to rm force: true.
FileUtils.rm_f Dir.glob("#{dir_path}/*")
This method will not remove directories, it will only remove files: http://ruby-doc.org/stdlib-2.2.0/libdoc/fileutils/rdoc/FileUtils.html#method-c-rm
Now days, I like using the Pathname class when working on files or directories.
This should get you started:
Pathname.new(dir_path).children.each { |p| p.unlink }
unlink will remove directories or files, so if you have nested directories you want to preserve you'll have to test for them:
Removes a file or directory, using File.unlink if self is a file, or Dir.unlink as necessary.
To tack on to MyroslavN, if you wanted to do it with Pathname (like with Rails.root, if it's a rails app) you want this:
FileUtils.rm_rf Dir.glob(Rails.root.join('foo', 'bar', '*'))
I'm posting this because I did a bad copypaste of his answer, and I accidentally wound up with an extra slash in my path:
Rails.root.join('foo', 'bar', '/*')
Which evaluates to /* because it sees it as a root path. And if you put that in the FileUtils.rm_rf Dir.glob it will attempt to recursively delete everything it can (probably limited by your permissions) in your filesystem.
The Tin Man almost got it right - but in case the directories are not empty use
Pathname.new(dir_path).children.each { |p| p.rmtree }
Here's another variation that's nice and succinct if you only have files or empty directories:
your_path.children.each.map(&:unlink)
Dir.foreach(dir_path) {|f| File.delete("#{dir_path}/#{f}") if f != '.' && f != '..'}

How to get Ruby Dir#glob to return basenames, not absolute_paths?

FakeProfilePictures::Photo.all_large_names_2x (defined below) returns an array of absolute path names, but when I do Dir["picture_*#2x.*"] from the correct directory in irb, I only get the basenames (what I want). What's the best way to get the base names? I know I could do it by adding .map { |f| File.basename(f) } as shown in the comment, but is there an easier/better/faster/stronger way?
module FakeProfilePictures
class Photo
DIR = File.expand_path(File.join(File.dirname(__FILE__), "photos"))
# ...
def self.all_large_names_2x
##all_large_names_2x ||= Dir[File.join(DIR, "picture_*#2x.*")] # .map { |f| File.basename(f) }
end
end
end
You can do
Dir.chdir(DIR) do
Dir["picture_*#2x.*"]
end
after the block, the original dir is restored.
You could chdir into DIR before globbing, but I would just run everything through basename.

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