Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to loop through file names returned by find?

Tags:

find

bash

x=$(find . -name "*.txt") echo $x 

if I run the above piece of code in Bash shell, what I get is a string containing several file names separated by blank, not a list.

Of course, I can further separate them by blank to get a list, but I'm sure there is a better way to do it.

So what is the best way to loop through the results of a find command?

like image 226
Haiyuan Zhang Avatar asked Mar 08 '12 02:03

Haiyuan Zhang


People also ask

How do I loop a file name?

Execute the following command to insert the file's name, followed by a newline, followed by the text Loops Rule! into each file: for FILE in *; do echo -e "$FILE\nLoops Rule\!" > $FILE; done.

What is Shopt?

Shopt is a built-in command in Unix-like operating systems, such as macOS and Linux distributions. The “shopt” command provides control over many settings that are used to tweak the operations in a Bash shell.


1 Answers

TL;DR: If you're just here for the most correct answer, you probably want my personal preference (see the bottom of this post):

# execute `process` once for each file find . -name '*.txt' -exec process {} \; 

If you have time, read through the rest to see several different ways and the problems with most of them.


The full answer:

The best way depends on what you want to do, but here are a few options. As long as no file or folder in the subtree has whitespace in its name, you can just loop over the files:

for i in $x; do # Not recommended, will break on whitespace     process "$i" done 

Marginally better, cut out the temporary variable x:

for i in $(find -name \*.txt); do # Not recommended, will break on whitespace     process "$i" done 

It is much better to glob when you can. White-space safe, for files in the current directory:

for i in *.txt; do # Whitespace-safe but not recursive.     process "$i" done 

By enabling the globstar option, you can glob all matching files in this directory and all subdirectories:

# Make sure globstar is enabled shopt -s globstar for i in **/*.txt; do # Whitespace-safe and recursive     process "$i" done 

In some cases, e.g. if the file names are already in a file, you may need to use read:

# IFS= makes sure it doesn't trim leading and trailing whitespace # -r prevents interpretation of \ escapes. while IFS= read -r line; do # Whitespace-safe EXCEPT newlines     process "$line" done < filename 

read can be used safely in combination with find by setting the delimiter appropriately:

find . -name '*.txt' -print0 |      while IFS= read -r -d '' line; do          process "$line"     done 

For more complex searches, you will probably want to use find, either with its -exec option or with -print0 | xargs -0:

# execute `process` once for each file find . -name \*.txt -exec process {} \;  # execute `process` once with all the files as arguments*: find . -name \*.txt -exec process {} +  # using xargs* find . -name \*.txt -print0 | xargs -0 process  # using xargs with arguments after each filename (implies one run per filename) find . -name \*.txt -print0 | xargs -0 -I{} process {} argument 

find can also cd into each file's directory before running a command by using -execdir instead of -exec, and can be made interactive (prompt before running the command for each file) using -ok instead of -exec (or -okdir instead of -execdir).

*: Technically, both find and xargs (by default) will run the command with as many arguments as they can fit on the command line, as many times as it takes to get through all the files. In practice, unless you have a very large number of files it won't matter, and if you exceed the length but need them all on the same command line, you're SOL find a different way.

like image 173
Kevin Avatar answered Sep 29 '22 12:09

Kevin