How to mass rename files in ruby - ruby

I have been trying to work out a file rename program based on ruby, as a programming exercise for myself (I am aware of rename under linux, but I want to learn Ruby, and rename is not available in Mac).
From the code below, the issue is that the .include? method always returns false even though I see the filename contains such search pattern. If I comment out the include? check, gsub() does not seem to generate a new file name at all (i.e. file name remains the same). So can someone please take a look at see what I did wrong? Thanks a bunch in advance!
Here is the expected behavior:
Assuming that in current folder there are three files: a1.jpg, a2.jpg, and a3.jpg
The Ruby script should be able to rename it to b1.jpg, b2.jpg, b3.jpg
#!/Users/Antony/.rvm/rubies/ruby-1.9.3-p194/bin/ruby
puts "Enter the file search query"
searchPattern = gets
puts "Enter the target to replace"
target = gets
puts "Enter the new target name"
newTarget = gets
Dir.glob("./*").sort.each do |entry|
origin = File.basename(entry, File.extname(entry))
if origin.include?(searchPattern)
newEntry = origin.gsub(target, newTarget)
File.rename( origin, newEntry )
puts "Rename from " + origin + " to " + newEntry
end
end

Slightly modified version:
puts "Enter the file search query"
searchPattern = gets.strip
puts "Enter the target to replace"
target = gets.strip
puts "Enter the new target name"
newTarget = gets.strip
Dir.glob(searchPattern).sort.each do |entry|
if File.basename(entry, File.extname(entry)).include?(target)
newEntry = entry.gsub(target, newTarget)
File.rename( entry, newEntry )
puts "Rename from " + entry + " to " + newEntry
end
end
Key differences:
Use .strip to remove the trailing newline that you get from gets. Otherwise, this newline character will mess up all of your match attempts.
Use the user-provided search pattern in the glob call instead of globbing for everything and then manually filtering it later.
Use entry (that is, the complete filename) in the calls to gsub and rename instead of origin. origin is really only useful for the .include? test. Since it's a fragment of a filename, it can't be used with rename. I removed the origin variable entirely to avoid the temptation to misuse it.
For your example folder structure, entering *.jpg, a, and b for the three input prompts (respectively) should rename the files as you are expecting.

I used the accepted answer to fix a bunch of copied files' names.
Dir.glob('./*').sort.each do |entry|
if File.basename(entry).include?(' copy')
newEntry = entry.gsub(' copy', '')
File.rename( entry, newEntry )
end
end

Your problem is that gets returns a newline at the end of the string. So, if you type "foo" then searchPattern becomes "foo\n". The simplest fix is:
searchPattern = gets.chomp
I might rewrite your code slightly:
$stdout.sync
print "Enter the file search query: "; search = gets.chomp
print "Enter the target to replace: "; target = gets.chomp
print " Enter the new target name: "; replace = gets.chomp
Dir['*'].each do |file|
# Skip directories
next unless File.file?(file)
old_name = File.basename(file,'.*')
if old_name.include?(search)
# Are you sure you want gsub here, and not sub?
# Don't use `old_name` here, it doesn't have the extension
new_name = File.basename(file).gsub(target,replace)
File.rename( file, new_path )
puts "Renamed #{file} to #{new_name}" if $DEBUG
end
end

Here's a short version I've used today (without pattern matching)
Save this as rename.rb file and run it inside the command prompt with ruby rename.rb
count = 1
newname = "car"
Dir["/path/to/folder/*"].each do |old|
File.rename(old, newname + count.to_s)
count += 1
end
I had /Copy of _MG_2435.JPG converted into car1, car2, ...

I made a small script to rename the entire DBZ serie by seasons and implement this:
count = 1
new_name = "Dragon Ball Z S05E"
format_file = ".mkv"
Dir.glob("dragon ball Z*").each do |old_name|
File.rename(old_name, new_name + count.to_s + format_file)
count += 1
end
The result would be:
Dragon Ball Z S05E1
Dragon Ball Z S05E2
Dragon Ball Z S05E3

In a folder, I wanted to remove the trailing underscore _ of any audio filename while keeping everything else. Sharing my code here as it might help someone.
What the program does:
Prompts the user for the:
Directory path: c:/your/path/here (make sure to use slashes /, not backslashes, \, and without the final one).
File extension: mp3 (without the dot .)
Trailing characters to remove: _
Looks for any file ending with c:/your/path/here/filename_.mp3 and renames it c:/your/path/here/filename.mp3 while keeping the file’s original extension.
puts 'Enter directory path'
path = gets.strip
directory_path = Dir.glob("#{path}/*")
# Get file extension
puts 'Enter file extension'
file_extension = gets.strip
# Get trailing characters to remove
puts 'Enter trailing characters to remove'
trailing_characters = gets.strip
suffix = "#{trailing_characters}.#{file_extension}"
# Rename file if condition is met
directory_path.each do |file_path|
next unless file_path.end_with?(suffix)
File.rename(file_path, "#{file_path.delete_suffix(suffix)}.#{file_extension}")
end

