Is there an OSX app/way to batch rename files via a purely text interface?
Many years ago - when I was a Windows user - I had an application which made renaming files an amazingly simple experience. You would select the files you wish to work with, and their filenames would appear within what was essentially a huge textarea. Imagine a text editor (such as TextEdit, Sublime Text, etc.) open to a document that simply lists all filenames, with each filename on a new line.
With the files in this interface, you had your "standard" find/replace tools (CMD+F), as well as the ability to use REGEX. You could use the keyboard's arrows to move around the filenames without extra clicks or bulky interfaces. Modified filenames were highlighted with a different background and applying changes to all files was a simple CMD+S.
Does something like this exist for OSX?
// edit - to clarify: I'm aware of OSX's built-in bulk file renaming options in Finder, and software along the likes of NameChanger, Renamer, Automator, ABetterFinderRename, etc. I'm specifically looking for software with the kind of UI I described.
// edit 2 - the original Windows software is called Oscar's Renamer: http://www.mediachance.com/free/renamer.htm
Solution 1:
Emacs can do this.
- Open Emacs
-
C-x C-f
and open the directory. It will open indired-mode
. M-x wdired-change-to-wdired-mode RET
You can now edit the filenames just as you asked. Pressing C-c C-c
will end wdired-mode and perform the modifications.
wdired-change-to-wdired-mode is an interactive autoloaded Lisp function in `wdired.el'.
It is bound to .
(wdired-change-to-wdired-mode)
Put a Dired buffer in Writable Dired (WDired) mode.
In WDired mode, you can edit the names of the files in the buffer, the target of the links, and the permission bits of the files. After typing C-c C-c, Emacs modifies the files and directories to reflect your edits.
See `wdired-mode'.
Solution 2:
If you're on OS X Yosemite (v10.10.x), Finder has a built-in way to batch rename files. Highlight all the files you want in Finder, hold down Control and click on one of them to bring up a right-click menu, and then click on "Rename x Files...". You'll get a nice find-and-replace interface, no third-party software necessary!
Solution 3:
Follow these steps:
Install DiffMerge and
gem install batch_rename
(see README.md for details).In Terminal.app,
cd
to the directory which contains the files you wish to rename.
(For convenience, I suggest to ⌘-drag the folder from the Finder into the Terminal window.)Type
batch-rename
and press Enter. This opens an interactive editor.
The script will wait for you to close the editor. Then it will rename the files.
(Disclosure: I’m the author of the batch-rename
tool.)
Edit: I liked OP’s problem so much that I’ve made my answer into a command-line tool for everyone’s convenience. To take advantage of the simplification, I have also rewritten my answer above.
I have uploaded the source to GitHub and published it as an installable gem on RubyGems.
Both are under a ISC license so feel free to send me issues and pull requests.
You can find my original answer below for reference.
1. Install DiffMerge.
2. cd
to the proper working directory.
3. Run the following Ruby script:
(batch_rename.rb
)
#!/usr/bin/env ruby
# encoding: utf-8
require 'shellwords'
require 'tmpdir'
Catalog = Struct.new(:time, :title, :file_name, :escaped_file_name) do
def lines
File.open(file_name).each.map(&:chop)
end
end
CAPTION = "Batch rename - #{ Dir.pwd }"
DIFF_TMP_DIR = Dir.mktmpdir('batch-rename-')
DIFFMERGE = '/usr/local/bin/diffmerge'
catalogs = [:before, :after].map do |t|
title = "#{ t.capitalize } renaming"
file_name = "#{ DIFF_TMP_DIR }/#{ title }.utf8"
escaped_file_name = Shellwords.escape(file_name)
Catalog.new(t, title, file_name, escaped_file_name)
end
`ls -A | tee #{ catalogs.map(&:escaped_file_name).join(' >') }`
args = catalogs.map.with_index do |c, i|
["-t#{ i+1 }=#{ c.title }", c.file_name]
end
system(DIFFMERGE, "-c=\"#{ CAPTION }\"", *(args.flatten))
puts catalogs
.map(&:lines)
.reduce(&:zip)
.select { |a| a.reduce(&:!=) }
.map { |pair| pair.map(&Shellwords.method(:escape)) }
.map { |a, b| "mv #{a} #{b}" }
.join("\n")