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?
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?
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 //
).
echo $f | sed 's/^.* - //'
" ; done will just output 6 errors. –
Rackley rename
or something similar that could do this much easier. –
Aram find
instead of ls
if you really want to do this recursively and only include files etc. –
Aram ls -1 */*
matches nothing at all in the example. It finds all files exactly two levels below the current directory, but all of the files are one level below. –
Rackley ls -1
returns. It's not "A/FooBar - Title" "B/FooBar - Title", it's "" "A:" "FooBar - Title" "" "B:" "FooBar - Title". You could ls */*Title
, which would work here. But then, once you match the right thing, your mv will move all of the files to Title (not A/Title, etc.), which I doubt you want. –
Rackley rename
have the same problem when using the s/^.* - //
regex? –
Aram 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'.-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.
find -exec
more often but i tend to use the while read
pattern quite often as the seems easier to do multiple commands etc. –
Aram find . -name "*.java" -exec rename 's|\.java|\.txt|' {} +
–
Vallation 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 //
).
echo $f | sed 's/^.* - //'
" ; done will just output 6 errors. –
Rackley rename
or something similar that could do this much easier. –
Aram find
instead of ls
if you really want to do this recursively and only include files etc. –
Aram ls -1 */*
matches nothing at all in the example. It finds all files exactly two levels below the current directory, but all of the files are one level below. –
Rackley ls -1
returns. It's not "A/FooBar - Title" "B/FooBar - Title", it's "" "A:" "FooBar - Title" "" "B:" "FooBar - Title". You could ls */*Title
, which would work here. But then, once you match the right thing, your mv will move all of the files to Title (not A/Title, etc.), which I doubt you want. –
Rackley rename
have the same problem when using the s/^.* - //
regex? –
Aram 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 :)
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.
© 2022 - 2024 — McMap. All rights reserved.