The Script
Code: Select all
# tracklink.awk - SJB - T1111.22 - F1201.20
# given a file, see if it is a link and, if so, track it until the final
# target file is found; handles relative links and resolves them so they
# don't include /dir/../ notation
# Usage:
# awk -f tracklink.awk linkfile ...
BEGIN {
if ( ARGC < 2 )
{
ErrOut("Usage:")
ErrOut(" awk -f tracklink.awk linkfile ...")
exit 1
}
argc = 1
while ( argc < ARGC )
{
if ( argc > 1 ) # print a blank line between
printf "\n" # trackings
linkfile = ARGV[argc] # potential link file
ARGV[argc++] = "" # take file off command line list
delete listl # reset; "listl" checks for
listlnum = 0 # circular links
path = ""
listl[linkfile] = ++listlnum # add to list
lsl = GetLsl(linkfile) # get "ls -ld" listing
PrintLsl(lsl, linkfile) # print "ls -ld" listing
while ( substr(lsl, 1, 1) == "l" ) # while link file
{
numA = split(lsl, A)
newlinkfile = A[numA] # get last field in listing
# if newlinkfile does not begin with "/" then it is
# relative, so add path from previous linkfile
if ( substr(newlinkfile, 1, 1) != "/" )
{
ndx = PosLastOccurCh("/", linkfile)
newpath = substr(linkfile, 1, ndx)
if ( newpath )
path = newpath
newlinkfile = path newlinkfile
gsub("/[^/]+/[.][.]/", "/", newlinkfile)
} # replace /dir/../ with /
linkfile = newlinkfile
if ( linkfile in listl )
{
ErrOut("tracklink.awk: circular links")
break
}
listl[linkfile] = ++listlnum # same add,
lsl = GetLsl(linkfile) # get and
PrintLsl(lsl, linkfile) # print as above
}
}
}
# returns "ls -ld" listing; -d option needed in case target is a directory
function GetLsl(linkfile, cmd, lsl)
{
cmd = "ls -ld " linkfile " 2> /dev/null"
cmd | getline lsl
close(cmd)
return lsl
}
# prints the "ls -ld" listing or, if empty, prints error msg with linkfile name
function PrintLsl(lsl, linkfile)
{
if ( lsl )
print lsl
else
ErrOut(linkfile ": no listing")
}
# returns the position of the last occurrence of char in string or zero if char
# is not present
function PosLastOccurCh(char, string, ndx, finalndx)
{
while ( ndx = index(string, char) )
{
finalndx += ndx
string = substr(string, ndx+1)
}
return finalndx
}
# prints the given string to stderr
function ErrOut(s) { print s > "/dev/stderr" }
You can run it like this:
awk -f tracklink.awk /path/linkfile
It works fine with both gawk and mawk. Because it uses a call to a system command, there is no guarantee that this will work on a non-POSIX system (like Windows without cygwin).
Making it Easy
Place tracklink.awk into a directory called ~/awk (create ~/awk if it does not exist). Then add the following three functions to ~/.bashrc (create ~/.bashrc if it does not exist):
Code: Select all
# tracks given file(s) if it is a link; must give its full path
function tracklink
{
/usr/bin/awk -f ~/awk/tracklink.awk $@
}
# resolves a single link (or any file) to its final target
function resolvelink
{
/usr/bin/awk -f ~/awk/tracklink.awk $1 | /usr/bin/awk 'END{print A[split($0,A)]}'
}
# prints "type $1" and "which $1" and tracks $1 if it is a link
function tell
{
type $1
/usr/bin/which $1 && /usr/bin/awk -f ~/awk/tracklink.awk `/usr/bin/which $1`
}
Code: Select all
source ~/.bashrc
Since the awk file is well documented, I won't explain it line by line. I will mention that circular links are handled and other broken links--those without targets--give a "no listing" message.
The tracklink() function will run on multiple files; the resolvelink() and tell() functions run on only one file at a time.
The tracklink() function in .bashrc allows you to run:
Code: Select all
tracklink /path/linkfile
To print out the final target of any link, if any, try:
Code: Select all
resolvelink /path/linkfile
To print out more info, consider the tell() function, meaning "tell me info on this file":
Code: Select all
tell linkfile
Testing It
Try entering this in a terminal:
Code: Select all
awk -f ~/awk/tracklink.awk $(ls -l /usr/bin/* | grep xul | awk '/^l/{print $8}')
I need to warn that this next test will normally produce a lot of output. That is why I output it to the test.out file. On my system, test.out is 47k and 879 lines long. (Those are just some of the links that exist on your system.) Again, you may need to change $8 to $9 (or $7).
Code: Select all
ls -l /usr/bin/* | awk '/^l/{print $8}' | xargs awk -f ~/awk/tracklink.awk > test.out
Code: Select all
less test.out
Have fun!