I need paths to be both fully resolved and relative to a given directory. This must be done efficiently, since the number of paths is often greater than 100,000.
Situation: I have directories which contain mostly symlinks to other directories, as in
foo
123 -> ../baz/123
896 -> ../bar/896
(Note that foo does NOT contain only symlinks to directories, it also contains ordinary files which I have to catch too.)
These symlinked directories contain files. . I want to get a list of those files, in the form
baz/123/some.file
bar/123/other.file
That is, when 'find' finds a symlink, I want it to dereference that path when it is reporting the contents.
So I am running this command from the parent directory of foo:
find -L foo -type f
But that doesn't work.
Honestly, you would expect the -L
option, which claims to 'follow symbolic links', to implement this behaviour. But, its actual behaviour is to look into the contents of those directories, but report files inside them with their non-dereferenced names, ie. the results look like
foo/baz/123/some.file
foo/bar/896/another.file
The results will be used for set operations against a list of filepaths that are all 1. fully resolved and 2. relative to foo's parent directory, so every result must also fulfil that criteria. I can guarantee for these purposes that all links are resolvable ie. none are circular or excessively deep. Most, but not all links, point at directories rather than files.
At the moment, the best I can do is a Python script that rewrites any non-dereferenced paths into resolved ones. But since the numbers of files involved is in the 100000+ range, this is not very practical (and rather ridiculous, since find
already had bothered to dereference them, it just didn't return the dereferenced paths).
(EDIT: See my comment on this post -- I've found a non-solution (in that it does the job efficiently but in the wrong way -- executing external commands).)
I'm convinced that I should be able to do this task just with find
and no external commands, but I'm not finding the man page enlightening here -- none of -L
, -H
, -P
, -follow
have the correct behaviour, nor does -printf %l
. -exec
is out for obvious reasons -- it is not internal to find
. Any ideas?
EDIT 2: at this point, Stephane has convinced me that there is no particularly good reason why find would have this functionality internally, so I'm willing to accept any reasonably efficient answer.
find -L foo -type f -exec realpath {} "+" | sed "s,^$REALPATH_OF_TOPLEVEL_DIR,,"
. This is actually acceptable in speed (3.8s to process 160000 files), but does not match the criteria of the question. I still believe that there is a way to do this basic thing without resorting to-exec
.-exec ... +
form:find -L foo -type f -print0 | xargs -0 realpath --relative-to "$REALPATH_OF_TOPLEVEL_DIR"
. This is virtually identical to the non-posixfind -L foo -type f -exec realpath --relative-to "$REALPATH_OF_TOPLEVEL_DIR" {} "+"
except an extra process is spawned.find
doesn't dereference them, it just follows them, or ommits to ignore them when descending the directory tree if you prefer.-exec cmd {} +
is POSIX-print0
andrealpath
are not.-printf '%s\0' {} +
. The problem is more than itsxargs
is unusable (no-0
, no-r
, and the minimum guaranteed limits are too low to be useful).