You could make a tar archive containing the symbolic links only. Assuming GNU utilities (so non-embedded Linux or Cygwin):
cd /some/dir
tar cf symlinks.tar --no-recursion .
find -type l -print0 | xargs -0 tar rf symlinks.tar
Another approach is to generate a shell script that would recreate the symlinks. Here's a Perl approach (while this is doable with only POSIX utilities, it's difficult to handle special characters correctly).
use File::Find;
sub wanted {
if (-L $_) {
$link_name = $_;
$target = readlink($_);
die $! if !defined $target;
# protect single quotes from shell expansion
$link_name =~ s/\x27/\x27\\\x27\x27/;
$target =~ s/\x27/\x27\\\x27\x27/;
print "ln -s -- \x27$target\x27 \x27$link_name\x27\n";
}
}
find({wanted => \&wanted, no_chdir => 1}, ".")
Don't remove the symbolic links. Exclude them from the copy, or make a symlink-less copy of the directory tree, as illustrated in my answer to your other question.
What you're asking for doesn't make much sense in the general case, so it's not surprising that find
has no provision for it.
A symlink with a relative target is relative to the path of the symlink. So for instance, if by traversing a directory by following symlinks, find
encounters a/b/c/d
and a
, a/b
, a/b/c
are all relative or absolute symlinks (or symlinks to paths with symlink components), what should it do?
If you're looking for a find
predicate or a GNU -printf
%
directive that expands to a symlink-free path to the file relative to the current directory or any directory, I'm afraid there's none.
If you're on Linux, you can get the absolute path of those files with:
find -L foo -type f -exec readlink -f {} \;
As you found out, there exists at least one realpath
command which accepts more than one path argument which in combination with the standard -exec cmd {} +
syntax is going to be a lot more efficient since it's running as few realpath commands as necessary:
find -L foo -type f -exec realpath {} +
find -L foo -type f -print0 | xargs -r0 realpath
might be quicker as if more than one realpath
command is needed, find
can keep on looking for more files while the first realpath
starts working which even on a single processor system might make it more efficient.
-print0
and xargs -r0
are not standard, come from GNU but are found in a number of other implementations like most modern BSDs.
Zsh has builtin support for it:
print -rl foo/***/*(-.:A)
If you don't care about the sorting order, you can disable sorting and make it a bit more efficient with:
print -rl foo/***/*(-.oN:A)
If you want to convert those to relative paths to the current directory, you could have a look at that SO question.
If you know that all those files have an absolute canonical path (whose none of the components are symlinks) inside the current directory, you can simplify it to (still with zsh
):
files=(foo/***/*(-.:A))
print -rl -- ${files#$PWD/}
Though short and convenient, and works whatever character filenames contain, I doubt it would faster than find
+ realpath
.
With the Debian realpath
and GNU tools, you can do:
cd -P .
find -L foo -type f -exec realpath -z {} + |
gawk -v p="$PWD" -v l="${#PWD}" -v RS='\0' -vORS='\0' '
substr($0, 1, l+1) == p "/" {$0 = substr($0, l+2)}; 1' |
xargs -r0 whatever you want to do with them
As I realise now, there's now a realpath
in recent versions of GNU coreutils, which has the exact feature you're looking for, so it's just a matter of
find -L foo -type f -print0 |
xargs -r0 realpath -z --relative-base . |
xargs -r0 whatever you want to do with them
(use --relative-to .
instead of --relative-base .
if you want relative paths even for files whose symlink free path doesn't reside below the current working directory).
Best Answer
I'd strongly suggest not to use
find -L
for the task (see below for explanation). Here are some other ways to do this:If you want to use a "pure
find
" method, it should rather look like this:(
xtype
is a test performed on a dereferenced link) This may not be available in all versions offind
, though. But there are other options as well:You can also exec
test -e
from within thefind
command:Even some
grep
trick could be better (i.e., safer) thanfind -L
, but not exactly such as presented in the question (which greps in entire output lines, including filenames):The
find -L
trick quoted by solo from commandlinefu looks nice and hacky, but it has one very dangerous pitfall: All the symlinks are followed. Consider directory with the contents presented below:If you run
find -L . -type l
in that directory, all/usr/share/
would be searched as well (and that can take really long)1. For afind
command that is "immune to outgoing links", don't use-L
.1 This may look like a minor inconvenience (the command will "just" take long to traverse all
/usr/share
) – but can have more severe consequences. For instance, consider chroot environments: They can exist in some subdirectory of the main filesystem and contain symlinks to absolute locations. Those links could seem to be broken for the "outside" system, because they only point to proper places once you've entered the chroot. I also recall that some bootloader used symlinks under/boot
that only made sense in an initial boot phase, when the boot partition was mounted as/
.So if you use a
find -L
command to find and then delete broken symlinks from some harmless-looking directory, you might even break your system...