Question

I want to skip sub-directory $SKIP using find.
What a pity this basic command does not work in all cases:

find $DIR -path $SKIP -prune -o print

Please help me improving the script skipit.sh:

#!/bin/bash
SKIP=$1
shift
find "${@:-.}" -path "$SKIP" -prune -o -print

Basic examples

The example are based on following directory tree:

dir
├── skip
├── a
└── b

OK:

> ./skipit.sh  dir/skip  dir
dir
dir/a
dir/b

fail:

> ./skipit.sh  dir/skip  ./dir
./dir
./dir/skip
./dir/b
./dir/a

fail:

> ./skipit.sh  ./dir/skip  dir
dir
dir/skip
dir/b
dir/a

using -regex

> cat  skipit.sh
#!/bin/bash
SKIP=${1#./}  #remove leading './'
shift
find "${@:-.}" -regex "[.]?/?$SKIP" -prune -o -print

OK:

> ./skipit.sh  dir/skip  ./dir
./dir
./dir/b
./dir/a

OK:

> ./skipit.sh  ./dir/skip  dir
dir
dir/b
dir/a

fail:

> ./skipit.sh  ./dir/skip  dir/skip/..
dir/skip/..
dir/skip/../skip
dir/skip/../b
dir/skip/../a

My find version

> find --version
find (GNU findutils) 4.4.2
Copyright (C) 2007 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Eric B. Decker, James Youngman, and Kevin Dalley.
Construit avec GNU gnulib version e5573b1bad88bfabcda181b9e0125fb0c52b7d3b
Fonctions activées : D_TYPE O_NOFOLLOW(enabled) LEAF_OPTIMISATION FTS() CBO(level=0) 

I could use realpath but as this command is not installed by default, I do not want to force other users to have to install it. I can also use readlink, but I would prefer a solution based on find features ;)

the challenge

/home/user/dir1
├── skip
└── dir
    └── skip
/home/user/dir2
└── skip

The output of skipit.sh must ignore directory /home/user/dir1/skip only in the following examples:

/home/user> ./skipit.sh  /home/user/dir1/skip dir1 dir2
dir1
dir1/dir    
dir1/dir/skip
dir2
dir2/skip

/home/user> ./skipit.sh  dir1/skip ./dir1 ./dir2
./dir1
./dir1/dir    
./dir1/dir/skip
./dir2
./dir2/skip

/home/user> ./skipit.sh  ../user/dir1/skip ./dir1 ./dir2
./dir1
./dir1/dir    
./dir1/dir/skip
./dir2
./dir2/skip

/home/user> ./skipit.sh  dir1/skip /home/user/dir1 /home/user/dir2
/home/user/dir1
/home/user/dir1/dir    
/home/user/dir1/dir/skip
/home/user/dir2
/home/user/dir2/skip
Was it helpful?

Solution

Try this:

#!/bin/bash

SKIP="$(sed -r 's|^(\.?\./)*||' <<<$1)"
shift

for arg in "${@:-.}"; do
  root="$(realpath "$arg")"
  find "$root" -regex ".*$SKIP$" -prune -o -print
done

Sample folder structure:

/tmp/test
├── dir1
│   ├── dir
│   │   └── skip
│   └── skip
└── dir2
    └── skip

Output:

/tmp/test $ ./test.sh dir/skip dir1/skip/..
/tmp/test/dir1
/tmp/test/dir1/dir
/tmp/test/dir1/skip

/tmp/test $ ./test.sh /tmp/test/dir1/skip dir1 dir2
/tmp/test/dir1
/tmp/test/dir1/dir
/tmp/test/dir1/dir/skip
/tmp/test/dir2
/tmp/test/dir2/skip

/tmp/test $ ./test.sh dir1/skip dir1 dir2
/tmp/test/dir1
/tmp/test/dir1/dir
/tmp/test/dir1/dir/skip
/tmp/test/dir2
/tmp/test/dir2/skip

/tmp/test $ ./test.sh ../test/dir1/skip ./dir1 ./dir2
/tmp/test/dir1
/tmp/test/dir1/dir
/tmp/test/dir1/dir/skip
/tmp/test/dir2
/tmp/test/dir2/skip

/tmp/test $ ./test.sh dir1/skip /tmp/test/dir1 /tmp/test/dir2
/tmp/test/dir1
/tmp/test/dir1/dir
/tmp/test/dir1/dir/skip
/tmp/test/dir2
/tmp/test/dir2/skip

OTHER TIPS

The Ansgar Wiechers's answer is awesome and simple to read/maintain ;-)

In the mean time I also work on this issue and wrote something like that:

#!/bin/bash

SKIP="$1"
shift

# if $SKIP does not exist => fast version
if [[ ! -d "$SKIP" ]];  then
  find -H "${@:-.}"
fi

# check what is available to compute real path
shopt -sq expand_aliases
if type realpath &>/dev/null;   then
  alias rp='realpath'
elif type readlink &>/dev/null; then
  alias rp='readlink -fn'
elif type perl &>/dev/null;     then
  alias rp='perl -e '\''use Cwd "abs_path"; print abs_path("$ARGV[1]");'\'
else
  alias rp='echo'
fi

SKIP=$( rp "$SKIP" )

for dir in "${@:-.}"
do
  if [[ -e $dir ]];   then
    dir=$( rp "$dir" )
    find -H "$dir" -path "$SKIP" -prune -o -print
  fi
done

In fact, the original code is more complex and can be found at https://github.com/olibre/ShellScripts/blob/master/rmdups.sh

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top