Showing posts with label script. Show all posts
Showing posts with label script. Show all posts

Tuesday, August 10, 2010

LINUX: Sub-shells

I mentioned sub-shells before and the way the affect aliases, but I did not talk much about the use of sub-shells themselves.  A sub-shell can be quite useful in scripting, either to isolate certain environments, or to run sets of commands in parallel.

Using them are simple:  Encapsulate the commands you want in a sub-shell within parentheses.
#!/bin/bash

( echo "Start sub-shell"
export ENVVAR="This is my sub-shell variable" )

echo $ENVVAR
The first thing you will notice when running this script is that the environment variable we set within the sub-shell does not propagate back to the script. The echo of $ENVVAR will be blank. Sub-shells are unidirectional; they use one-way passing of the environment, so it can inherit your current environment, but will not give back anything. This is very useful if you need to do multiple things that all require separate environments.
#!/bin/bash

( echo "Start sub-shell 1"
echo $ENVVAR
export ENVVAR="1"
echo $ENVVAR )

( echo "Start sub-shell 2"
echo $ENVVAR
export ENVVAR="2"
echo $ENVVAR )

( echo "Start sub-shell 3"
echo $ENVVAR
export ENVVAR="3"
echo $ENVVAR )
From the output, you will see that the ENVVAR set in one sub-shell does not carry over to the other. Each environment is isolated. This technique can be used if you are doing multiple code builds in parallel, each with their own development environment, variables, compilers, etc. As it stands, the above executes each sub-shell serially, running one then the next. How about running in parallel? Add an ampersand "&" at the end of each sub-shell to indicate backgrounding the whole block.
#!/bin/bash

( echo "Start sub-shell 1"
echo $ENVVAR
sleep 2
export ENVVAR="1"
echo $ENVVAR ) &

( echo "Start sub-shell 2"
echo $ENVVAR
sleep 2
export ENVVAR="2"
echo $ENVVAR ) &

( echo "Start sub-shell 3"
echo $ENVVAR
sleep 2
export ENVVAR="3"
echo $ENVVAR ) &
I put sleep 2 in each block to slow it down so you can see the effects of backgrounding. What you will see is that it will execute all the echo commands to print "Start..." at the same time, then waits 2 seconds, then the numbers get printed later. The three sub-shells are running in parallel! Did you catch the problem in the above though? You were returned to the prompt prior to the script finishing. You received your prompt and then all the sudden, the numbers got printed out after. In order to tell the script to wait till all the sub-shells are finished before exiting the script, simply type "wait" at the end of the script. The final script will look like:
#!/bin/bash

( echo "Start sub-shell 1"
echo $ENVVAR
export ENVVAR="1"
echo $ENVVAR ) &

( echo "Start sub-shell 2"
echo $ENVVAR
sleep 2
export ENVVAR="2"
echo $ENVVAR ) &

( echo "Start sub-shell 3"
echo $ENVVAR
sleep 2
export ENVVAR="3"
echo $ENVVAR ) &

wait

Tuesday, July 27, 2010

LINUX: Scripts and Alias

This one stumped me for a while; we had build scripts that kept failing, yet if you manually executed the commands from the script, everything worked fine. Eventually I discovered that it wasn't properly executing the "make" command as was setup in the alias. When we set up our build environment, we alias "make" to have a bunch of compiler flags so when you finish running the setupenv file (to set environment variables and such), all you have to do is run "make" and the alias keeps track of all the necessary build flags.

Scripts never work properly because they interpret your "make" command to be just that--"make" with no build flags. It turns out this is because scripts (non-interactive shells) (1) don't expand aliases and (2) don't bring in the current environment.

Concerning point 2 about the current environment: This is actually a good thing. Imagine if you have a special environment you set up and the script runs fine in your area, then someone else tries to run it in theirs and it fails. It would be a pain to begin debugging by comparing what is in your .bashrc file and what you did to customize your environment. A script should be self contained, able to run in any environment. So when you run it, it doesnt carry over your environment variables into its non-interactive subshell that it runs in.

We are more concerned with point 1 though: A shell script, at least on all the Fedora and RedHat servers I've worked on, do not expand the aliases by default. Before I came across the good solution on the internet, I hacked together my own solution of parsing the expanded command out of the alias output:
`alias | grep make | cut -d\' -f2`
That works, but is not an elegant solution by any means. Recently a coworker was having trouble with a build script and I got into explaining to him he may have a problem with a non-expanding alias. I showed him my solution and then gave the caveat that there is probably a better, easier solution out there. Then I looked for one and found it (don't ask me why I didn't find it the first time when I created my hack solution). Just place the following command in the begginning of your shell script:
shopt -s expand_aliases
What this does is sets the shell option (shopt) to enable (-s) the expansion of aliases (expand_aliases). The man page says that this option is enabled by default for interactive shells, so the implication (and reality we've witnessed) is that it is disabled by default for non-interactive shells.

If you want to see this in action, try this script:
#!/bin/bash
alias testme="echo It Worked"
alias
testme
The result will print the new alias you assigned (typing alias in line 3 prints out current aliases) for testme. Notice there are no other aliases set, not even the ones that are set globally for the system. The execution of the testme command fails with "command not found" because it is not recognizing the alias.

Now try again with the shopt set:
#!/bin/bash
shopt -s expand_aliases
alias testme="echo It Worked"
alias
testme
Now it will work perfectly, showing the alias we set and echoing the "It Worked" statement.