2

As background, I'm using git on an hp-ux 11.11 (11v1) system. I'm doing an extensive .gitignore file to exclude many many files. My .gitignore is basically "Ignore everything, except these directories, but also ignore these patterns under those directories." Precisely how and why is not exactly relevant to the question.

My object is to be sure I'm getting what I expect to get when I run git add before running git add. To accomplish this, I'm running tests against a given directory's files with git check-ignore.

My approach normally looks thus:

find test2 -type f -exec git check-ignore -v -n {} \; grep !

For clarity, the repository exists in the root (/.git) (it's actually symlinked to /baz/dev/myRepo/.git), so I'm calling, for example, git status from /. I'm also calling find from / so that the paths match what git is expecting to see and the format of the paths returned by find to the -exec action match.

Standard output for this would look something like the following:

.gitignore:208:!test2/backupScripts/**  test2/backupScripts/CV_PostJob.sh
.gitignore:208:!test2/backupScripts/**  test2/backupScripts/CV_PostJob.sh.old
.gitignore:208:!test2/backupScripts/**  test2/backupScripts/CV_PreJob.sh
.gitignore:208:!test2/backupScripts/**  test2/backupScripts/CV_PreJob.sh.old
.gitignore:208:!test2/backupScripts/**  test2/backupScripts/CV_ScanCheck.sh
.gitignore:208:!test2/backupScripts/**  test2/backupScripts/oldCH.txt
.gitignore:212:!test2/scripts/**        test2/scripts/deprecated/CommvaultBackupScript.sh
.gitignore:212:!test2/scripts/**        test2/scripts/deprecated/NetBackupScript.sh
.gitignore:212:!test2/scripts/**        test2/scripts/benchmark.sh
.gitignore:212:!test2/scripts/**        test2/scripts/migrateVolumeSync.sh
.gitignore:212:!test2/scripts/**        test2/scripts/test.sh
.gitignore:206:!test2/* test2/CommvaultBackupScript.sh
.gitignore:206:!test2/* test2/NetBackupScript.sh
.gitignore:206:!test2/* test2/benchmark.sh
.gitignore:206:!test2/* test2/test.sh

Generally, this technique works well, however there are in some cases directories such as /test2/foo and /test2/bar which both contain large numbers of files which will be excluded by patterns in the .gitignore:

#Ignore everything
*

#add back /test2
!test2
!test2/**
#reignore foo and bar
test2/foo
test2/bar

So, the core problem is: I'd like to be able to run find test2 -exec git check-ignore without enumerating test2/foo and test2/bar and not have to write a command like find test2 -name foo -prune -o -name bar -prune -o -type f -exec git check-ignore {} \; especially for cases where the ignored subdirectories may number over a dozen and in reality, I just want to validate that the files immediately in test2/ specifically are going to be properly included, and then do another run to validate a single small subdirectory such as test2/backupScripts.

Ultimately, I'd like to adapt the POSIX compliant command find . -type f -print -o -name . -o -prune such that it can be run with -exec and so that find passes paths to git check-ignore which are absolute instead of relative. However, I'm at a loss understanding how exactly that command works. I can confirm that it does indeed only scan the root directory of wherever it is run from.

I'm laboring under the restriction of not having a modern version of findutils, and no apparent availability of GNU findutils for the platform, so -maxdepth option is not available.

Additionally, /test2 is a mountpoint, so running git from within test2 for the repository at /.git will require modifying Git's environment (export GIT_DISCOVERY_ACROSS_FILESYSTEM=1) to enable traversing mountpoint boundaries (at which point cd test2 && find . -type f -exec git check-ignore --no-index -v -n {} \; -o -name . -o -prune | grep ! works fine). I'd prefer not to do this if it's possible (for other uses of Git on the system).

So, how exactly does find . -type f -print -o -name . -o -prune work? Can it be modified to replace one or more of the dots with some pathspec that will enable it to be called from /? Or is this some kind of "magic" (as in: the desired behavior can only be elicited when options are provided in this order) sequence that performs a specific function when provided in this specific order? A google search for this specific command appears to return little else besides reposts of this specific answer itself in multiple languages: find without recursion

I've attempted to deconstruct this specific command's behavior by doing the following:

cd /
find test2 -type f -print -o -name . -o prune
#no output

cd /test2
find . -type f -print -o -name . -o -prune
#get files in CWD as expected

find . -type f -print 
#gives me all files and directories below this, as you would expect

find . -type f -print -o -name . 
#gives the same as the prior command

find . -name . 
#gives just the CWD entry (.)

find . -name . -o -prune 
#gives just the files and folders in CWD, including the (.) entry

cd /
find test2 -name test2 -o -prune 
#also gives the files and folders directly in test2, including test2 itself

find test2 -name test2 -o -prune -o -type f -print 
#no output

find test2 -name test2 -prune 
#returns just test2 itself

find test2 -type f -print -o -name test2 -prune 
#predictably gives no output, as test2 is pruned

find test2 -type f -print -o -name foo -prune 
#gives full directory list except for /test2/foo, as expected.

find test2 -type f -print -o -name "test2/*" -prune 
#gives full directory list, as a / will never be in the "base" name which find tests.

cd /
find test2/* -type f -print -o -prune 
#this is the closest, same as "find . -name . -o -prune" but with cd to test2 first.
4
  • You're misusing find's -o.option - -o means OR, and the Unix Way evaluates the Right Hand Side (RHS) only if the LHS if False. If the LHS is True, the OR will be True, and there's no reason to evaluate the RHS. Read man find repeatedly.
    – waltinator
    Mar 23 at 22:51
  • find . -type f -print -o -name . -o -prune doesn't do what you hope. -o (OR) looks at the state of find . -type f -print (-print is always True) and doesn't look at the rest of the command line. Remember your logic table for OR: True OR True = True, True OR False =True, False OR True = True, False OR False = False. If the Left Hand expression is True, the Right Hand expression doesn't have to be looked at.
    – waltinator
    Mar 23 at 23:17
  • First, this string, specifically, on HP-UX 11.11 does exactly what I state it does. It lists files in the current directory and only files in the current directory, excluding even the current directory listing (.). I don't know if I am or am not misusing -o, I pulled this from an answer here (stackoverflow.com/a/26163176/15324778) as stated in the question , but as my examples show, omitting any part of this results in the process breaking down in the ways described. The question is why does find give only files in the current directory when issued those parameters. (how does it work?) Apr 1 at 16:29
  • As a follow up, removing -o -prune from the end changes the behavior, as described in the sample output. Namely it outputs the current directory entry (.). Apr 1 at 16:35

2 Answers 2

3
find . -type f -print -o -name . -o -prune

Is a more convoluted way to find regular files in the current directory and not in subdirectories than the more canonical:

find . ! -name . -prune -type f

Which with the GNU implementation of find (or those which have copied GNU find's -mindepth/-maxdepth) can also be written:

find . -mindepth 1 -maxdepth 1 -type f

(-mindepth being redundant in this case as -type f would exclude . (depth 0) anyway).

In:

find . -type f -print -o -name . -o -prune

. is the file which find will start searching for files with.

. itself is not a regular file so it won't be printed, and the right part of the first -o will be tested. -name . matches, so the expression resolves to true and the -prune is not run. Because the whole find expression includes a -print which is an action predicate, there is no implicit printing even though the expression as a whole resolves to true for that file.

Now, since . was not pruned, and since it's a directory, find will read its contents and apply the expression to each of the files in it (to the exclusion of the . and .. special entries).

For those, for files of type regular, -print will be run. For any other type of file, since -name . cannot match, -prune will be executed. And for those files that are of type directory, that means that find won't descend into them.

So the important things in there are:

  • regular (f type) files are printed
  • directories other than . (the top level one) are pruned, so find won't descend into subdirectories.
3
  • So the "magic" is due to an explicit -print action predicate, that disables implicit printing for other any other match group, which is how the -o -name . portion causes the cwd entries to not get printed. Can you give advice on why "find . -name . -o -type f -print -o -prune" is invalid? Apr 18 at 15:29
  • @ChristopherEberle find . -name . -o -type f -print -o -prune is not invalid. It would also print the names of the regular files in the current directory and not descending into subdirectories. Apr 18 at 16:05
  • It was a typo on my part, I failed to put a dash before prune when entering the command. It does give exactly the same results, just as you said. Apr 18 at 16:11
0

From man find:

-prune
True; if the file is a directory, do not descend into it.

It's usually used with the -path option:

find $HOME -path $HOME/.config -prune \
  -type f -mtime 0

will search $HOME, ignoring $HOME/.config for files modified today.

1
  • This is not particularly helpful in the described scenario on account of the number of subdirectories in some cases will overrun what i can actually enter at a prompt due to prompt length limits. It also doesn't answer the question of the "magical" behavior of the specific arguments in the question itself. Mar 23 at 22:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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