How to loop through file names returned by find?

前端 未结 13 1104
野性不改
野性不改 2020-11-22 04:20
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

相关标签:
13条回答
  • 2020-11-22 04:41

    You can put the filenames returned by find into an array like this:

    array=()
    while IFS=  read -r -d ''; do
        array+=("$REPLY")
    done < <(find . -name '*.txt' -print0)
    

    Now you can just loop through the array to access individual items and do whatever you want with them.

    Note: It's white space safe.

    0 讨论(0)
  • 2020-11-22 04:43
    # Doesn't handle whitespace
    for x in `find . -name "*.txt" -print`; do
      process_one $x
    done
    
    or
    
    # Handles whitespace and newlines
    find . -name "*.txt" -print0 | xargs -0 -n 1 process_one
    
    0 讨论(0)
  • 2020-11-22 04:45

    TL;DR: If you're just here for the most correct answer, you probably want my personal preference, find . -name '*.txt' -exec process {} \; (see the bottom of this post). 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.

    0 讨论(0)
  • 2020-11-22 04:46

    You can store your find output in array if you wish to use the output later as:

    array=($(find . -name "*.txt"))
    

    Now to print the each element in new line, you can either use for loop iterating to all the elements of array, or you can use printf statement.

    for i in ${array[@]};do echo $i; done
    

    or

    printf '%s\n' "${array[@]}"
    

    You can also use:

    for file in "`find . -name "*.txt"`"; do echo "$file"; done
    

    This will print each filename in newline

    To only print the find output in list form, you can use either of the following:

    find . -name "*.txt" -print 2>/dev/null
    

    or

    find . -name "*.txt" -print | grep -v 'Permission denied'
    

    This will remove error messages and only give the filename as output in new line.

    If you wish to do something with the filenames, storing it in array is good, else there is no need to consume that space and you can directly print the output from find.

    0 讨论(0)
  • 2020-11-22 04:50
    find . -name "*.txt"|while read fname; do
      echo "$fname"
    done
    

    Note: this method and the (second) method shown by bmargulies are safe to use with white space in the file/folder names.

    In order to also have the - somewhat exotic - case of newlines in the file/folder names covered, you will have to resort to the -exec predicate of find like this:

    find . -name '*.txt' -exec echo "{}" \;
    

    The {} is the placeholder for the found item and the \; is used to terminate the -exec predicate.

    And for the sake of completeness let me add another variant - you gotta love the *nix ways for their versatility:

    find . -name '*.txt' -print0|xargs -0 -n 1 echo
    

    This would separate the printed items with a \0 character that isn't allowed in any of the file systems in file or folder names, to my knowledge, and therefore should cover all bases. xargs picks them up one by one then ...

    0 讨论(0)
  • 2020-11-22 04:50

    If you can assume the file names don't contain newlines, you can read the output of find into a Bash array using the following command:

    readarray -t x < <(find . -name '*.txt')
    

    Note:

    • -t causes readarray to strip newlines.
    • It won't work if readarray is in a pipe, hence the process substitution.
    • readarray is available since Bash 4.

    Bash 4.4 and up also supports the -d parameter for specifying the delimiter. Using the null character, instead of newline, to delimit the file names works also in the rare case that the file names contain newlines:

    readarray -d '' x < <(find . -name '*.txt' -print0)
    

    readarray can also be invoked as mapfile with the same options.

    Reference: https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream

    0 讨论(0)
提交回复
热议问题