Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I recently converted all of my FLAC files to a lower sampling rate of 44.1 kHz and bit depth of 24 bits (because iPhone/iPod don't support anything above that) using XLD on my Mac OS 10.7 (Lion).

Although I told XLD to overwrote all previous files, XLD appended a (1) at the end of very file like from

some_song.m4a

to

some_song(1).m4a

So now I want to remove that (1) from all the FLAC files I converted.

I know I could have probably used some program or even an AppleScript to rename the files, but I wanted to learn using the old school way of command line.

I know that find . -name *\(1\).m4a will grab all the converted FLAC file.

Next I know I have to do something with -exec and mv to rename all the found files. But what I can't figure out is how to keep the original filename and only remove the (1).

Maybe I need to do some group regex capturing to store the part of the filename that I don't want to modify? Or maybe it's not possible to do everything in one line and I should create a shell script (which I'm not that comfortable doing, but I'm willing to give it a try).

Any tips or suggestions are welcomed! Thanks!

share|improve this question

migrated from serverfault.com May 11 '12 at 20:26

This question came from our site for system and network administrators.

5  
Why the downvote? It seems like a valid question... –  jshin47 May 11 '12 at 19:44
    
unrelated to your particular question (on find) but that may be solving your actual problem (converting audio files), you may be interested to have a look at audiotools.sourceforge.net and to this example case (for macosx lion) invibe.net/LaurentPerrinet/SciBlog/2012-04-22 –  meduz May 18 '12 at 12:17

5 Answers 5

up vote 13 down vote accepted

Don't try to parse find output except as a last resort. It is important to realize that on Unix file systems, file names are not strings (a common misconception) but rather binary blobs which can contain any character except / and the null character. Parsing file names safely and correctly is enough of a pain that 99% of the time you'll just want to avoid doing it altogether (just look at how hairy the sed expression in @yarek's answer is and even that doesn't cover all cases). Thankfully, in this case there is a much simpler approach:

find . -name '*(1).m4a' -execdir sh -c \
'for arg; do mv "$arg" "${arg%(1).m4a}".m4a; done' _ {} \+
share|improve this answer
    
neat approach yet as hairy as the sed expression :) +1 –  yarek May 11 '12 at 21:18
1  
something's missing here... where's done? –  yarek May 11 '12 at 21:27
    
@yarek Thanks for the catch. The biggest difference between this and the sed pipe approach is this is much safer. It's still easier to read than regex backslash soup, provided you understand some basic shell scripting constructs. –  jw013 May 11 '12 at 23:23
    
You're right; this is much cleaner. And the $arg variable makes it much easier to read. –  hobbes3 May 12 '12 at 2:17
1  
@DQdlM The snippet I posted above is just find invoking sh with fairly portable POSIX syntax. For more details, you can check out the section on Parameter Expansion, the section on compound commands that explains the for loop, and the POSIX find spec. In addition to those resources, I'd be happy to answer any specific questions you have. –  jw013 May 12 '12 at 21:46

On Debian and Ubuntu, I can use rename 's/\(1\)//' *.m4a to solve your problem.

share|improve this answer
    
Weird, I can do man rename, but I actually don't have rename: -bash: rename: command not found (which rename doesn't show anything). –  hobbes3 May 11 '12 at 19:10
    
@hobbes3 on the mac here man -a rename finds rename (2) - the syscall, and rename (n) - the TCL command. There are neither rename (1) nor the utility itself there. –  yarek May 11 '12 at 19:18
1  
IIRC, rename is a perl script included with the examples included in some perl installs, and a couple distros include it in the path because it's a convenient command. (@Hobbes perhaps you can find it on the internet) –  Kevin May 11 '12 at 22:47
    
@Kevin on my system rename is normal binary (not perl). However another caveat is that not all versions of rename support regexes. The version I have for example only lets you do direct string replacements, eg rename '(1)' '' *.m4a –  Patrick May 11 '12 at 23:13
1  
The Perl rename script is only installed by Debian and derivatives. Other Linux systems have a different rename utility (shown by Patrick). OSX has neither. –  Gilles May 11 '12 at 23:30

In zsh, using zmv:

autoload zmv      # you can put this line in your .zshrc
zmv '(*)\(1\)(*)' '$1$2'

In the second argument (the new name), $1 and $2 refer to the parenthesized groups (PATTERN) in the source pattern. Another way of writing this renaming is

zmv '(*)' '${1/\(1\)/}'
share|improve this answer

The following approach gives you ability to preview/prune the generated commands before executing them, and it is very portable: it should work not only on a mac, not only with bash, and not only with GNU sed; even on systems without find(1) command it is possible to substitute it with du(1) without a trouble.

find . -name '*(1).m4a' |
sed 's/\(.*\)(1).m4a$/mv & \1.m4a/' 

If happy with the printed commands, re-run with | sh -x appended.

If concerned about spaces in file names, add another s to escape all spaces:

find . -name '*(1).m4a' |
sed -e 's/ /\\ /g' -e 's/\(.*\)(1).m4a$/mv & \1.m4a/'

If other special chars are expected, it gets a litle bit more tricky:

find . -name '*(1).m4a' |
sed -e "s/'/'\\\\''/g" -e 's/\(.*\)(1).m4a$/mv '\''&'\'' '\''\1.m4a'\'/

First function converts all ' into a form such that these are taken literally when in the middle of '...'-escaped string. Second function generates mv commands whose arguments are enclosed within '...'.

share|improve this answer
1  
That command isn't escaping the filename (the spaces, (, and all other special characters) or adding quotes around the filename. I tried modifying your command, but I couldn't figure out how to add quotes around both filenames for the mv command. One of the resulting output from your command is mv ./The Beatles - Let It Be (MFSL LP 1-109) 24-96 Vinyl Rip/01 Two Of Us(1).m4a ./The Beatles - Let It Be (MFSL LP 1-109) 24-96 Vinyl Rip/01 Two Of Us.m4a. And if I run that command I get -bash: syntax error near unexpected token '('. –  hobbes3 May 11 '12 at 19:23
    
this is supposed to be a homework :) but ok, i'll update the answer –  yarek May 11 '12 at 19:26
    
By the way GNU sed can execute command itself by adding e command after substitution. For example find . -name '*(1).m4a' | sed 's/\(.*\)(1).m4a$/mv & \1.m4a/e' will exexcute command without piping to sh -x. –  rush May 11 '12 at 20:39
    
@Rush that's the only sed which does that; and it needs to start the shell anyway, but a new one for each command (reminds the make's problem, doesn't it?) –  yarek May 11 '12 at 20:46
2  
I don't think we should be downvoting. It is a valid solution after all. –  hobbes3 May 12 '12 at 2:17

Here's a small script that does it:

for var in `find . -type f -name "*(1).m4a"`; do
    new=`echo $var | cut -d'(' -f1`;
    mv $var $new.m4a;
done
share|improve this answer
    
this one will choke on files with spaces in their names; also it requires big enough temporary storage for the `find ...` –  yarek May 22 '12 at 16:31

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.