I have this script for OSX for finding folders that only contain one file, and if that file is an audio file outputting the path of the audio file

find "$1" -type d -exec sh -c '[[ $(find "$0" -mindepth 1 | wc -l) -eq 1 ]] 
&& [[ $(find "$0" -mindepth 1 -type d | wc -l) -eq 0 ]]  
&& find "$0"' {} \; |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"

i.e use like

./findodd.sh /Users/paul/Music

but there are two improvements I need:

  1. What can I change so that it list files in folders containing 2 files, 3 files ectera, would be even better if this could be passed as a parameter

  2. Currently it find folders containing only one file , and that file must be an audio file, But what I really want it to do is find folder containing only one audio file, i.e if the folder contains three files but only one is an audio file I want that audio file to to be listed.

thanks Paul

  • I keep checking the date
    – Ram
    Mar 11 '14 at 0:14
  • To get the 2nd Part you asked - You just need to re arange the commands 1. do the Filtering on the Audio-Files and then do the Count. If you first filter the contents of the folder it will take a look and see there are 4 Files but only the one which actually is an Audio-File will be passed into the next Command and the others will be ignored -> Then it counts and PING it's one -> print out.
    – konqui
    Mar 11 '14 at 9:37
$ find

Example runs:

$ ./findaudio.sh /tmp/findaudio 1

$ ./findaudio.sh /tmp/findaudio 2

# The first parameter defaults to the current directory and
# the second parameter defaults to 1 so this works as well:
$ ./findaudio.sh

And here the code:


shopt -s nullglob

find "${1:-.}" -type d | while read dir; do
        files=( "${dir}"/*.{mp4,mp3,ogg,flac,wma,m4a} )
        (( ${#files[@]} == ${2:-1} )) && echo "${files[*]}"

It iterates over all subdirectories of the given directory and uses globbing to read all audio filenames of the current subdirectory into the array files. If the size of the array matches your desired value it just prints out the filenames separated by a newline.

EDIT: This is my earlier approach based on the assumption that you wanted to print the folders, not the filenames in question. I'll leave it here for future reference.

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -u

What this does is find all files with the listed audio extensions and only print their directory components instead of the full path. This gives you a list of parent folders for all audio files. The uniq skips over non-unique lines which should give you the result you are after i.e. only print folders which contain exactly one audio file.

This should in theory also be quite a bit faster than your earlier attempt.

You can improve upon this to satisfy your first point by counting the duplicate lines and printing only the folders that match your requested count. A naive solution would be:

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=1 '$1==count'
1 ./folder2

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=2 '$1==count'
2 ./folder1

though it might be better to fuse the uniq-part and right-hand-side of the pipe in a single awk line.

  • Thanks but your basic script is worse than the orginal because it outputs the folder (which i dont want), it outputs relative paths (which I dont want) and it doesnt work on OSX (because OSX doesnt support -printf) option. I dont have a problem with speed I just want minor adjustment to original script to fix point 1. and ideally point 2 Mar 5 '14 at 19:45
  • @PaulTaylor You are right, I misread your initial question and thought you wanted the folders to be printed, not the files. The reason my snippet gave relative paths is because find will print relative paths if given relative paths as input and absolute ones if given absolute paths (so it depends on the invokation). I updated my post and added a new solution based on your clarification which I think is what you had in mind. The code is as brief as your initial snippet but fulfills both your requirements if I understand them correctly now and should be more readable as well :-) Mar 11 '14 at 9:59


OK, after actually trying this myself on my own Music folder, this is the solution to both your requests:

COMMAND='[[ $(find "$0" -maxdepth 2 |egrep "\.mp4|\.mp3|\.ogg|\.flac|\.wma|\.m4a"| wc -l) == '$2' ]] && echo "$0"'
find $1 -type d -exec sh -c "$COMMAND" {} \;

So there were a few things wrong with your script:

  1. You were using mindepth instead of maxdepth.
  2. The periods (.) in your egrep would have matched any character. So .wma would have matched 'Snowman.txt'.
  3. You didn't need to do the second test for type 'd' as only directories are passed into the shell command.

Notes on my script:

  1. Usage is: findodd.sh <top_folder> <no_of_files>
  2. Quotes are critical. The definition of COMMAND is actually 2 string literals on either side of the $2. That's really important.
  3. It only lists the folders containing the files, not the files themselves. To do the latter you'd have to replace the echo "$0" with another find.

Now I've been testing on an Arch Linux machine, and my shell is 'bash', so I've no idea if this will work on OSX, as all shells are NOT created equal. :-)


Hmmmm. I don't know how similar OSX is to Unix/Linux, but I'll give this a stab.

The answer to both of your questions, I believe, lies in the first test of the 'sh -c' command. That's the bit that reads:

$(find "$0" -mindepth 1 | wc -l) -eq 1

To pass a second parameter to your script for the number of files, you should be able to just change the '1' to $2, so the test would be:

$(find "$0" -mindepth 1 | wc -l) -eq $2

Don't put quotes around the $2 because otherwise it will be interpreted as the second parameter passed to the 'sh -c' command, not your script.

The command line would then be:

./findodd.sh /Users/paul/Music 2

To achieve your second requirement, as I understand it, you need to put the egrep command into that first test, thus:

$(find "$0" -mindepth 1 |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"| wc -l) -eq $2

You might have to watch the quotes, though.

Anyway, give that a go and let us know.

  • Thanks unfortunately it doesn't work, maybe an OSX thing. Mar 15 '14 at 22:28

You could implement this in Python by doing something like this:

#!/usr/bin/env python

import fnmatch
import os
import sys

if len(sys.argv) != 3 or \
        not sys.argv[1].isdigit() or \
        not os.path.exists(sys.argv[2]):
    print "Usage: %s [number of files] [search root]" % sys.argv[0]

num_files = int(sys.argv[1])
search_root = sys.argv[2]

# this must be a tuple to work with endswith()
audio_extensions = (

for dirpath, dirnames, filenames in os.walk(search_root):
    audio_files = [f for f in filenames if f.endswith(audio_extensions)]
    if len(audio_files) == num_files:
        print "\n".join([os.path.join(dirpath, f) for f in audio_files])

If you chmod +x findodd.py you can then run it in the same way as you run your current script, e.g:

./findodd.py 1 /Users/paul/Music
  • Interesting but I dont want to introduce python into the mix Mar 15 '14 at 22:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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