Citrix Blogs

A Practical Example of Using Cloud Storage with XenServer in the Citrix Demo Center: Chapter 4

In this chapter, I’ll discuss Bash functions, how to create them and how to use them.

Bash functions: Functions are a good way to isolate common code that can be called from multiple other places. It’s also very useful when imported into the context of your command shell, to give you an extra set of commands. So in my case, the Object Storage library is a lot of functions, that when imported into the current shell can be used in isolation to (for example) download an Object. Of course in the build code, it’s a lot easier (and more readable) to glue a lot of these functions together to accomplish a specific task.

As an example, one of my meta-data files contains the directory name of the VM bits that need to be downloaded for a given demo. In this case a function to enumerate a directory is used, and a function to download an object is used for each of those object paths returned by the enumerator.

The three functions I use are:

The following snippet shows roughly how the SLObjectStorageDownloadFiles function can be used to download all objects in all paths specified in the file /ctx/scripts/custom/list_swift_packages.

The list_swift_packages files, for example, may contain the following lines:

democenter-files/workloads/xd7_m60_v2
democenter-files/workloads/Helper_VM_v04

The “do” loop shown below, will take each line of the file as a valid path, and download all objects contained in that path from a specific Object Storage region, into the current directory.

cat /ctx/scripts/custom/list_swift_packages | while read dname
do
if ! (
if ! SLObjectStorageDownloadFiles “$dname” $region …
then
exit 1
fi
)
then
break
fi
done

As noted in previous chapters, because a function is being called within a “do” loop, a new shell will be pushed by the current shell. Here I show that I explicitly push a new shell since this has implications for error handling. Notice how I can test the pushing of the new shell, and if non-zero break out of the “do” loop if ! (… Syntax here is not pretty, so in such cases try your best to make it clear with use of indents and comments.

Remembering from previous chapters: Object Storage has no concept of directories, although object names can have a separator that luckily defaults to a “/”. The SLObjectStorageDownloadFile and SLObjectStorageDownloadFiles functions downloads object names as a path names locally, so you will end up with a directory structure like ./democenter-files/workloads/… on the local disk.

How to create a function: Life with the Bash shell is simple! All you need do is start typing in the function from the CLI! In practice, you’ll have a separate text file that will be populated with the functions. These functions can then be imported into the context of your current shell for use by you. Or they can be imported into the context of a running script, for use in that script. Either way the syntax for importing the function is the same. If, for example, the path to your function library file is:

/ctx/scripts/common/ctxs_swift_imports

…then to import the function library, do the following:

. /ctx/scripts/common/ctxs_swift_imports

(Note the “.” followed by a space).

Dot space: The “dot space” idiom in Bash is used to run a shell script without pushing a new shell (and hence process) to run it. The script is run in the context of the current shell. In the above example, the file ctxs_swift_imports contains only function definitions. Running the file as a script would work, but the functions would be defined in a sub-shell, which would disappear after the script had finished. The “dot space” idiom is not really an “import” function as such, although it appears to work that way when working with a file that only contains function definitions. Generally, this idiom is used to isolate functions and common shell variables in a separate script file, and to run that script in the context of the current shell, thereby preserving any of the variables (including functions) that may have been declared in there.

Syntactically, the general form of a function definition is like this:

MyFirstFunction()
{
‘ Some normal bash statements here
}

…or…

function MyFirstFunction()
{
‘ Some normal bash statements here
}

The function keyword is not needed, but is there for clarity. The () should always be empty, and the body of the function should be between the {}. I won’t go into all the rules of engagement here, but sufficient to say that running a function is a bit like running a shell script in a sub shell (see below for clarification).

Example: Run the date command passing all arguments passed to the function through to the date command. Also define a local variable stat whose context is only within the function. Function can return a value for the callers $? special variable.

function MyFirstFunction()
{
local stat
if date ${*}
then
stat=0
else
stat=1
fi
return $stat
}

Once the function has been defined in the context of the current shell or script (by literally typing the above in the CLI), calling it is easy:

MyFirstFunction
Tue Mar  6 12:20:41 CST 2018

MyFirstFunction +’%D’
03/06/18

if ! MyFirstFunction
then
‘ Some error
fi
Tue Mar  6 12:20:41 CST 2018

mydate=$(MyFirstFunction)
echo $mydate
Tue Mar  6 12:20:41 CST 2018

All the rules of redirection and running the function in the background can apply as well.

If running a function is so much like running a script, why don’t I place the functional parts in a separate script? I could place each functional part in its own script file in a directory, and add its path to the $PATH variable. There are differences:

Of course there comes a time where you have to have a script that “glues” all these functions together in some meaningful manner.

Exit mobile version