Using connectors after find command

I want my bash to print 'found' only if something is found, using the find command. But using && does not help: even if found nothing, I'm getting 'found' printed. Example:

$ pwd
/data/data/com.termux/files/home/test/test1/test4
$ ls
xaa  xab
$ find . -name xac && echo 'found'
found
$ find . -name xaa && echo 'found'
./xaa
found

Solution 1:

You can make find itself print found:

find . -name xac -printf "found\n" -quit

The -quit will make find quit after the first match, so found is only printed at most once.

On a similar thread on Unix & Linux (make find fail when nothing was found), I used grep -qz to return a non-zero exit status if find found nothing:

find /some/path -print0 -quit | grep -qz .

Which you can use to construct compound commands using && or if:

find /some/path -print0 -quit | grep -qz . && echo found

Solution 2:

muru's answer is appropriate and well suited for cases where we want to print something if file is found. For general case when we want to execute external command, such as echo, we could use -exec flag.

$ find . -name 'xac' -exec echo "I found " {} \; -quit             
I found  ./xac

The {} part passes filename to the command between -exec and \; as arguments. Note the \ before ; - it prevents shell from misinterpreting it; in shell closing semicolon signifies end of command, but when escaped with slash , shell will treat it as literall text to be passed to find command and to find command it serves as closing -exec flag's arguments.


For constructing conditionals of the if found do this; else do that sort, we could make use of command substition $() and test command ( aka [ ):

$ [ "x$(find . -name 'noexist' -print -quit)" != "x" ] && echo "found" || echo "not found"                                                                                              
not found

$ [ "x$(find . -name 'xac' -print -quit)" != "x" ] && echo "found" || echo "not found"                                                                                                  
found

Addressing Dan's comment

Dan in the comments asked:

Wouldn't echo "I found {}" be better than echo "I found " {}? Maybe for echo it's fine, but if someone copies the command and replaces echo with another command, they may have a problem

Let's understand the problem first. Usually, in shells there's concept of word-splitting, which means unquoted variables and positional parameters will be expanded and treated as separate items. For instance, if you have variable var and it contains hello world text, when you do touch $var the shell will break it down into two separate items hello and world and touch will understand that as if you were trying to create 2 separate files; if you do touch "$var", then shell will treat hello world as one unit, and touch will create one file only. This is important to understand that this happens only due to how shells work.

By contrast, find doesn't suffer from such behavior, because commands are processed by find itself and executed by execvp() system call, so there's no shell involved. While curly braces do have special meaning in shells, because they appear in the middle of find command, and not in the beginning, they carry no special meaning to shell in this case. Here's an example. Let's create a few difficult filenames and try to pass them as argument to stat command.

$ touch with$'\t'tab.txt with$' 'space.txt with$'\n'newline.txt

$ find -type f -exec stat -c "%F" {} \; -print                                                                                                                         
regular empty file
./with?newline.txt
regular empty file
./with space.txt
regular empty file
./with?tab.txt

As you can see, stat receives difficult filenames perfectly fine with find, which is one of the main reasons why it is recommended for usage in portable scripts, and especially useful when you're traversing directory tree and want to do something with filenames that might potentially have special characters in them. Therefore, it is not necessary to quote curly braces for commands executed in find.

It is a different story when shell gets involved. Sometimes you need to use a shell to process filename. In that case, quoting will indeed matter, but it's important to realize that it's not find's problem - it is the shell that does word splitting.

$ find -type f -exec bash -c "stat {}" sh \;   
stat: cannot stat './with': No such file or directory
sh: line 1: newline.txt: command not found
stat: cannot stat './with': No such file or directory
stat: cannot stat 'space.txt': No such file or directory
stat: cannot stat './with': No such file or directory
stat: cannot stat 'tab.txt': No such file or directory

So when we quote within shell, it will work. But again, that's important for shell, not find.

$ find -type f -exec bash -c "stat -c '%F' '{}'" sh \;                                                                                                                 
regular empty file
regular empty file
regular empty file