Related

Recursively rename folders based on contents in Ruby

I have a collection of folders (within a folder) that all need to be renamed based on their contents.
Specifically, I'd like to rename "/working_directory/my_folder/my_file.extension" to /working_directory/my_file/my_file.extension"
There are a few other files within /my_folder/. How might I recursively do this using ruby?
I'm new to ruby and programming, I have so tried to just extract the file names, but have not have much luck. The attempt at itterating through the folders. This will cycle through /working_directory/ every time Find.find is called. The intent is to search /working_directory/my_folder/ only for the file with the .fls extension.
require 'find'
Path = "/working_directory/"
Dir.foreach(Path) do |file|
puts file
new_dir = Path+file
puts new_dir
Find.find(new_dir) do |i| # this is intended to by /working_directory/my_folder/
fls_file << i if i =~ /.*\.fls$/
puts fls_file
end
end
Assuming, the my_file is to be chosen by extension, one might do:
Dir["/working_directory/**/*"].select do |dir_or_file|
File.directory? dir_or_file # select only directories, recursively
end.inject({}) do |memo, dir|
new_name = Dir["#{dir}/*.extension"].to_a
unless new_name.size == 1 # check if the folder contains only one proper file
puts "Multiple/No choices; can not rename dir [#{dir}] ⇒ skipping..."
next memo # skip if no condition met
end
my_file = new_name.first[/[^\/]+(?=\.extension\z)/] # get my_name
memo[dir] = dir.gsub /[^\/]+(?=\/#{myfile}\.extension\z)/, my_file
memo
end.each do |old, neu|
# dry run to make sure everything is OK
puts "Gonna rename #{old} to #{neu}"
# uncomment the lines below as you are certain the code works properly
# neu_folder = neu[/(.*?)([^\/]+\z)/, 1]
# FileUtils.mkdir neu_folder unless File.exist? neu_folder
# FileUtils.mv old, neu # rename
end
The rename is done after the main processing for the sake of previous iterator consistency, probably in this case it might be done in the previous loop, instead of injecting old: neu pairs into hash and iterating it later.
We are heavily using string parsing with regexps here.
my_file = new_name.first[/[^\/]+(?=\.extension\z)/] # get my_name
this line gets a new folder name by parsing a tail of the string, containing no slashes and trailing with '.extension\z' (see positive lookahead.)
memo[dir] = dir.gsub /[^\/]+(?=\/#{myfile}\.extension\z)/, my_file
This line assigns a new element on an accumulator hash, substituting the old folder name with the new one.

Parsing Text File in Ruby with "^z" Characters

I have a group of large text files with a ton of information in them with inconsistent formatting. I don't really care all that much about most of the info, but I'm trying to extract IDs that are included in the file. I've drafted a fairly simple script to do this (IDs are 3 digits - 7 digits).
puts("What's the name of the file you'd like to check? (don't include .txt)")
file_to_check = gets.chomp
file_to_write = file_to_check + "IDs" + ".txt"
file_to_check = file_to_check + ".txt"
output_text = ""
count_of_lines = 0
File.open(file_to_check, "r").each_line do |line|
count_of_lines += 1
if /.*\d{3}-\d{7}.*/ =~ line
temp_case = line.match(/\d{3}-\d{7}/).to_s
temp_case = temp_case + "\n"
output_text = output_text + temp_case
else
# puts("this failed")
end
end
File.open(file_to_write, "w") do |file|
file.puts(output_text)
file.puts(count_of_lines)
end
One of the files includes characters that VIM shows as ^Z, which seem to be killing the script before it actually gets to the end of the file.
Is there anything I can do to have Ruby ignore these characters and keep moving through the file?
Per Mircea's comment, the answer is here. I used "rt" based on one of the comments on the selected answer.

Ruby regex replace some subpattern captures

I have regex for path parsing. Below is the part of regex that repeats multiple times.
dir_pattern = /
\/?
(?<dir> #pattern to catch directory
[^[:cntrl:]\/\n\r]+ #directory name
)
(?=\/) #indistinguishable from file otherwise
/x
Input:
/really/long/absolute/path/to/file.extension
Desired output:
to/really/long/file.extension
I want to cut off some (not all directories) and reorder remaining ones. How could I achieve that?
Since I'm already using regexes for filtering files needed, I would like to keep using them.
Ok, here is a regex answer based on the new information posted above:
rx = /\/[^\/]+/i
# matches each character that is not a '/'
# this ensures any character like a '.' in a file name or the dot
# in the extension is kept.
path = '/really/long/absolute/path/to/file.extension'
d = path.scan(rx)
# returns an array of all matches ["/really", "/long", "/absolute", "/path", "/to", "/file.extension"]
new_path = [y[4], y[0], y[1], y[-1]].join
# returns "to/really/long/file.extension"
Lets wrap it in a method:
def short_path(path, keepers)
rx = /\/[^\/]+/i
d = path.scan(rx)
new_path = []
keepers.each do |dir|
new_path << d[dir]
end
new_path << d[-1]
new_path.join
end
Usage: just past the method the path and an array of the positions you want to keep in the new order.
path = '/really/long/absolute/path/to/file.extension'
new_path = short_path(path, [4,0,1])
# returns '/to/really/long/file.extension'
If you need to remove the first '/' for a relative path just:
new_path.sub!(/\//, '')
Old answer using string manipulation without regex...
x = "01234567 capture me!"
puts "#{x[7]}#{x[4]}#{x2}"
#=> "742"

Search for word in text file and display (Ruby)

I'm trying to take input from the user, search through a text file (case insensitively), and then display the match from the file if it matches (with the case of the word in the file). I don't know how to get the word from the file, here's my code:
found = 0
words = []
puts "Please enter a word to add to text file"
input = gets.chomp
#Open text file in read mode
File.open("filename", "r+") do |f|
f.each do |line|
if line.match(/\b#{input}\b/i)
puts "#{input} was found in the file." # <--- I want to show the matched word here
#Set to 1 indicating word was found
found = 1
end
end
end
So, what you want to do is to store the result of the match method, you can then get the actual matched word out of that, ie.
if m = line.match( /\b#{input}\b/i )
puts "#{m[0]} was found in the file."
# ... etc.
end
Update
Btw, you didn't ask - but I would use scan in this case, so that I got an array of the matched words on each line (for when there's more than one match on the same line), something like this:
if m = line.scan( /\b#{input}\b/i )
puts "Matches found on line #{f.lineno}: #{m.join(', ')}"
# ... etc.
end
If you don't need to report the locations of the matches and the file is not overly large, you could just do this:
File.read("testfile").scan /\b#{input}\b/i
Let's try it:
text = <<THE_END
Now is the time for all good people
to come to the aid of their local
grocers, because grocers are important
to our well-being. One more thing.
Grocers sell ice cream, and boy do I
love ice cream.
THE_END
input = "grocers"
F_NAME = "text"
File.write(F_NAME, text)
File.read(F_NAME).scan /\b#{input}\b/i
# => ["grocers", "grocers", "Grocers"]
File.read(F_NAME) returns the entire text file in a single string. scan /\b#{input}\b/i is sent to that string.

Renaming Script in Ruby

I'm trying to write my first Ruby script that will rename files in a specific folder. I am basing my script off of this response : How to rename a file in Ruby?. However, I need help elaborating on some things. Here is the code from the above link that I currently have written out.
puts "Renaming files..."
folder_path = "/Desktop/untitled/"
Dir.glob( folder_path + "*" ).sort.each do |f|
filename = File.basename(f, File.extname(f))
File.rename( f, folder_path + filename.capitalize + File.extname(f))
end
puts "Renaming complete."
With this example, I understand that the script is simply capitalizing the name of the original file. But what do I do if I want to insert a segment in the name of the file. Say for example I have:
"This is my name."
written out. What would I do if I just want to focus on the "my name" portion, and change it into something that would state:
"This is my (first) name."
Also, what if I wanted to remove a space:
"This is myfirstname."
Thanks so much!
If you want to achieve replacing portion of the filename with something else, you should use a sub or gsub function of the String class:
filename = File.basename(f, File.extname(f))
Now in filename you have stored a String representing a current file's name. You can check it using instance_of? function of an Object class, just if you're curious:
filename.instance_of?(String)
# -> true
What you should do is to use gsub method to replace all occurencies of given string, or sub to replace only first of it. Here you can find detailed information of using these functions.
I suppose in your case this should do the trick:
filename.gsub('my name', 'my (first) name')
# 2nd question:
filename.gsub("my first name", "myfirstname")
Also, regular expressions are allowed in sub and gsub methods. You should give it a try if you want to write more complex patterns, for example strip all numbers from file.
A nice way to create strings with variables in Ruby is:
first = "Eugene"
filename = "This is my #{first} name"
filename is equal to "This is my Eugene name"
so with the file portions you asked about:
"This is my #{folder_path}#{filename.gsub!(' ', '').capitalize}#{File.extname(f)}"
Removing spaces can be done with gsub
(check out string class documentation http://www.ruby-doc.org/core-1.9.3/String.html):
filename.gsub(' ', '')
You can also use the File classes join method to concatenate strings into a path and avoid cross platform issues with slashes ('/' vs '\')
For more see http://www.ruby-doc.org/core-1.9.3/File.html#method-c-join

Resources