Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I am running the following to find all directories without a number in them (not including the original path):

find /path/2/directory -type d -name '[!0-9]*'

The problem is that it finds directories that are subdirectories of directories that have numbers.

Example:

/path/2/directory/20160303/backup

or even

/path/2/directory/backup20160303a/backup

Is there any way to prevent find from returning such directories?

I cannot solve this problem by limiting the depth, the depth can vary.

Example:

/path/2/directory/subdirectory/20160303/backup
share|improve this question
up vote 10 down vote accepted

Use -prune to ignore those directories:

find /path/to/directory -name '*[0-9]*' -prune -o -type d -print

though if you're on a gnu setup you may want to use the C locale when running the above, see Stéphane's comment below.

share|improve this answer
    
This one worked without any tweaking. Thanks for the help. – Mocking Mar 3 at 23:03
    
Note that on GNU systems at least, that will still include directories whose name contains digits and an invalid character. You could fix the locale to C to avoid that. – Stéphane Chazelas Mar 5 at 8:51
    
Note that the first -type d is not necessary here. – Stéphane Chazelas Mar 5 at 8:51
    
@StéphaneChazelas - yup, thanks for the heads up ! I assume that gnu specific behaviour is a known bug ? @Mocking - it doesn't need tweaking because it uses the right pattern with -name as opposed to Anthon's solution, see my comments under his answer. – don_crissti Mar 5 at 14:11
1  
Not really a bug, * is 0 or more characters, so doesn't match non-characters. Behaviour differs across implementations. Same as for regexps, . matches a character, so .* doesn't match across non-characters in most implementations. When it comes to shells * and ? in practice do match bytes that don't form part of valid characters (even though at least for ? that's against POSIX (and something that needs to be addressed in the spec). – Stéphane Chazelas Mar 6 at 18:15

If you don't want find to descent beyond a certain match you should use -prune and not e.g. -path or filtering the output of find with grep -v

To test things out make an environment with some extra files and subdirs, so you can check your find not to display unwanted material:

mkdir -p tmp/2/abc/def
touch tmp/2/abc/def/file1
mkdir -p tmp/2/abc/9876/xyz
touch tmp/2/abc/9876/xyz/file2
tree tmp/

gives:

tmp
└── 2
    └── abc
        ├── 9876
        │   └── xyz
        │       └── file2
        └── def
            └── file1

If you do find tmp/2/abc \! -path "*[0-9]*" as suggested by @terdon, the output will be empty, because -path doesn't just take into account the directories starting below abc but the whole path, which includes 2. So that is not what you want.

If you do find tmp/2/abc -type d | grep -vE '/[0-9-]+(/|$)', as suggested by @cas, you'll find that it doesn't print anything either, again because it matcheds not just the files down from where you're searching but also the directory named 2. Apart from that this would require find to first walk the whole tree under 9876 and if there are a few hundred thousand items there the walking (and filtering) will take a noticable amount of time.

If you do:

find tmp/2/abc -type d -name '[!0-9]*' -print 

you will find that the output includes the path tmp/2/abc/9876/xyz. To get rid of that cut off what you don't want with -prune:

find tmp/2/abc -type d -name '[!0-9]*' -print -o -name '[0-9]*' -prune

which gives:

tmp/2/abc
tmp/2/abc/def

you can slightly improve the on the efficiency of that by swapping the pruning and printing which is what @don_cristti did in his enhancement of this answer.

share|improve this answer
    
I am aware of maxdepth but the depth can vary. Thanks though. – Mocking Mar 3 at 22:40
    
@Mocking I noticed, you have to prune matching directories that have numbers, I updated the answer – Anthon Mar 3 at 22:43
    
I'm gonna upvote this since you posted prune first but it didn't work for some cases even when I tweaked it. @don_crissti had a copy and paste working one. Thanks for the help though. Edit: Nevermind I don't have enough rep. – Mocking Mar 3 at 23:03
    
fwiw, your post and terdon's weren't working OK when I posted mine and they're still not working fine - at least on my setup; terdon's has the flaws you mentioned and it prints also regular file names and yours doesn't work as expected - I had to replace -name '[!0-9]*' with ! -name '*[0-9]*' (and use the same '*[0-9]*' for the second -name) to make it work. – don_crissti Mar 5 at 11:05
    
without details of your setup it is easy to claim they are not working fine and that a minor tweak is necessary, that is why I included my test environment in my answer and pointed out the issues with the others. – Anthon Mar 5 at 11:12

I think you're looking for -path:

   -path pattern
          File  name matches shell pattern pattern.  The metacharacters do
          not treat `/' or `.' specially; so, for example,
                    find . -path "./sr*sc"
          will print an entry for a directory called `./src/misc' (if  one
          exists).  

It searches the entire path of each file/directory for the given pattern. So, if you have a directory structure like this:

$ tree
.
├── 111
│   └── foo
│       └── bar
├── bar
│   └── foo
│       └── baz
└── foo
    └── 111
        └── bar

9 directories, 0 files

You can use find like this:

$ find /path/to/directory/ \! -path "*[0-9]*" 
.
./foo
./bar
./bar/foo
./bar/foo/baz

Or, with GNU find:

find /path/to/directory/ -not -path "*[0-9]*" 

Since this will have to descend into each directory to check its name, it will be significantly slower on large directory trees than @Anthon's solution with prune. If you don't have thousands of directories, though, it should be fine.

share|improve this answer
    
This would have been almost perfect, the problem I think is that my original /path/to/directory has a number in it. This is my fault for not clarifying the problem, I edited my question. – Mocking Mar 3 at 23:01

It's easy to use grep (or awk or sed or perl etc) to remove such directories from find's output:

find /path/to/directory -type d | grep -vE '/[0-9-]+(/|$)'

Note: I've included - in the character class because dated directories are sometimes YYYY-MM-DD rather than just YYYYMMDD

share|improve this answer
    
Of course it might be horrible expensive (in time) to first descend into subdirectories that you know you can ignore upfront – Anthon Mar 3 at 22:46

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.