Converting or renaming files, whilst still maintaining the directory structure

For various reasons we might need to convert files from one format to another, for instance from lossless FLAC to MP3. For example:

ffmpeg -i lossless-audio.flac -acodec libmp3lame -ab 128k compressed-audio.mp3

This could be any conversion, but it implies that the input file and the output file are in the same directory. What if we have a carefully curated directory structure and we want to convert (or rename) every file within that structure?

find . -name “*.whateveryouneed” -exec somecommand {} \; is the tool for you.

Find does what it says on the tin, it finds files. For example:

find . -name "*.flac"

will return a list of all your .flac files from within the current directory and its subdirectories. Big deal, ls -lR can do that. However, what if you wanted to do something TO each of those files? That’s where -exec and the magic { } comes in. { } contains the current file and its path, whilst -exec specifies an executable.

find -name "*.flac" -exec ffmpeg -i {} -acodec libmp3lame -ab 128k {}.mp3 \;

This will find every file which matches the pattern *.flac in the current directory and all its subdirectories. One by one it will put this into the magic { }, and pass this filename to ffmpeg, creating a new mp3 based on the input file.

Now there is a problem with this, if we had a file called:
Analord 01 – 01 – SteppingFilter 101.flac

The new mp3 file is named:

Analord 01 - 01 - SteppingFilter 101.flac.mp3

We probably just want the output file to be called .mp3 rather than .flac.mp3. But as we passed in { }, that was the whole original filename and .mp3 just got appended to the end. What we need to do now is do a batch rename of all our freshly created files and again, we can do this with the magic of find (and the rename command).

find -name "*.flac.mp3" -exec rename "s/flac\.//" {} \;

Find once again finds all the files called *.flac.mp3 (including their path) and passes that to the rename command. Rename uses the regular expression s/thing-to-find/thing-to-replace/ In this case, it replaces anything that is “flac.” (note it is \. in the expression, as the dot is escaped) it then replaces the section of the filename that contains “flac.” with nothing at all i.e. there’s nowt between the final two //.

So finally we have our original directory structure complete with the original files and the converted files. If we want rid of the originals and just to keep our converted ones, once again, find -exec is the way to go.

find . -name "*flac*" -exec rm {} \;

You’ll note in all of the above commands there’s a \; at the end. This is to tell find where to find the end of the command to be executed. It’s escaped (and needs the space before it) as otherwise the shell will eat it, thinking it’s a command separator.

In short, if you want to do something to every file in an arbitrary directory structure, give find -exec thing-you-want-executed {} \; a go!

Author