13

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!

1
  • 5
    Why the downvote? It seems like a valid question...
    – jshin47
    May 11, 2012 at 19:44

5 Answers 5

14

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' _ {} \+
15
  • neat approach yet as hairy as the sed expression :) +1
    – yrk
    May 11, 2012 at 21:18
  • 1
    something's missing here... where's done?
    – yrk
    May 11, 2012 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, 2012 at 23:23
  • You're right; this is much cleaner. And the $arg variable makes it much easier to read.
    – hobbes3
    May 12, 2012 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, 2012 at 21:46
6

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

5
  • 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, 2012 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.
    – yrk
    May 11, 2012 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, 2012 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
    – phemmer
    May 11, 2012 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. May 11, 2012 at 23:30
4

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\)/}'
3

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 '...'.

10
  • 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, 2012 at 19:23
  • this is supposed to be a homework :) but ok, i'll update the answer
    – yrk
    May 11, 2012 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, 2012 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?)
    – yrk
    May 11, 2012 at 20:46
  • 2
    I don't think we should be downvoting. It is a valid solution after all.
    – hobbes3
    May 12, 2012 at 2:17
1

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
1
  • this one will choke on files with spaces in their names; also it requires big enough temporary storage for the `find ...`
    – yrk
    May 22, 2012 at 16:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .