A bash wrapper script for setting up environment variables

This post was written by eli on January 21, 2010
Posted Under: FPGA,Linux,Software

The problem

Sometimes software packages require setting some environment variables for its proper execution. When these variables clearly have no effect on any other applications in the system, that’s fine. When they want to manipulate some sensitive variables, which other applications may depend on, that’s a whole different story.

When it’s a single executable, the problem is fairly simple. When it’s gazillions of them, all requiring the same set of environment variables, it’s not so fun.

I solved this by writing one single wrapper for all executables, and a lot of symbolic links. This wrapper sets the environment variables for the relevant application, and then runs the desired executable. The path is set to run the wrapper, rather than the executable, so this is completely transparent. In this way, the new software sees the correct environment variables but without polluting them for the entire system.

Don’t play with my library path

I’ve just installed Xilinx ISE 9.2 on my Fedora 12 Linux machine. One of things I was required to do, was to add this snippet (more or less) to my .bashrc file:

if [ -n "$LD_LIBRARY_PATH" ]
then
   LD_LIBRARY_PATH=${XILINX}/bin/${PLATFORM}:${XILINX}/X11R6/bin/lin64:/usr/X11R
6/lib:${LD_LIBRARY_PATH}
else
   LD_LIBRARY_PATH=${XILINX}/bin/${PLATFORM}:${XILINX}/X11R6/bin/lin64:/usr/X11R6/lib
fi

That means that every Linux application from now on should look in Xilinx’ libraries before attempting to go for the ones Fedora supplies. But why first? Because Xilinx seems to override some standard libraries. Which is good for its own application, but can be pretty disastrous for all others. It means, for example, that removing or upgrading ISE may cause other things in your system break.

Why Xilinx chose this approach, I can only guess. It was most likely somewhere between “we can’t get it to work otherwise” and “you’re not using the computer for anything else, are you?”

My solution was to move these problematic lines to a wrapper script for each executable. If Xilinx wants these libraries for its own executables, so be it. Don’t pollute the whole system.

Setting up the path

Xilinx wanted me to add a lot of mumbo-jumbo into the .bashrc. Most went to the wrapper script (shown below). The only thing I put in .bashrc was appending a directory to the path. Xilinx wanted me to put ${XILINX}/bin/${PLATFORM}, but I went for ${XILINX}/bin-wrappers/${PLATFORM}

So this was added to .bashrc:

if [ -n "$PATH" ]
then
   PATH=${XILINX}/bin-wrappers/${PLATFORM}:${PATH}
else
   PATH=${XILINX}/bin-wrappers/${PLATFORM}
fi
export PATH

The wrapper

Now I created a the ${XILINX}/bin-wrappers directory, a lin64 directory underneath. In lin64, the a file named xilinx-app-wrapper is executable and looks like this:

#!/bin/bash

# First setup variables
PLATFORM=lin64

if [ -n "$LD_LIBRARY_PATH" ]
then
   LD_LIBRARY_PATH=${XILINX}/bin/${PLATFORM}:${XILINX}/X11R6/bin/lin64:/usr/X11R6/lib:${LD_LIBRARY_PATH}
else
   LD_LIBRARY_PATH=${XILINX}/bin/${PLATFORM}:${XILINX}/X11R6/bin/lin64:/usr/X11R6/lib
fi
export LD_LIBRARY_PATH

if [ -n "$NPX_PLUGIN_PATH" ]
then
   NPX_PLUGIN_PATH=${XILINX}/java/${PLATFORM}/jre/plugin/i386/ns4:${NPX_PLUGIN_PATH}
else
   NPX_PLUGIN_PATH=${XILINX}/java/${PLATFORM}/jre/plugin/i386/ns4
fi
export NPX_PLUGIN_PATH

if [ -n "$LMC_HOME" ]
then
   LMC_HOME=${XILINX}/smartmodel/${PLATFORM}/installed_${PLATFORM}:${LMC_HOME}
else
   LMC_HOME=${XILINX}/smartmodel/${PLATFORM}/installed_${PLATFORM}
fi
export LMC_HOME

# Now call the real executable. Putting the double quotes around $@
# tells bash not to break arguments with white spaces, so this is completely
# transparent.

