ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices

Welcome to LinuxQuestions.org, a friendly and active Linux Community.

You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!

Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.

If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.

Having a problem logging in? Please visit this page to clear all LQ-related cookies.

Introduction to Linux - A Hands on Guide

This guide was created as an overview of the Linux Operating System, geared toward new users as an exploration tour and getting started guide, with exercises at the end of each chapter.
For more advanced trainees it can be a desktop reference, and a collection of the base knowledge needed to proceed with system and network administration. This book contains many real life examples derived from the author's experience as a Linux system and network administrator, trainer and consultant. They hope these examples will help you to get a better understanding of the Linux system and that you feel encouraged to try out things on your own.

The shell will first apply parameter expansion. It sees that the second item is a glob pattern, and will expand it to the list of all matching files. If you have files /somewhere/one and /somewhere/two, then the output will be

Code:

Argument "/somewhere/one"
Argument "/somewhere/two"

In other words, the script does not need to worry about locating the files, as the shell will do it for you.
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

I would approach the problem in a different way. I'd expect my script to take at least two parameters, the first parameter being the directory to create the links in, and all others would be the desired symlink targets, absolute or relative to current directory. You could then use tab expansion in most shells to find both the link directory, and the symlink target files.

The one issue with that approach is that both are specified as relative to current directory, whereas for the ln command, the symlink target is always relative to the symlink itself. In other words, we get/know some-path-to-A and some-path-to-B, but need to supply from-A-to-B to ln -s.

The beginning of the script is simple. If there are not enough parameters, then output usage. (Although I am using Bash, I tend to use POSIX shell idioms. Others will recommend Bash-specific replacements. If you only use Bash for scripting, go with Bash; I'm a crufty curmudgeon.)

To find where it actually leads, I use a subshell to change to that directory, then run /bin/pwd to find out its actual path, and capture it to a variable. If it fails -- say, the directory does not exist --, cd will output an error message, and we can just abort the script.

Code:

# Find out the path to link directory. If it is not a real directory, abort.
LINKPATH="$(cd "$LINKDIR" && /bin/pwd)" || exit $?

Next, we loop over all leftover parameters. This way we only need to worry about the current link within the loop:

Code:

# Loop over all other arguments. (The link directory was shifted out.)
for TARGET in "$@" ; do
# Verify the target exists.
if [ ! -e "$TARGET" ]; then
echo "$TARGET: No such file or directory." >&2
exit 1
fi

Next, we split the target into the file name part (which will be the symlink name, of course), and the actual path. For the path I use the same /bin/pwd trick as earlier, except this time I suppress the error message cd might produce by redirecting standard error to /dev/null:

Next comes the tricky bit. I construct two strings, the first of which describes the directories that need to be ascended from the link directory, and the second describes the path to then descend, to get from link directory to target:

Code:

# The list of directories needed to ascend first from link dir,
uplist="$LINKPATH"
# then to descend down to target dir.
downlist="$TARGETPATH/${TARGETFILE#/}"

Since there is no reason to ascend a directory if you'll immediately descend into it anyway, we can remove the leading common path segments from both:

If there are no slashes, then ${var#*/} evaluates to ${var} (i.e. no change!) so we need to explicitly check if there were no slashes. It is easiest to do by comparing against the unmodified value.

Next, we can construct the actual path. Start with ./ so that there will always be a trailing slash. We can remove it after the path is constructed:

Code:

# The symlink starts at the link directory.
LINK="."
# The symlink the ascends each directory up from the link directory,
LINK=".$(echo "/$uplist/" | sed -e 's|[^/]\+|..|g; s|//\+|/|g')"
# then descends down into the target directory and item.
LINK="${LINK}$downlist"
# Remove the superfluous "./" at the start of the symlink,
LINK="${LINK#./}"
# as well as any trailing slashes.
LINK="${LINK%/}"

Now $LINK is the path from the link directory to the target item. So, create the symlink:

That's it. If you run the script with some test arguments, you'll see that the symlinks are always relative; this is due to the path walking tricky part in the middle. It is also what makes the script useful compared to just using plain ln -s .

Note that the above script uses POSIX idioms for a reason: it should run using dash too, not just Bash (by changing only the first line to #!/bin/dash ). If you always have Bash at your fingerprints, you can clean up the syntax quite a bit using Bash-only features. Like I said, I'm just stuck in my ways. For now.

For debugging, I recommend you modify the second-to-last line to echo ln -s , and perhaps sprinkle some informative echo or printf lines here and there. (printf is supported by both Bash and POSIX shells; use echo only for unformatted string output.)