I have a deeply nested folder structure on a Windows 7 machine. Windows refuses to delete the directories as their names are too long. I want to rename all subfolders to something like 2 in the hope that it will be short enough to be deleted. This is my script:
#count = 0
Dir.glob("**/*") do |file| #find src files in current folder and all subfolders
if File.directory?(file)
File.rename(file, File.dirname(file) + File::SEPARATOR + "2")
#count += 1
end
end
puts #count
When the script runs, instead of renaming all sub-directories, it changes one more sub-directory, gradually going one level deeper each time. I.e., the output from running the script at the moment is:
C:\>renamer.rb
30
C:\>renamer.rb
31
C:\>renamer.rb
32
I'm confused as to why this is happening and would appreciate any input.
Am I taking the correct approach? I assume Ruby's recursive directory deletion methods would fail. However, when I try and execute
require "FileUtils"
FileUtils.remove_dir ("2", force = true)
I get the error
syntax error, unexpected ',', expecting ')'
FileUtils.remove_dir ("2", force = true)
^
syntax error, unexpected ')', expecting end-of-input
FileUtils.remove_dir ("2", force = true)
^
The problem is that Dir.glob("**/*") returns an array like this:
['folder', 'folder/sub', 'folder/sub/sub']
Now when you do:
File.rename(file, File.dirname(file) + File::SEPARATOR + "2")
it will rename folder, but when it reaches folder/sub, that doesn't exist anymore, because you have renamed folder to 2: it will be 2/sub instead of folder/sub. The solution is to reverse the array. This starts the renaming process on the deepest level and works its way up to the top level:
Dir.glob("**/*").reverse.each do |file|
# rest of your code can stay the same
end
As for your second problem, instead of:
FileUtils.remove_dir ("2", force = true)
You should use:
FileUtils.remove_dir("2", true)
First of all, make sure there is no space between remove_dir and (. That's what's causing the error.
Also force is the name of the parameter and by default it's false. That's why you see force = false in the API. If you want force to be true you can simply pass true to the function, like I show above.
Related
I have the following code :
system('cls')
Dir.chdir("testing")
puts Dir.pwd
Dir.glob('*.csv').each do|csv_filename|
next if csv_filename == '.' or csv_filename == '..'
puts "\t" + csv_filename
end
file_Num= Dir[".testing/*"].length
puts "file count = " + file_Num.to_s
I am trying to display all csv filenames within the testing directory and get a count of such csv files, not directories. The above renders only the correct csv file names as expected but file count always = 0. Yes, I am new trying to teach myself Ruby but I've searched for what I am trying to accomplish and cannot seem to put the pieces together. I need a file count because if that num is > 3 I would like to send an alert to the screen of some type. Wold appreciate any help on this. Thanks!
Look like you put the wrong path in file_Num= Dir[".testing/*"].length
Should be
file_Num = Dir["*.csv"].length
as you already change dir to testing.
In case you would like to count all csv files in subdirectories
file_Num = Dir["**/*.csv"].length
this is a text file renamer i made, you throw the file in a certain folder and the program renames them to file1.txt, file2.txt, etc
it gets the job done but it's got two problems
it gives me this error no implicit conversion of nil into String error
if i add new files into the folder where there's already organized files, they're all deleted and a new file is created
what's causing these problems?
i=0
Dir.chdir 'C:\Users\anon\Desktop\newfolder'
arr = Dir.entries('C:\Users\anon\Desktop\newfolder')
for i in 2..arr.count
if (File.basename(arr[i]) == 'file'+((i-1).to_s)+'.txt')
puts (arr[i]+' is already renamed to '+'file'+i.to_s)
else
File.rename(arr[i],'file'+((i-1).to_s)+'.txt')
end
end
There are two main problems in your program.
The first is that you are using an out of bounds value in the array arr. Try this a = [1,2,3]; a[a.count] and you will get nil because you are trying at access a[3] but the last element in the array has index 2.
Then, you are using as indexes for names fileINDEX.txt always 2...foobar without taking into account that some indexes may be already used in your directory.
Extra problem, you are using Dir.entries, this in my OS gives regular entries more . and .. which should be managed properly, they are not what you want to manipulate.
So, I wrote you a little script, I hope you find it readable, to me it works. You can improve it for sure! (p.s. I am under Linux OS).
# Global var only to stress its importance
$dir = "/home/p/tmp/t1"
Dir.chdir($dir)
# get list of files
fnames = Dir.glob "*"
# get the max index "fileINDEX.txt" already used in the directory
takenIndexes = []
fnames.each do |f|
if f.match /^file(\d+).txt/ then takenIndexes.push $1.to_i; end
end
# get the first free index available
firstFreeIndex = 1
firstFreeIndex = (takenIndexes.max + 1) if takenIndexes.length > 0
# get a range of fresh indexes for possible use
idxs = firstFreeIndex..(firstFreeIndex + (fnames.length))
# i transform the range to list and reverse the order because i want
# to use "pop" to get and remove them.
idxs = idxs.to_a
idxs.reverse!
# rename the files needing to be renamed
puts "--- Renamed files ----"
fnames.each do |f|
# if file has already the wanted format then move to next iteration
next if f.match /^file\d+.txt/
newName = "file" + idxs.pop.to_s + ".txt"
puts "rename: #{f} ---> #{newName}"
File.rename(f, newName)
end
So I have the following little script to make a file setup for organizing reports that we get.
#This script is to create a file structure for our survey data
require 'fileutils'
f = File.open('CustomerList.txt') or die "Unable to open file..."
a = f.readlines
x = 0
while a[x] != nil
Customer = a[x]
FileUtils.mkdir_p(Customer + "/foo/bar/orders")
FileUtils.mkdir_p(Customer + "/foo/bar/employees")
FileUtils.mkdir_p(Customer + "/foo/bar/comments")
x += 1
end
Everything seems to work before the while, but I keep getting:
'mkdir': Invalid argument - Cust001_JohnJacobSmith(JJS) (Errno::EINVAL)
Which would be the first line from the CustomerList.txt. Do I need to do something to the array entry to be considered a string? Am I mismatching variable types or something?
Thanks in advance.
The following worked for me:
IO.foreach('CustomerList.txt') do |customer|
customer.chomp!
["orders", "employees", "comments"].each do |dir|
FileUtils.mkdir_p("#{customer}/foo/bar/#{dir}")
end
end
with data like so:
$ cat CustomerList.txt
Cust001_JohnJacobSmith(JJS)
Cust003_JohnJacobSmith(JJS)
Cust002_JohnJacobSmith(JJS)
A few things to make it more like the ruby way:
Use blocks when opening a file or iterating through arrays, that way you don't need to worry about closing the file or accessing the array directly.
As noted by #inger, local vars start with lower case, customer.
When you want the value of a variable in a string usign #{} is more rubinic than concatenating with +.
Also note that we took off the trailing newline using chomp! (which changes the var in place, noted by the trailing ! on the method name)
I wrote some code to get input from a user and then alter it to my needs. I need it in the altered and unaltered form so I am saving the input into two variables. What I don't understand is why it both variables are changing. I tried some extra puts lines to determine what the cause is but I am unable to figure it out. The code:
puts "Enter the full directory path of the flv files."
folder = gets.chomp
puts "Folder 1: " + folder
path = folder
path.slice!(0..6)
path.gsub!('\\', '/')
path += '/'
puts "Folder: " + folder
puts "Path: " + path
With input: f:\folder\subfolder\another
Output:
Folder 1: f:\folder\subfolder\another
Folder: folder/subfolder/another
Path: folder/subfolder/another/
What I'm going for is getting a directory and keeping the directory for other processes, but also transforming it into a URL friendly format. Ideas?
path = folder # does not actually copy the object, copies the reference
path.object_id == folder.object_id # the objects are the same, see
path.slice!(0..6) # all bang methods work with the same object
Thus your path is a reference to the same object as folder.
To fix this, use
path = folder.clone
When you do b = a, it's making b point at the same value as a, so when you change a's value using something like slice!, b will also point to the changed value.
To avoid this, duplicate the object instead:
b = a.dup
I'm learning Ruby, but I'm having trouble with the whole implicit return value thing. Someone please tell me why this returns an empty vector:
3 def get_filenames(path)
4 filenames = []
5
6 if (path == ".") || (path == "..")
7 []
8 elsif File.directory? path
9 Dir.entries(path).each do |sub_path|
10 filenames += get_filenames(sub_path)
11 end
12 else #presumably it's a file
13 [File.basename(path,".*")]
14 end
15 end
It should be returning an array of all file names (sans their extension) found when recursively searching from the argument path.
Assume that I call the function with "/tmp" and tmp contains 2 files: "A.txt" and "B.m" and then a directory which contains 1 file "C.exe". I want this function to return ["A","B","C"]
first of all, Dir.entries does not get absolute paths, so when you try calling get_filenames(sub_path) you call for a relative filename path (and your function receives an absolute path)
use this:
def get_files(dir)
files = []
Find.find(dir) { |path| files << File.basename(path,".*") if FileTest.file?(path) }
return files
end
Here's a simple solution to your query...
Find every file present in the current directory and sub-directories
{Find.find("", "#{path}") do |file|
if File.file?(file)
filenames << file.to_s
end
end
}
Your if statement has three paths; the first returns an empty array, and the last returns a single element wrapped in an array. So far so good.
But the middle path returns the value of Dir.entries, which itself returns all the entries for the folder identified by path. The each iterator produces the side effect of recursively calling get_filenames and appending them to the local variable filenames, but the return value of Dir.entries is not affected by this -- it still returns all the entries in the folder.
To get the result you want, just add filenames after the Dir.entries call.
It is finding your if statement to be true, I believe, causing it to exit on the first pass through. What happens if you remove that portion of the logic (move elseif to if so you just have if and else?
This works on my machine as expected.
Try adding print statements in the argument and each one of the returned values to see what is actually happening in your case.
It is possible that your think you're passing one value when in fact you're passing another.