exec ${XILINX}/bin/lin64/${0##*/} "$@"

It’s pretty simple until we reach the bottom line: The script is copied from Xilinx’ own example file, as they requested to be put in .bashrc. So before getting to the bottom, it’s just plain environment setting.

Now to the last line: I chose to run the Xilinx application with the bash-builtin exec function. This makes Xilinx’ application replace the bash script, so we have one process running (and one process to kill if necessary) and the return value issue handled neatly.

This exec transparently runs the Xilinx application, which depends on the command used to call the wrapper. Details:

We have this ${XILINX}/bin/lin64/${0##*/} expression. The ${0##*/} thing means $0 with anything coming before a slash, including the slash chopped off. Since $0 contains the application’s name as it was called, ${0##*/} is the application name without the path. So the path is set absolutely, and the application’s name is taken from $0. Now we have the exact path to the corresponding Xilinx application.

So this wrapper is a single script which can wrap all Xilinx executables. All we have to do is to symlink to the wrapper with the same names as the Xilinx applications.

Finally, we have the “$@” thing. That means all arguments with which the wrapper was called. Without the double quotes, possible spaces in the arguments would break them up.

Note that this works with any array, so

a[0]="Hello there";
a[1]="One argument";
exec ./test "${a[@]}"

will send the ./test script only two arguments (double quotes not passed to application)

Symbolic links

The idea is now to create a symbolic link for each executable in the bin directory, all pointing at xilinx-app-wrapper. This makes sense, since this script detects by which command it was called, and will exec the correct Xilinx application in turn.

The only problem is that Xilinx’ bin directory also includes several library files, which shouldn’t be executable. To overcome this I wrote a small script, which I used to create the symbolic links (and removed it afterwards):

#!/bin/bash
TARGETDIR=${XILINX}/bin-wrappers/lin64
for i in * ;
  do if file $i | grep -iq executable ; then
    ( cd $TARGETDIR && ln -s xilinx-app-wrapper $i ; )
  fi ;
done

I ran the script from ${XILINX}/bin/lin64, and its principle is simple: The “file” application is called on each file in that directory. If the word “executable” appears in the definition, it earns a symbolic link in the bin-wrappers directory (to the script, of course, and not to the Xilinx application).

So the result of this is:

$ cd ${XILINX}/bin-wrappers/lin64
$ ls -l

[...skipped a lot of lines...]

lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xilgrep -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xilhelp -> xilinx-app-wrapper*
-rwxr-xr-x. 1 root root 925 2010-01-21 00:04 xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xilinxd -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xilperl -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xinfo -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 _xinfo -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xinfoenv -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xlicmgr -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xplorer.pl -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xplorer.tcl -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xpower -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xpwr -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xreport -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 XSLTProcess -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xst -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xtclsh -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xusbdfwu -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xusb_emb -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xusb_xlp -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xusb_xpr -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 xusb_xup -> xilinx-app-wrapper*
lrwxrwxrwx. 1 root root  18 2010-01-20 23:53 zip -> xilinx-app-wrapper*

Note that among all symlinks, we have xilinx-app-wrapper itself, which is the only thing that actually runs in this directory, and hence the only thing which needs changing when a Xilinx-global change in the environment is necessary.

That’s it. At this point everything ticked like a clockwork.

Barely relevant stuff

Since I didn’t reach the solution above right away, I tried a few other things. It’s a shame to throw them away just like that.

First, let’s see the “test” script mentioned above:

#!/bin/bash
while [ -n "$1" ]
do
  echo Argument: $1
  shift
done

It’s a simple script which shows which arguments were given to it by scanning them one by one. If an argument was broken because of spaces, here’s how I saw it.

And now an alternative (and much more cumbersome) way pass arguments transparently:

args="";

while [ -n "$1" ]; do
  # Append the new argument within quotes, where possible existing
  # double-quotes are converted to \"
  args+="\"${1/\"/\\\"}\""
  shift
  [ -n "$1" ] && args+=" ";
done

bash <<END
./test $args
END

The trick about this script is to create an empty variable $args, and append each incoming argument surrounded by double quotes and a white space. If I wanted to make this simple, I would go

args+="\"$1\" "

somewhere in the middle, but hey, I don’t want a white space after the last argument. Besides, what happens if the argument itself contains a double quote? Solution: Replace each double quote (“) with an escaped one (\”). That’s what the terrible expression in the curly brackets stand for. It’s basically ${variable/search-pattern/replace-with} plus the fact that both double quotes and backslashes have to be escaped with a backslash. Looks a bit like Perl on a bad day.

And except for begin horrible, it has another major disadvantage: It creates another process. I couldn’t make an exec call by this method. So I feed a bash shell with the data through standard input, which isn’t very cute. But if one argument is END, it still works, by the way. So if each argument needs some manipulation, and can’t be passed through with “$@”, the latter method will do the job.

Reader Comments

This helped me a lot to get ISE working nicely with my system. Thank you very much!

I’m using Xilinx ISE 10.1 32bit on Ubuntu 10.04
only some minor changes to paths were necessary.

#1 
Written By Xilinx User on June 21st, 2010 @ 13:13

Thanks, great help for getting around some problems caused by proprietary software requiring LD_LIBRARY_PATH to be set.

#2 
Written By Mirjam on February 21st, 2011 @ 17:08

Add a Comment

required, use real name
required, will not be published
optional, your blog address