How to exclude a directory in find . command

后端 未结 30 1359
醉酒成梦
醉酒成梦 2020-11-22 03:36

I\'m trying to run a find command for all JavaScript files, but how do I exclude a specific directory?

Here is the find code we\'re using.<

相关标签:
30条回答
  • 2020-11-22 03:59

    how-to-use-prune-option-of-find-in-sh is an excellent answer by Laurence Gonsalves on how -prune works.

    And here is the generic solution:

    find /path/to/search                    \
      -type d                               \
        \( -path /path/to/search/exclude_me \
           -o                               \
           -name exclude_me_too_anywhere    \
         \)                                 \
        -prune                              \
      -o                                    \
      -type f -name '*\.js' -print
    

    To avoid typing /path/to/seach/ multiple times, wrap the find in a pushd .. popd pair.

    pushd /path/to/search;                  \
    find .                                  \
      -type d                               \
        \( -path ./exclude_me               \
           -o                               \
           -name exclude_me_too_anywhere    \
         \)                                 \
        -prune                              \
      -o                                    \
      -type f -name '*\.js' -print;         \
     popd
    
    0 讨论(0)
  • 2020-11-22 04:01

    If -prune doesn't work for you, this will:

    find -name "*.js" -not -path "./directory/*"
    

    Caveat: requires traversing all of the unwanted directories.

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

    There is clearly some confusion here as to what the preferred syntax for skipping a directory should be.

    GNU Opinion

    To ignore a directory and the files under it, use -prune
    

    From the GNU find man page

    Reasoning

    -prune stops find from descending into a directory. Just specifying -not -path will still descend into the skipped directory, but -not -path will be false whenever find tests each file.

    Issues with -prune

    -prune does what it's intended to, but are still some things you have to take care of when using it.

    1. find prints the pruned directory.

      • TRUE That's intended behavior, it just doesn't descend into it. To avoid printing the directory altogether, use a syntax that logically omits it.
    2. -prune only works with -print and no other actions.

      • NOT TRUE. -prune works with any action except -delete. Why doesn't it work with delete? For -delete to work, find needs to traverse the directory in DFS order, since -deletewill first delete the leaves, then the parents of the leaves, etc... But for specifying -prune to make sense, find needs to hit a directory and stop descending it, which clearly makes no sense with -depth or -delete on.

    Performance

    I set up a simple test of the three top upvoted answers on this question (replaced -print with -exec bash -c 'echo $0' {} \; to show another action example). Results are below

    ----------------------------------------------
    # of files/dirs in level one directories
    .performance_test/prune_me     702702    
    .performance_test/other        2         
    ----------------------------------------------
    
    > find ".performance_test" -path ".performance_test/prune_me" -prune -o -exec bash -c 'echo "$0"' {} \;
    .performance_test
    .performance_test/other
    .performance_test/other/foo
      [# of files] 3 [Runtime(ns)] 23513814
    
    > find ".performance_test" -not \( -path ".performance_test/prune_me" -prune \) -exec bash -c 'echo "$0"' {} \;
    .performance_test
    .performance_test/other
    .performance_test/other/foo
      [# of files] 3 [Runtime(ns)] 10670141
    
    > find ".performance_test" -not -path ".performance_test/prune_me*" -exec bash -c 'echo "$0"' {} \;
    .performance_test
    .performance_test/other
    .performance_test/other/foo
      [# of files] 3 [Runtime(ns)] 864843145
    

    Conclusion

    Both f10bit's syntax and Daniel C. Sobral's syntax took 10-25ms to run on average. GetFree's syntax, which doesn't use -prune, took 865ms. So, yes this is a rather extreme example, but if you care about run time and are doing anything remotely intensive you should use -prune.

    Note Daniel C. Sobral's syntax performed the better of the two -prune syntaxes; but, I strongly suspect this is the result of some caching as switching the order in which the two ran resulted in the opposite result, while the non-prune version was always slowest.

    Test Script

    #!/bin/bash
    
    dir='.performance_test'
    
    setup() {
      mkdir "$dir" || exit 1
      mkdir -p "$dir/prune_me/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/w/x/y/z" \
        "$dir/other"
    
      find "$dir/prune_me" -depth -type d -exec mkdir '{}'/{A..Z} \;
      find "$dir/prune_me" -type d -exec touch '{}'/{1..1000} \;
      touch "$dir/other/foo"
    }
    
    cleanup() {
      rm -rf "$dir"
    }
    
    stats() {
      for file in "$dir"/*; do
        if [[ -d "$file" ]]; then
          count=$(find "$file" | wc -l)
          printf "%-30s %-10s\n" "$file" "$count"
        fi
      done
    }
    
    name1() {
      find "$dir" -path "$dir/prune_me" -prune -o -exec bash -c 'echo "$0"'  {} \;
    }
    
    name2() {
      find "$dir" -not \( -path "$dir/prune_me" -prune \) -exec bash -c 'echo "$0"' {} \;
    }
    
    name3() {
      find "$dir" -not -path "$dir/prune_me*" -exec bash -c 'echo "$0"' {} \;
    }
    
    printf "Setting up test files...\n\n"
    setup
    echo "----------------------------------------------"
    echo "# of files/dirs in level one directories"
    stats | sort -k 2 -n -r
    echo "----------------------------------------------"
    
    printf "\nRunning performance test...\n\n"
    
    echo \> find \""$dir"\" -path \""$dir/prune_me"\" -prune -o -exec bash -c \'echo \"\$0\"\'  {} \\\;
    name1
    s=$(date +%s%N)
    name1_num=$(name1 | wc -l)
    e=$(date +%s%N)
    name1_perf=$((e-s))
    printf "  [# of files] $name1_num [Runtime(ns)] $name1_perf\n\n"
    
    echo \> find \""$dir"\" -not \\\( -path \""$dir/prune_me"\" -prune \\\) -exec bash -c \'echo \"\$0\"\' {} \\\;
    name2
    s=$(date +%s%N)
    name2_num=$(name2 | wc -l)
    e=$(date +%s%N)
    name2_perf=$((e-s))
    printf "  [# of files] $name2_num [Runtime(ns)] $name2_perf\n\n"
    
    echo \> find \""$dir"\" -not -path \""$dir/prune_me*"\" -exec bash -c \'echo \"\$0\"\' {} \\\;
    name3
    s=$(date +%s%N)
    name3_num=$(name3 | wc -l)
    e=$(date +%s%N)
    name3_perf=$((e-s))
    printf "  [# of files] $name3_num [Runtime(ns)] $name3_perf\n\n"
    
    echo "Cleaning up test files..."
    cleanup
    
    0 讨论(0)
  • 2020-11-22 04:02

    None of previous answers is good on Ubuntu. Try this:

    find . ! -path "*/test/*" -type f -name "*.js" ! -name "*-min-*" ! -name "*console*"
    

    I have found this here

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

    -prune definitely works and is the best answer because it prevents descending into the dir that you want to exclude. -not -path which still searches the excluded dir, it just doesn't print the result, which could be an issue if the excluded dir is mounted network volume or you don't permissions.

    The tricky part is that find is very particular about the order of the arguments, so if you don't get them just right, your command may not work. The order of arguments is generally as such:

    find {path} {options} {action}
    

    {path}: Put all the path related arguments first, like . -path './dir1' -prune -o

    {options}: I have the most success when putting -name, -iname, etc as the last option in this group. E.g. -type f -iname '*.js'

    {action}: You'll want to add -print when using -prune

    Here's a working example:

    # setup test
    mkdir dir1 dir2 dir3
    touch dir1/file.txt; touch dir1/file.js
    touch dir2/file.txt; touch dir2/file.js
    touch dir3/file.txt; touch dir3/file.js
    
    # search for *.js, exclude dir1
    find . -path './dir1' -prune -o -type f -iname '*.js' -print
    
    # search for *.js, exclude dir1 and dir2
    find . \( -path './dir1' -o -path './dir2' \) -prune -o -type f -iname '*.js' -print
    
    0 讨论(0)
  • 2020-11-22 04:04

    The -path -prune approach also works with wildcards in the path. Here is a find statement that will find the directories for a git server serving multiple git repositiories leaving out the git internal directories:

    find . -type d \
       -not \( -path */objects -prune \) \
       -not \( -path */branches -prune \) \
       -not \( -path */refs -prune \) \
       -not \( -path */logs -prune \) \
       -not \( -path */.git -prune \) \
       -not \( -path */info -prune \) \
       -not \( -path */hooks -prune \)  
    
    0 讨论(0)
提交回复
热议问题