Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

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?

share|improve this question

3 Answers 3

up vote 5 down vote accepted

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
}
share|improve this answer
    
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. –  quack quixote Jun 13 '10 at 1:20
    
otherwise, nice little one-liner. much appreciated. –  quack quixote Jun 13 '10 at 1:20
    
@quack: Thanks for pointing that out. I missed it when I proofread my answer. –  Dennis Williamson Jun 13 '10 at 4:15

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
share|improve this answer

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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