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" }
Running the Script
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`
}
Then, from the command line, run this command to reload .bashrc:
- Code: Select all
source ~/.bashrc
Understanding It All
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
With the filename, this function needs either a relative path, or a fully qualified path. Given a non-link file (including a directory name), it will simply print a "ls- ld" listing of it.
To print out the final target of any link, if any, try:
- Code: Select all
resolvelink /path/linkfile
On a normal file, resolvelink will simply print that filename. For links it will print the filename of the final target. If a fully qualified path is given, then a fully qualified path is returned with the link; if a relative path (or no path) is given, then a relative path (or no path) is returned with the link. On error, it will give an error message and print the final file it found, if any. Given a non-link file (including a directory name), it will simply print the filename given.
To print out more info, consider the tell() function, meaning "tell me info on this file":
- Code: Select all
tell linkfile
The function tell() is where this started for me. This function will print out the result of type (a bash shell builtin) and which, and, if which succeeds, it gives tracklink.awk the results of which and prints the link info. Since which is used, the file given needs to be in the system PATH, but no path needs to be given with the filename.
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}')
On most Linux Mint systems, this will print three trackings. If you get a few "no listing" errors, try changing $8 to $9 (or $7).
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
You can open test.out in your favorite editor or use less:
- Code: Select all
less test.out
less uses PageUp/PageDown/Home/End/UpArrow/DownArrow, among others, for navigation. To quit, press q.
Have fun!




