Archive

Archive for the ‘bash’ Category

Bashed!: Getting the function return as an array.

November 14th, 2009 2 comments
I want to:

You want to get an array back from a function (which exposes your underlying evil intentions)…

How the hell?
get_haxlog_types() {
    declare -a types
    types[0]="TRACE"
    types[1]="DEBUG"
    types[2]="INFO"
    types[3]="WARN"
    types[4]="ERROR"
    echo "${types[@]}"
}
logTypes=( `get_haxlog_types` )
echo "${logTypes[@]}"
echo "${logTypes[0]}"
echo "${logTypes[2]}"
WhaaaaaaAAAAA?

The above is a slightly modified function from one of my open source bash projects. To see this work, you will need to put the above into a file, and then make that file executable (like chmod 777 [filename] or some such).

There are two parts to this solution – how the function returns and how you assign output of the function to a variable. Let’s start with how the function returns. I’m going to tell you something that will blow your mind here, so try not to utter primal screams and throw various innocent electronics devices around when the light shines. Ready? The return of the function has nothing to do with this.

Yeah – do you see a “return” statement anywhere in the code snippit above? No? That’s because we are not “returning” our array values – aaaand because functions return a numerical value specifying the success or lack thereof of the function execution. In simple terms, “0” = good. Anything else = “AAAAaaaaahhhhhrrrrg!” The secret sauce of the solution to your problem is something that you are probably already doing if you’ve spent any time working with shell scripting. It’s all in the “echo”.

In a function, as in a shell script, the echo command is your best friend. Most of the time you are just echoing the value of a variable in your script in an attempt to figure out at just what point your code decided to hate you. In this case you are using the fact that the backtick command substitution will take the function name string (get_haxlog_types) and run it as a script command, which then echos the contents of the array you created within that function.

The command substitution doesn’t finish the job, however. To see why, take away the subshell parenthesis and see what you’re left with. A little setup in the example script first:

logTypes=`get_haxlog_types` 
echo "${logTypes}"
echo ${logTypes}
echo "@ = ${logTypes[@]}"
echo "0 = ${logTypes[0]}"
echo "2 = ${logTypes[2]}"

… and then run the script. I’ve called my “test” because I have no imagination:

[root@vps tmp]# ./test
TRACE DEBUG INFO WARN ERROR
TRACE DEBUG INFO WARN ERROR
@ = TRACE DEBUG INFO WARN ERROR
0 = TRACE DEBUG INFO WARN ERROR
2 = 
[root@vps tmp]#

What you’re left with is the array getting echoed in the function (echo "${types[@]}") as one long string and being assigned to the logTypes variable, which is not what we are looking for. To get the echoed string assigned out as an array variable, we use ever useful subshell to get the string filtered through the IFS (internal field separator), which then breaks the string up by the spaces and assigns the now line break (LF) delimited string to the logTypes variable as an array, rather than a single string. Exempli gratia:

The set up, now with subshell secret sauce…

logTypes=(`get_haxlog_types`) 
echo "${logTypes}"
echo ${logTypes}
echo "@ = ${logTypes[@]}"
echo "0 = ${logTypes[0]}"
echo "2 = ${logTypes[2]}"

And the results we’ve wanted:

[root@vps tmp]# ./test
TRACE
TRACE
@ = TRACE DEBUG INFO WARN ERROR
0 = TRACE
2 = INFO
[root@vps tmp]#

That’s all there is to it.

Categories: bash Tags: , , , ,

Bashed!: Assigning a directory listing to a variable

November 8th, 2009 No comments
I want to:

You need to get a directory listing into a variable so you can iterate through it and do things (horrible, unspeakable things) to the child files and directories.

How the hell?
DIRECTORY="/root/example"
dirListing=(`ls ${DIRECTORY}`)
WhaaaaaaAAAAA?

We will go over this from the inside out. First, the variable expansion ${DIRECTORY} translates to whatever you have set the variable “DIRECTORY” to. In our example, we’ve set “DIRECTORY” to an example directory I created for this little tutorial:

[root@vps ~]# mkdir example
[root@vps ~]# touch example/one
[root@vps ~]# touch example/two
[root@vps ~]# touch example/three
[root@vps ~]# touch example/four

Next, the backticks (or backquotes), eg `…`, tell the processor to treat everything in them as command line text. This is called a command substitution. This means without the backticks, ls ${DIRECTORY} is a string. Inside the backticks, it is a command.

Once the command is run, the results are run through the IFS filter (internal field separator). Since one of the characters assigned by default to the IFS variable (${IFS}) is a space, the spaces in our directory listing become delimiters, and the resulting string is broken up.

So far, we’ve got the command:

dirListing=`ls ${DIRECTORY}`

… and that seems to treat us well. The directory listing is assigned to the dirListing variable, so you would see something like this:

[root@vps /]# DIRECTORY=/tmp
[root@vps /]# dirListing=`ls ${DIRECTORY}`
[root@vps /]# echo ${dirListing}
four one three two

Notice that the string looks still to be all one line? It isn’t really – try using double quotes and you’ll see how the string is now broken up by the IFS:

[root@vps ~]# echo "${dirListing}"
four
one
three
two
[root@vps ~]#

So why aren’t we happy yet? Well, if you knew a little bit more about variable expansion in bash, you’d then realize that with the echo command, we are technically asking the shell to print out either ${dirListing} or ${dirListing[0]}. The former means the entire string assigned to the dirListing variable, and the latter means the first element of the dirListing array. If we try echoing the first element of an array however, we get this:

[root@vps ~]# echo "${dirListing[0]}"
four
one
three
two
[root@vps ~]#

The string was broken up, but unless you use double quotes to print it out, you don’t see the LF line breaks, and the entire output was assigned as a long string again – not as the array we are looking for. To get an array, we need to use a subshell. Now the subshell does quite a bit, but we are only interested in one particular effect is has on the output of delimited strings when that output is assigned to a variable. Yes, that is a VERY specific use case of subshell parenthesis! Try this:

[root@vps ~]# (`ls ${DIRECTORY}`)
-bash: four: command not found
[root@vps ~]#

In my case, “four” is the name of the first file in my “/root/tmp” directory. The subshell tried to execute it, like it is supposed to. If that file had been executable, it would have been run, and then the next file name would have been executed. We aren’t looking for execution though – we want the broken up string assigned out as an array. Let’s run the same command and this time assign the output to our variable…

[root@vps ~]# DIRECTORY="/tmp"
[root@vps ~]# dirListing=(`ls ${DIRECTORY}`)
[root@vps ~]# echo $dirListing
four
[root@vps ~]# echo ${dirListing}
four
[root@vps ~]#

Remember that using the ${…} form of variable designation actually outputs either the value of the variable or the first element of the array (eg: ${…[0]})? Looks like the we got our array output! Let’s check the next few elements to make sure:

[root@vps ~]# echo ${dirListing[1]}
one
[root@vps ~]# echo ${dirListing[2]}
three
[root@vps ~]# echo ${dirListing[3]}
two
[root@vps ~]#

See? That wasn’t so hard, was it?

Categories: bash Tags: , , , ,