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'm relatively new to Bash and am trying to do something that on the surface seemed pretty straightforward - run find over a directory hierarchy to get all of the *.wma files, pipe that output to a command where I convert them to mp3 and save the converted file as .mp3. My thinking was that the command should look like the following (I've left off the audio conversion command and am instead using echo for illustration):

$ find ./ -name '*.wma' -type f -print0 | xargs -0 -I f echo ${f%.*}.mp3

As I understand it, the -print0 arg will let me handle filenames that have spaces (which many of these do as they are music files). I'm then expecting (as a result of xargs) that each file path from find is captured in f, and that using the substring match/delete from the end of the string, that I should be echoing the original file path with a mp3 extension instead of wma. However, instead of this result, I'm seeing the following:


So my question (aside from the specific 'what am I doing wrong here'), is this - do values that are the result of a pipe operation need to be treated differently in string manipulation operations than those that are the result of a variable assignment?

share|improve this question
There's no reason to use xargs with find. It comes with an -exec option. Can you just add the command you are going to use to your question, and someone can show you the correct find command? –  ixtmixilix Jan 6 '13 at 1:08
yes (as I noted below), so long as I can still do the string manipulation on each result of the find command (e.g. the {} member) –  Howard Dierking Jan 6 '13 at 6:58
actually there are edge cases where xargs is more suitable than exec. See this stackpost stackoverflow.com/questions/896808/find-exec-cmd-vs-xargs for a case in point. –  d34dh0r53 Jan 6 '13 at 7:27
@d34dh0r53 What edge case? The thread you link to doesn't point any. –  Gilles Jan 6 '13 at 21:29

3 Answers 3

up vote 1 down vote accepted

As other answers have already identified, ${f%.*} is expanded by the shell before it runs the xargs command. You need this expansion to happen once for each file name, with the shell variable f set to the file name (passing -I f doesn't do that: xargs has no notion of shell variable, it looks for the string f in the command, so if you'd used e.g. xargs -I e echo … it would have executed commands like ./somedir/somefile.wmacho .mp3).

Keeping on this approach, tell xargs to invoke a shell that can perform the expansion. Better, tell findxargs is a largely obsolete tool and is hard to use correctly, as modern versions of find have a construct that does the same job (and more) with fewer plumbing difficulties. Instead of find … -print0 | xargs -0 command …, run find … -exec command … {} +.

find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +

The argument _ is $0 in the shell; the file names are passed as the positional arguments which for f; do … loops over. A simpler version of this command executes a separate shell for each file, which is equivalent but slightly slower:

find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;

You don't actually need to use find here, assuming you're running a reasonably recent shell (ksh93, bash ≥4.0, or zsh). In bash, put shopt -s globstar in your .bashrc to activate the **/ glob pattern to recurse in subdirectories (in ksh, that's set -o globstar). Then you can run

for f in **/*.wma; do
  echo "${f%.*}.mp3"

(If you have directories called *.wma, add [ -f "$f" ] || continue at the beginning of the loop.)

share|improve this answer
great explanations as well as pointed me in a direction that I hadn't even realized (upgrading my bash shell to get globstar functionality) - in the end, recursive globbing was the solution that kept the command simple and accomplished everything I was trying to do. Thanks! –  Howard Dierking Jan 8 '13 at 4:48

In case of your solution evaluation of ${f%.*}.mp3 is done in shell that you are invoking the whole command in, not in the shell forked by the xargs. And in your shell there is no f variable, it is substituted by an empty string.

Solution using xargs with -I f:

% cat 1.sh 
echo ${f%.*}.mp3
% /bin/ls -1
% find ./ -name '*.wma' -type f -print0 | xargs -0 -I f ./1.sh f

But I would do it this way:

% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'
share|improve this answer

The find command does not care about spaces or special characters. But sometimes the programs to which we pipe may care. From the directory containing the WMA music files...

find ./ -name '*.wma' -type f -readable -writable -exec ls {} \;

where ls can be substituted with the command to convert the WMA file to an MP3 file.

Explanation? The ‑exec switch executes a command as an argument. The argument contains {}, which is replaced in the command with the name of the currently found file. The command is terminated by a semicolon, but the semicolon must be escaped first so that the shell will not try to use it.

The WMA files should be regular files. So we might be able to abbreviate while at the same time choosing a case-insensitive search with -iname:

find . -iname '*.wma' -exec ls {} \;

This will take a bit more time, but music files often have varied letter case. If the command produces *.wma.mp3 files, say with this command:

find . -iname '*.wmv' -exec ffmpeg -i {} -acodec mp3 -ac 2 -ab 128 '{}.mp3' \; 

then we might remove the ".wma" portion after the conversions have finished.

find . -iname '*.wma.mp3' | while read filename; do mv "${filename}" "`echo "${filename}" | sed -e 's/\.wma.mp3$/\.mp3/'`"; done
share|improve this answer
this looks really close to my desired output and your explanation makes sense, but the one thing I'm noticing is that I can't do the substring manipulation on {} - is that possible? –  Howard Dierking Jan 6 '13 at 4:19
I think with this solution you will get files named <filename>.wma.mp3 unless the converter has an option to strip the existing extension. –  d34dh0r53 Jan 6 '13 at 7:30
@d34dh0r53 We cannot guess that conclusion, not knowing which command is being used. –  Christopher Jan 6 '13 at 13:56
@d34dh0r53 is right - the command I'm using (ffmpeg) has a arg for the new file name - that's why I was trying to do string manipulation of the original file name - so that I can supply the new name. –  Howard Dierking Jan 6 '13 at 16:23
@HowardDierking Oh, yup! Well, I added a post-conversion command to rename *.wma.mp3 to *.mp3. –  Christopher Jan 6 '13 at 18:12

Your Answer


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.