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