3

For example, if a symlink

a -> b
b -> c
c -> d

say, the symlink level of a is 3.

Then, is there any utility to get this info? And, also I want to get the expansion detail of a symlink, which will show me something like:

1. /abc/xyz is expanded to /abc/xy/z (lrwx--x--x root root)
2. /abc/xy/z is expanded to /abc/xy-1.3.2/z (lrwx--x--x root root)
3. /abc/xy-1.3.2/z is expanded to /abc/xy-1.3.2/z-4.6 (lrwx--x--x root root)
4. /abc/xy-1.3.2/z-4.6 is expanded to /storage/121/43/z_4_6 (lrwx--x--x root root)
5. /storage/121/43/z_4_6 is expanded to /media/kitty_3135/43/z_4_6 (lrwx--x--x root root)

So I can diagnostic with the symlinks. Any idea?

3 Answers 3

5

This recursive Bash function will print the link chain and count plus the diagnostic:

chain() { local link target; if [[ -z $_chain ]]; then unset _chain_count _expansion; _chain="$1"; fi; link=$(stat --printf=%N $1); while [[ $link =~ \-\> ]]; do target="${link##*\`}"; target="${target%\'}"; _chain+=" -> $target"; ((_chain_count++)); _expansion+="$_chain_count. $1 is expanded to $target $(stat --printf="(%A %U %G)" $target)"$'\n'; chain "$target"; return; done; echo "$_chain ($_chain_count)"; echo "$_expansion"; unset _chain _chain_count _expansion; }

It requires stat. For more information and a version that uses readlink instead of stat, see my answer here (the count feature would need to be added but adding the permissions and owner/group would be a little more challenging).

For this:

a
b -> a
c -> b
d -> c

The output of chain d would be:

d -> c -> b -> a (3)
1. d is expanded to c (lrwxrwxrwx username groupname)
2. c is expanded to b (lrwxrwxrwx username groupname)
3. b is expanded to a (-rw-r--r-- root root)

Here is a more readable version of the function:

chain ()
{
    local link target;
    if [[ -z $_chain ]]; then
        unset _chain_count _expansion;
        _chain="$1";
    fi;
    link=$(stat --printf=%N $1);
    while [[ $link =~ \-\> ]]; do
        target="${link##*\`}";
        target="${target%\'}";
        _chain+=" -> $target";
        ((_chain_count++));
        _expansion+="$_chain_count. $1 is expanded to $target $(stat --printf="(%A %U %G)" $target)"$'\n';
        chain "$target";
        return;
    done;
    echo "$_chain ($_chain_count)";
    echo "$_expansion";
    unset _chain _chain_count _expansion
}
3
  • seems there's a disconnect between the one-liner and exploded versions. (typo in the exploded maybe?) seems to be missing a $'\n after the $(stat bit. that wonky unindented line with only '; on it immediately follows the missing characters. Jun 13, 2010 at 1:20
  • otherwise, nice little one-liner. much appreciated. Jun 13, 2010 at 1:20
  • @quack: Thanks for pointing that out. I missed it when I proofread my answer. Jun 13, 2010 at 4:15
2

This is actually rather tricky, in general, since you could have a symlink such as:

ln -s ../symlink/xyz pqr

where 'symlink' is itself a symlink of some arbitrary length. I have a program which does the same job as realpath() but which also checks the security of all the symlinks on the way. As such, it could be used to answer your question. And some of its test scripts could be used to validate your calculations.

This is one such test:

#!/bin/ksh
#
# @(#)$Id: realpathtest.sh,v 1.4 2008/04/14 19:36:54 jleffler Exp $
#
# Create confusing path names

base=${1:-"./rpt"}

mkdir -p $base/elsewhere/confused $base/elsewhere/hidden \
         $base/other/place $base/elsewhere/private

(
cd $base
[ -h link ] || ln -s elsewhere/confused link
[ -h some ] || ln -s other/place some
)
(
cd $base/elsewhere/confused
[ -h mischief ] || ln -s ../hidden mischief
[ -h where    ] || ln -s ../private where
)
(
cd $base/other/place
[ -h dubious  ] || ln -s ../../link/mischief dubious
[ -h doubtful ] || ln -s ../../link/where doubtful
)
(
cd $base/elsewhere/private
echo "What is the real pathname for $base/some/doubtful/file?" > file
)
(
cd $base/elsewhere/hidden
[ -h file ] || ln -s name file
echo "What is the real pathname for $base/some/dubious/file?" > name
)

for name in \
    $base/some/dubious/file \
    $base/some/doubtful/file \
    $base/elsewhere/confused/mischief/file \
    $base/elsewhere/confused/where/file
do
    if realpath $name
    then cat $(realpath -s $name)
    fi
done

Contact me if you want the source for the 'realpath' or 'linkpath' program (see my profile); . The 'realpath' program is not rocket science though - it basically calls realpath() for each argument it is given. There's considerably more effort involved in analyzing the full set of links.

2

I don't really know about a utility. You can script it pretty easily. Adding some stat or ls output might get you the rest of the way.

#!/bin/sh

level=0
file=$1
while [ -h "$file" ]
do
    previous="$file"
    file=`readlink "$file"`
    level=$(($level+1))
    echo "$level". "$previous -> $file"
done

Using this on a couple of links set up in my homedir I get:

$ ./test.sh link2
1. link2 -> link1
2. link1 -> Test.java

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .