ubuntu - Find all files where other files with different extension dont already exist

07
2014-07
  • clamp

    I want to use the find command, to find all files with a certain extension (avi), where the same filename with another extension (mp4) does not already exist.

    so considering these files:

    a.avi
    a.mp4
    c.avi
    

    it should only result in the file c.avi being found.

  • Answers
  • techie007

    Would this do?

    find . -name '*.avi' -type f -execdir bash -c 'f=("${0%avi}"*);((${#f[@]}==1))' {} \; -print
    

    I'll try to explain how it works.

    Well, in fact it's really easy: find all files in current directory such that the name has avi extension. For each of these, say it's file X.avi execute in the directory there're in (execdir) the command:

    bash -c 'f=("${0%avi}"*);((${#f[@]}==1))' {}
    

    where {} is replaced by the file name, in our test case X.avi. So this is like

    bash -c 'f=("${0%avi}"*);((${#f[@]}==1))' X.avi
    

    At this point, let me stress that this is 100% safe regarding spaces and other funny symbols in file names! Now you see the snippet that bash will execute? I mean this snippet

    f=("${0%avi}"*);((${#f[@]}==1))
    

    It will be executed with the 0-th positional parameter set to our file name; in our test example it's X.avi.

    The part "${0%avi}" expands to the filename, with trailing avi removed (btw, this file name is guaranteed to have this extension at this point), and the part "${0%avi}"* will expand to all files in current directory (remember, the one containing the file) that have the .avi extension. In our test example this is just like:

    X.*
    

    We then build an array f out of these, and, finally, we exit this bash process with success if f contains only one element (so, very likely, f only contains X.avi in our test example), and failure otherwise. If this was a success, we print the file name.

  • Scott Leadley

    A solution with 2 exec()s:

    find . -name '*.avi' -print | \
      perl -n -e '$x = $_; $x =~ s/\.avi\s*$/.mp4/; (-e $x) || print'
    

    Steps:

    1. these are the droids you're looking for
    2. for every input line/file, test for a corresponding .mp4 file and echo the .avi filename if not found
      1. copy the input line to $x
      2. substitute .mp4 for .avi in $x (but only at end-of-line)
      3. if the corresponding .mp4 file exists we're done with this line, otherwise print the input line
  • rici

    Another option: find all .mp4 and .avi files, then eliminate from the list any files for which both extensions exist, and then eliminate the remaining .mp4 files:

    find . -name '*.avi' -or -name '*.mp4' |
    rev | sort -t. -k2 -k1,1 | uniq -s3 -u | grep -v '^4pm' | rev | sort
    

    (Assumes that no file has a newline in its name.)

  • Scott Leadley

    A solution with 3 exec()s:

    find . -name '*.avi' -print | \
      sed 's/\(.*\)\.avi$/if [ ! -f "\1.mp4" ]; then echo "\1.avi"; fi/' | \
      sh
    

    Steps:

    1. these are the droids you're looking for
    2. strip off the .avi file extension; write a stream of sh commands that tests for a corresponding .mp4 file and echos the .avi filename if not found
    3. evaluate the sh commands

  • Related Question

    bash - Apply a command to all files in a directory, one directory at a time
  • NickG

    I want to apply replaygain information to all the MP3 files in my music collection. To do this, I'm using a tool called mp3gain (on Linux).

    In order to apply the album gain correctly, I need to run the mp3gain command on a per directory basis. In other words, I need to find a directory, run mp3gain on all files in that directory, then repeat for every other directory. So far, I've come up with this:

     find . -type d -exec mp3gain {}/*.mp3 \;
    

    The output looks like this (only showing a couple of the many directories):

    [...]
    ./dev2/Physicist/*.mp3
    Can't open ./dev2/Physicist/*.mp3 for reading
    ./Real Things/*.mp3
    Can't open ./Real Things/*.mp3 for reading

    It appears to me that the '*' is being escaped, so rather than looking for all files ending in '.mp3', it is looking for a file called '*.mp3'.

    What command should I use?


  • Related Answers
  • John T

    Looks like it's not globbing properly. How about something like this:

    #!/bin/bash
    OLDIFS=$IFS
    IFS=$(echo -en "\n\b")
    for dir in $(find . -type d)
    do
            mp3gain $dir/*
    done
    IFS=$OLDIFS
    

    as a single command:

    OLDIFS=$IFS;IFS=$(echo -en "\n\b");for dir in $(find . -type d);do mp3gain $dir/*;done;IFS=$OLDIFS
    
  • user19647

    It might not look nice, but this command will find all directories that contain mp3 files write them to a tempfile and then go through that tempfile listing the contents of each dir. If you're happy with the Output, you can go ahead and plug your mp3gain command into it in place of the ls.

    Setting the IFS variable to a newline character is important so that you can work with files and directories that contain spaces.

    The reason I've chosen to list directories containing mp3s first is in case mp3gain throws an error when encountering empty sets of files. In this way it never encounters such a situation.

    IFS=$'\n'; for i in `find -type f -iname *.mp3`; do dirname $i ; done | sort | uniq > ~/mp3directories.txt && for i in `cat ~/mp3directories.txt`; do ls -1 $i/*.mp3 ; echo ; done
    
  • Rebol Tutorial

    You can use Rebol it works on Unix and Windows

    How to apply a function to all files in a directory recursively

    http://reboltutorial.com/blog/how-to-apply-a-function-to-all-files-in-a-directory-recursively/