Finding directories NOT CONTAINING a file

John Bennett

New Member
Credits
31
Have my music on a drive, saved in directories as AAA, BBB, CCC, etc with music saved by artist, and each directory should have an m3u (playlist) file. Unfortunately, a number don't, and am looking for an easy way to find which directories don't.
Have been trying to get the command "find" to work for me, using:
Code:
find . -type d '!' -exec test -e "{}/*.m3u" \; -print
or similar,(stolen from somewhere...), but can't get it to work.
Can anyone straighten me out on this, and/or suggest a simple bash script to do the same..?
Thanks.
 


LorenDB

Active Member
Credits
1,016
Try running
Code:
ls *.m3u
in each directory, assigning it to a variable, and comparing that to the string
Code:
ls: cannot access '*.m3u': No such file or directory
which is the output of ls in a folder with no .m3u files. If the two are equal, there are no .m3u files in the folder.
 

John Bennett

New Member
Credits
31
Try running
Code:
ls *.m3u
in each directory, assigning it to a variable, and comparing that to the string
Code:
ls: cannot access '*.m3u': No such file or directory
which is the output of ls in a folder with no .m3u files. If the two are equal, there are no .m3u files in the folder.
You ARE joking??
I am talking THOUSANDS of directories....

Thanks.
 

JasKinasis

Well-Known Member
Credits
2,399
I couldn't quite figure out how to do it using find on it's own, because of some of the limitations of using -exec.
There probably is a workaround that would allow you to do something using one or more -exec statements.

But, as always with bash - there's always more than one way to skin the proverbial cat. This should work:
Bash:
#!/usr/bin/env bash
searchpath="/path/to/music"
outputfile="/path/to/output.txt"

{ find $searchpath -type d -print | \grep -v ^"$searchpath"$ | while read dir; do
  [[ -z $(\ls "$dir" | \grep m3u$ ) ]] && echo "$dir";
done } > "$outputfile"
The above code does the following:
- Sets up some variables for the search-path and the path to the output file. You should set these variable to whatever you want to use.

- Uses find to iterate through the search-path and list all directories.

- Pipes the directory names through \grep, to filter out the searchpath directory itself - I'm assuming you only want the subdirectories, not the base-directory. The remaining results are piped to a while read loop.

For each directory the "while read" loop receives:
- we list its contents and \grep for any results ending in m3u.
If NO .m3u files are present, the directory path is echoed.

The curly braces { } around our piped find |grep|while read... commands group all of the commands together and the entire output is redirected to a file using a single redirect/write operation.

Another alternative would be to redirect the output of the echo command to the file using the append redirection operator >>. But this would mean that if the script was ran more than once, your file would keep getting appended to, if it already existed. If we did this - we would need to check for the existence of the output file at the start of the script and either delete it, or blank it.
So by grouping all of the commands together using the curly braces - And by redirecting ALL of the output in a single operation - we can completely re-write the output file, each time the script is ran.
It means less redirects/file-write operations in the script, it also saves us from having to delete/blank the output file at the start of the script, if it already exists.

Additional Notes:
In the above example, instead of using the more traditional "if.. then.. fi" type syntax. I've used the boolean/logical AND operator (&&), which will only trigger the right-hand operation if the left-hand operation succeeds. The overall effect is the same as an if.. then..fi. But it saves us typing a few extra words.

It helps if you read everything enclosed between [[ and ]] as the IF condition and everything after the && as the THEN action.

For more complex conditional clauses, I'll normally use the "if...then..else..fi" type syntax, or a switch statement. But for simple "if..then" conditional clauses - I often find that using the logical AND operator is more succinct/efficient.

My use of \grep is also deliberate.
It's very common for linux users to have grep aliased to something in their .bashrc - passing additional parameters to format the output in some way.

For example, in my .bashrc - I have grep aliased to:
alias grep='grep --color=auto -n'
Which colourises the output of grep AND includes the line-numbers for any results. So any time I type grep {pattern} {search-path}, the alias for grep will expand what I typed to:
grep --color=auto -n {pattern} {search-path}

But in this context - we don't want the colourisation, or line numbers included in the output. We just want the names of the directories. So we need to invoke grep without invoking the alias.

The way to achieve this is to use \grep - The backslash allows us to "escape" any aliases that the user has set for grep. This way we can be sure that the only parameters that get sent to the grep command are the ones that we specified in our script.
So we'll know exactly what to expect in the output from \grep.

And this works for any other command. Pre-pending any command with a backslash will escape any aliases that might be set for that command.

Bearing this in mind - I've also escaped \ls in the script - Just in case the user has ls aliased to use something like the -F option - which appends an extra character to the end of certain file-system objects names to indicate their type - e.g. / for a directory, * for an executable file, @ for a symbolic link, | for a pipe etc. etc.
Because - again, we're only interested in the directory names.
We already know they're directories, because find already did that for us. We don't need ls messing with the format of the file-names - we just want the file-names.

After setting the variables to whatever you want to use and running the above script, you should be left with a list of sub-directories found in $searchpath which do not contain a .m3u playlist.

I hope all of this helps!
 


Members online


Latest posts

Top