13

I was just trying to list all directories and files under current directory and also write if they are file or directory with the following command:

find -exec echo `echo {} : ;if [ -f {} ]; then echo file; else echo directory;fi` \;

I know it is a silly command, I can use other things like -type f or -type d, but I want to learn why that piece of code did not work as I expected. It just prints directory to all of them. For example while output of find is:

.
./dir
./dir/file

output of my code is :

. : directory
./dir : directory
./dir/file : directory

And output of

echo `echo dir/file : ;if [ -f dir/file ]; then echo file; else echo directory;fi`

is

dir/file : file

I am working on Ubuntu 14.10 and using find (GNU findutils) 4.4.2

Esref
  • 553
  • 2
  • 6
  • 17
  • 1
    find -exec bash -c 'echo -n "{} : ";if [ -f "{}" ]; then echo file; else echo directory;fi' \; – Costas Feb 26 '15 at 22:09
  • 1
    Thanks @Costas but I know that using bash or sh I can achieve my initial goal, but now I want to understand why if behaves different with the exec parameter. – Esref Feb 26 '15 at 22:13
  • Put arguments into quotes "{}". Why you use echo twice? – Costas Feb 26 '15 at 22:19
  • I already tried that and it gives the same output. find -exec echo echo "{}" : ;if [ -f "{}" ]; then echo file; else echo directory;fi ; – Esref Feb 26 '15 at 22:23

2 Answers2

17

First, your snippet executes the command

echo {} : ;if [ -f {} ]; then echo file; else echo directory;fi

because it needs its output to evaluate the command substitution. Since there is no file named {}, this produces the output

{} :
directory

Then the find command is executed with the arguments -exec, echo, {}, :, directory, so for every file, it outputs the file name followed by a space and : directory.

What you actually want to do is to execute the shell snippet echo {} :; … on each file found by find. This snippet must be executed by a shell spawned by find, not by the shell that starts find, since it is receiving data from find on its command line. Therefore you need to instruct find to run a shell:

find -exec sh -c 'echo {} : ;if [ -f {} ]; then echo file; else echo directory;fi' \;

This is better, but still not right. It'll work with some (not all) find implementations if your file names don't contain any special characters, but since you are interpolating the file name in a shell script, you allow file names to execute arbitrary shell commands, e.g. if you have a file called $(rm -rf /) then the command rm -rf / will be executed. To pass file names to the script, pass them as separate arguments.

Also the first echo prints a newline after the colon. Use echo -n (if your shell supports it) or printf to avoid this.

find -exec sh -c 'printf "%s :" "$0"; if [ -f "$0" ]; then echo file; else echo directory; fi' {} \;

You can use -exec … {} + to group shell invocations, which is faster.

find -exec sh -c 'for x; do printf "%s :" "$x"; if [ -f "$x" ]; then echo file; else echo directory; fi; done' _ {} +
6

Another way for executing if; then; else; fi together with find is:

find |
while IFS= read -r p; do if [ -f "$p" ]; then echo file; else echo directory; fi; done
ncomputers
  • 1,524
  • 1
  • 11
  • 23