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!

asked May 11, 2012 at 18:44
1
  • 5
    Why the downvote? It seems like a valid question... Commented 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' _ {} \+
answered May 11, 2012 at 21:05
15
  • neat approach yet as hairy as the sed expression :) +1 Commented May 11, 2012 at 21:18
  • 1
    something's missing here... where's done? Commented 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. Commented May 11, 2012 at 23:23
  • You're right; this is much cleaner. And the $arg variable makes it much easier to read. Commented 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. Commented May 12, 2012 at 21:46
6

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

Gilles 'SO- stop being evil'
865k204 gold badges1.8k silver badges2.3k bronze badges
answered May 11, 2012 at 18:54
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). Commented 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. Commented 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) Commented 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 Commented 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. Commented 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\)/}'
answered May 12, 2012 at 2:55
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 '...'.

answered May 11, 2012 at 18:54
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 '('. Commented May 11, 2012 at 19:23
  • this is supposed to be a homework :) but ok, i'll update the answer Commented 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. Commented 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?) Commented May 11, 2012 at 20:46
  • 2
    I don't think we should be downvoting. It is a valid solution after all. Commented 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
Michael Mrozek
95.6k40 gold badges245 silver badges236 bronze badges
answered May 12, 2012 at 15:32
1
  • this one will choke on files with spaces in their names; also it requires big enough temporary storage for the `find ...` Commented May 22, 2012 at 16:31

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.