5

I have a directory with many pairs of files. Each non-text file has a textual description partner. For example a directory can look like this:

a.jpg
a.jpg.text
b.ogv
b.ogv.text
cd ef.JpG
cd ef.JpG.text

I want to confirm that there are no loose text files (description files of something already deleted). So I've attempted to execute the following:

find . -name '*.text' -exec if [ ! -f `basename -s .text {}` ]; then echo {}; fi \;

However, now I get an error:

bash: syntax error near unexpected token `then'
Radu Rădeanu
  • 169,590
v010dya
  • 1,472

2 Answers2

7

You just can't use if inside of an -exec action of find command in the way you may wish. This because of the following reasons:

  • The -exec action expects a command. This is from man find (somewhere at the line 697 in my terminal):
    -exec command ;
          Execute  command;  true  if 0 status is returned.  All following
          arguments to find are taken to be arguments to the command until
          an  argument  consisting of `;' is encountered.  The string `{}'
          is replaced by the current file name being processed  everywhere
          it occurs in the arguments to the command, not just in arguments
          where it is alone, as in some versions of find.  Both  of  these
          constructions might need to be escaped (with a `\') or quoted to
          protect them from expansion by the shell.  See the EXAMPLES sec‐
          tion for examples of the use of the -exec option.  The specified
          command is run once for each matched file.  The command is  exe‐
          cuted  in  the starting directory.   There are unavoidable secu‐
          rity problems surrounding use of the -exec  action;  you  should
          use the -execdir option instead.

    -exec command {} +
          This  variant  of the -exec action runs the specified command on
          the selected files, but the command line is built  by  appending
          each  selected file name at the end; the total number of invoca‐
          tions of the command will  be  much  less  than  the  number  of
          matched  files.   The command line is built in much the same way
          that xargs builds its command lines.  Only one instance of  `{}'
          is  allowed  within the command.  The command is executed in the
          starting directory.
  • if is not a command as you mat think, but a shell keyword. See the output of type if command.

Now, to accomplish what you wish, you don't really need to use if inside of -exec. Just make the test inside of -exec, then use -print (see man find for more info):

find . -name '*.text' -exec $SHELL -c '[ ! -f ${1%.*} ]' $SHELL '{}' ';' -print

Another way would be to use a bash script, as follow:

find . -name '*.text' -exec my_if {} \;

where cat ~/bin/my_if gives the following output in my case:

#!/bin/bash
if [ ! -f $(basename -s .text "$1") ]; then echo "$1"; fi

Finally, I think that all the normal people using bash would use:

for f in *.text; do if [ ! -f $(basename -s .text "$f") ]; then echo "$f"; fi; done
Radu Rădeanu
  • 169,590
  • 1
    your for loop doesn't go into subdirectories! but i guess i can use fild instead of the shell expansion. – v010dya Jan 15 '15 at 07:14
  • @Volodya You said nothing about subdirectories. You said that you hava a directory with many pairs of files. Also you give an example with the content of a directory; I don't see subdirectories there :) – Radu Rădeanu Jan 15 '15 at 07:21
3

Here is a non-optimized way to use an if in an exec clause:

find . -name '*.text' -exec sh -c \
    'if [ ! -f "$(dirname "$1")/$(basename "$1" .text)" ]; then echo == $1; fi' sh {} \;

and here is something much better that doesn't launch one shell per file:

find . -name '*.text' -exec sh -c \
    'for i do if [ ! -f "${i%.text}" ]; then echo == $i; fi;done' sh {} +
jlliagre
  • 5,833
  • I get tons of 'Unexpected operator' errors from this. I think it's "files with spaces" problem. – v010dya Jan 13 '15 at 18:14
  • Hmm... also it gives me all the text files now, regardless if they have a pair. – v010dya Jan 13 '15 at 18:27
  • 1
    I think the issue is that basename strips off the leading directory components as well as the suffix - try replacing "$(basename -s .text "$1")" by "${1%.*}" which should just remove the rightmost .suffix – steeldriver Jan 13 '15 at 19:07
  • @steeldriver Doesn't seem to help. I still get files that have pairs. – v010dya Jan 13 '15 at 19:51
  • @steeldriver You are right in your analysis, answer corrected. Thanks! – jlliagre Jan 13 '15 at 20:28
  • @jlliagre after a bit more thought, another (possibly better) option might be to return to using basename, but use -execdir in place of -exec so the file existence test happens relative to the directory containing each file.text – steeldriver Jan 13 '15 at 21:55
  • @steeldriver -execdir would indeed simplify the scripts but has more overhead than using the shell for filtering out the suffixes. – jlliagre Jan 14 '15 at 01:02