Sample Header Ad - 728x90

How does find . -type f -print -o -name . -o -prune actually work?

2 votes
2 answers
803 views
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:
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.
Asked by Christopher Eberle (23 rep)
Mar 23, 2022, 02:50 PM
Last activity: Apr 18, 2022, 04:07 PM