Recursive batch rename

2019-07-30 20:02发布

问题:

I have several folders with some files that I would like to rename from

Foo'Bar - Title

to

Title

I'm using OS X 10.7. I've looked at other solutions, but none that address recursion very well.

Any suggestions?

回答1:

Try this:

ls -1 * | while read f ; do mv "$f" "`echo $f | sed 's/^.* - //'`" ; done

I recommend you to add a echo before mv before running it to make sure the commands look ok. And as abarnert noted in the comments this command will only work for one directory at a time.

Detailed explanation of the various commands:

ls -1 * will output a line for each file (and directory) in the current directory (except .-files). So this will be expanded in to ls -1 file1 file2 ..., -1 to ls tells it to list the filename only and one file per line.

The output is then piped into while read f ; ... ; done which will loop while read f returns zero, which it does until it reaches end of file. read f reads one line at a time from standard input (which in this case is the output from ls -1 ...) and store it in the the variable specified, in this case f.

In the while loop we run a mv command with two arguments, first "$f" as the source file (note the quotes to handle filenames with spaces etc) and second the destination filename which uses sed and ` (backticks) to do what is called command substitution that will call the command inside the backticks and be replaced it with the output from standard output.

echo $f | sed 's/^.* - //' pipes the current file $f into sed that will match a regular expression and do substitution (the s in s/) and output the result on standard output. The regular expression is ^.* - which will match from the start of the string ^ (called anchoring) and then any characters .* followed by - and replace it with the empty string (the string between //).



回答2:

There are two parts to your problem: Finding files to operate on recursively, and renaming them.

For the first, if everything is exactly one level below the current directory, you can just list the contents of every directory in the current directory (as in Mattias Wadman's answer above), but more generally (and possibly more easy to understand, to boot), you can just use the find command.

For the second, you can use sed and work out how to get the quoting and piping right (which you should definitely eventually learn), but it's much simpler to use the rename command. Unfortunately, this one isn't built in on Mac, but you can install it with, e.g., Homebrew, or just download the perl script and sudo install -m755 rename /usr/local/bin/rename.

So, you can do this:

find . -exec rename 's|[^/]* - ||' {} +

If you want to do a "dry run" to make sure it's right, add the "-n" flag to rename:

find . -exec rename -n 's|[^/]* - ||' {} +

To understand how it works, you really should read the tutorial for find, and the manpage for rename, but breaking it down:

  • find . means 'find all files recursively under the current directory'.
  • You can add additional tests to filter things (e.g., -type f if you want to skip everything but regular files, or `-name '*Title' if you want to only change files that end in 'Title'), but that isn't necessary for your use.
  • -exec+ means to batch up the found files, and pass as many of them as possible in place of any {} in the command that appears in the '…'.
  • rename 's|[^/]* - ||' {} means for each file in {}, apply the perl expression s|[^/]* - || to the filename, and, if the result is different, rename it to that result.
  • s|[^/]* - || means to match the regular expression '[^/]* -' and replace the match with '' (the empty string).
  • [^/]* - means to match any string of non-slash characters that ends with ' - '. So, in './A/FooBar - Title', it'll match the 'FooBar -'.

I should mention that, when I have something complicated to do like this, if after a few minutes and a couple attempts to get it right with find/sed/awk/rename/etc., I still haven't got it, I often just code it up imperatively with Python and os.walk. If you know Python, that might be easier for you to understand (although more verbose and less simple), and easier for you to modify to other use cases, so if you're interested, ask for that.



回答3:

I know you asked for batch rename, but I suggest you to use Automator. It works perfectly, and if you create it as a service you will have the option in your contextual menu :)



回答4:

After some trial and error, I came across this solution that worked for me to solve the same problem.

find <dir> -name *.<oldExt> -exec rename -S .<oldExt> .<newExt> {} \;

Basically, I leverage the find and rename utilities. The trick here is figuring out where to place the '{}' (which represents the files that need to be processed by rename) of rename.

P.S. rename is not a built-in linux utility. I work with OS X and used homebrew to install rename.