HOWTO · Bash

How to Run find -exec Command in Bash

This article explores how to effectively use the find command with the -exec option in Bash. Learn to search for specific text in files and execute commands on found files. Discover practical examples, including searching for text, deleting old logs, and moving large files. Enhance your command-line skills with these powerful techniques.

On this page

find selects file-system paths; grep searches the contents of files. The -exec action connects them by passing each selected path as an argument to another command. This guide shows reusable syntax, content searches, the difference between running once per match and batching paths, and cautious file operations.

How find -exec works

find starts at a directory you specify, evaluates tests such as file type, name, age, or size, and visits matching paths. With -exec, it invokes the command you name and substitutes each matched path for {}. The path is passed as an argument, so spaces, tabs, quotes, and wildcard characters in a filename are not reparsed by a shell. This is safer than building a command string or piping newline-separated names into another command.

The search and action are separate jobs: a predicate such as -name '*.txt' chooses files, while a command such as grep reads their contents. Add -type f when the action should apply only to regular files. By default, find also considers directories, so choose predicates that match the kind of input the command expects.

Basic find -exec syntax

The reusable form is find STARTING_PATH TESTS -exec COMMAND {} \;. STARTING_PATH sets the tree to search; tests restrict which paths reach the action; COMMAND and its options specify the work; and {} marks where find inserts a path. In an interactive shell, write the terminator as \; so the shell passes a literal semicolon to find. Without that escape or quoting, the shell treats the semicolon as a command separator.

Quote filename patterns such as '*.txt'. The quotes make the shell pass the pattern intact, allowing find to match names at each visited path instead of expanding the pattern against files in the current directory. Paths beginning with a hyphen should be written with an explicit starting directory such as ./-archive so they are not mistaken for command options.

Run this example:

set -eu
root=$(mktemp -d)
trap 'rm -rf "$root"' EXIT
mkdir -p "$root/notes"
printf 'report: first\nkeep this line\n' > "$root/notes/weekly report.txt"
printf 'draft only\n' > "$root/notes/draft.txt"
find "$root" -type f -name '*.txt' -exec grep -nH 'report' {} +

The example creates its own small directory, searches only text files, and asks grep to print matching line numbers and file names. -H keeps the file name visible even when a particular invocation receives one path. The temporary directory is removed when the shell exits. This is a runnable illustration; no captured output is asserted here.

Search file contents with find and grep

To search text under /documents, use find /documents -type f -name '*.txt' -exec grep -nH 'report' {} +. find selects the candidate text files, and grep prints each matching line with its path and line number. Use grep -l instead of -nH when you need only the names of files containing a match. A matching file can contain multiple matching lines, so the line-oriented form may print the same path more than once.

The search string is passed literally as one argument because it is quoted. For a regular expression, describe the expression deliberately; for a plain fixed string, grep -F avoids interpreting regular-expression metacharacters. A text search can fail to read a file because of permissions or file encoding, so treat the command’s diagnostics and exit status as part of the result when scripting around it. No sample paths are listed here because matches depend on the files present in the reader’s directory.

Choose between ; and +

With -exec COMMAND {} \;, find runs the command separately for every matching path. This form is easy to understand and works with commands designed to process one path at a time. The shell-escaped semicolon ends the action definition; it is not part of the command being run.

With -exec COMMAND {} +, find supplies multiple matched paths to each command invocation, splitting them into groups that fit the system’s argument-size limit. Put {} once, as the final command argument immediately before +. Batching often reduces process-start overhead for tools such as grep, but it does not change what those tools do to each file, and it is not useful for a command whose meaning depends on receiving exactly one path. The plus sign is passed directly to find; it does not need shell escaping.

Run other commands on matched files safely

For a first pass before removing old logs, replace the action with -print and inspect the listed paths: find /var/log -type f -name '*.log' -mtime +30 -print. The -mtime +30 test selects files whose modification time is more than 30 whole 24-hour periods ago; it does not mean “older than the thirtieth calendar date.” Once you have checked the selection and have the required permissions, GNU and many other find implementations support -delete, as in find /var/log -type f -name '*.log' -mtime +30 -delete. Deletion is irreversible through this command, so verify the starting directory and predicates before using it.

To move regular files larger than 100 mebibytes from a downloads tree, first inspect find /downloads -type f -size +100M -print. If the list is right and /archive/ exists on the same system with suitable permissions, a per-file action is find /downloads -type f -size +100M -exec mv -- {} /archive/ \;. The +100M size test uses find’s size units and rounds up to the next unit; confirm the implementation’s unit conventions if an exact byte threshold matters. Moving files can overwrite or collide with names already in the destination, so check for duplicate basenames before running it. For either task, keep the selection criteria narrow and review the preview before changing files.