LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   Make multiples links with a parameter (Bash & Perl) (http://www.linuxquestions.org/questions/programming-9/make-multiples-links-with-a-parameter-bash-and-perl-948072/)

Trotel 06-01-2012 08:50 PM

Make multiples links with a parameter (Bash & Perl)
 
Hi, I try to make several links (all files in /my_path/*) in one location (/usr/local/bioinfo) in one step, to do that I create a script "links"

I try this, and that work

PHP Code:

#!/bin/bash
cd /usr/local/bioinfo/
echo -
"Write the path"
read path
for i in my $path
do
t=${i##*/}
ln -"$i" "${t%.*}"
done 

But I want to execute a script with a parameter (ex. -path), like this:

Code:

./links -path /my_path/*
How can I do that?

PD: Somebody can say me how do that in perl, I have to use STDIN but the rest I have no idea.

Thanks

DNL

Nominal Animal 06-01-2012 10:17 PM

Say you save the following script as args:
Code:

#!/bin/bash

for ARG in "$@" ; do
    printf 'Argument "%s"\n' "$ARG"
done

What happens when you run ./args /somewhere/*

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.)
Code:

#!/bin/bash

# Make sure locale is POSIX, so we do not choke on non-UTF8 file names in UTF-8 locales.
export LANG=C
export LC_ALL=C

# Use / as the input field separator.
IFS="/"

# Output help if not enough parameters or -h or --help specified.
if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    exec >&2
    echo ""
    echo "Usage: $0 -h | --help"
    echo "      $0 LINK-DIRECTORY TARGET(s).."
    echo ""
    echo "This script will create a symlink to each TARGET in LINK-DIRECTORY."
    echo ""
    exit 0
fi

Next, we remove the link directory from the parameter list, and shift the rest one place up.
Code:

# Pop the link directory from the parameter list.
LINKDIR="$1"
shift 1

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:
Code:

    # Filename part of target,
    TARGETFILE="$(basename "$TARGET")"

    # and full path part of target.
    if ! TARGETPATH="$(cd "$(dirname "$TARGET")" 2>/dev/null && /bin/pwd)" ; then
        echo "$TARGET: Directory does not exist." >&2
        exit 1
    fi

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:
Code:

    # Remove leading common directories from uplist and downlist.
    while [ "${uplist%%/*}" = "${downlist%%/*}" ] && [ -n "$uplist" ] && [ -n "$downlist" ]; do
        olduplist="$uplist"
        uplist="${uplist#*/}"
        [ "$olduplist" = "$uplist" ] && uplist=""

        olddownlist="$downlist"
        downlist="${downlist#*/}"
        [ "$olddownlist" = "$downlist" ] && downlist=""
    done

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:
Code:

    # Create the symlink; abort if failure.
    ln -s "$LINK" "$LINKPATH/$TARGETFILE" || exit $?
done

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.)

Hope you find this informative,

Trotel 06-04-2012 01:13 PM

I forgot to tell you that I am beginner, can you explain me in simple words

bigearsbilly 06-05-2012 06:36 PM

what is wrong with:

ln -s my_path/* /usr/local/bioinfo

Trotel 06-05-2012 09:06 PM

Quote:

Originally Posted by bigearsbilly (Post 4696376)
what is wrong with:

ln -s my_path/* /usr/local/bioinfo

The link is broken

Reuti 06-06-2012 10:30 AM

ln will create the entries you specify. To avoid broken links you can execute it directly inside the target directory:
Code:

$ mkdir demo
$ touch demo/a
$ touch demo/b
$ touch demo/c
$ mkdir target
$ cd target
$ ln -s ../demo/* .
$ ls -lh
total 0
lrwxrwxrwx 1 reuti users 9 2012-06-06 17:23 a -> ../demo/a
lrwxrwxrwx 1 reuti users 9 2012-06-06 17:23 b -> ../demo/b
lrwxrwxrwx 1 reuti users 9 2012-06-06 17:23 c -> ../demo/c



All times are GMT -5. The time now is 12:20 AM.