Tuesday, April 22, 2008

Functions Vs. Subroutines In Perl And Bash - Palindromes Revisited

Hey There,

As the clever title of today's post suggests, this is the follow up to our post on scripting out a way to determine if a string is a palindrome using Perl.

One of the main things to note (which isn't the only difference, of course) is the way in which the Bash shell and Perl deal differently with "routines." In our Perl script, we did the meat of our recursive work inside a "subroutine." In today's Bash script, that same work is handled inside a "function."

Functionally speaking, both a Perl "subroutine" (Identified by the "sub" declaration) and a Bash "function" (Identified by the "function" declaration) do the same things. They allow you to create a block of code for use within in your script. Naturally, functions and/or subroutines, are a great help if you find that your script consists of typing the same set of instructions more than once (if you have to type the same block of code more than twice, they're even better ;)

Technically speaking, there are some major differences between the two that should be noted. These should be platform independent and true for both Bash and Perl on Linux or Unix.

In Perl, since all of the information in the script is processed before the script is run, you can include your "subroutine" anywhere in the script, even if it's after the line on which you call it. It's common practice to put subroutines at the bottom of the script, but you can put them anywhere within the script that you like, if you're so inclined.

In Bash, since the script is parsed from top-to-bottom in a left-to-right fashion, "functions" absolutely need to appear in the script before the line on which they are called. If you put a function definition at the bottom of your Bash shell script and call that function 15 lines prior, you'll receive something along the order of a "command not found" error.

In Perl, when you pass arguments to a subroutine, you have a few different ways you can do it (basically, deprecated methods still work), but the most elegant way to pass simple variables to your Perl subroutine is to include them within parentheses, separated by commas, like so:

MySub( $var1, $var2, $var3);

The subroutine that would accept, and process, all of those arguments may look something like this:

sub MySub( $var1, $var2, $var3) {
print "$var1 $var2 $var3\n";
}


In Bash, when you pass arguments to a function, you can just pass them as if they were arguments to a regular command, like this:

MyFunction $var1 $var2 $var3

The function that would accept, and process, these arguments may look something like this:

function MyFunction {
echo "$var1 $var2 $var3"
}


Interesting side note: In Bash, if you declare a function, you can leave out the reserved word "function" if you want to, but this requires that you use parentheses, like with Perl. So you could write your function like this:

MyFunction () {
echo "$var1 $var2 $var3"
}


However, if you opt to use the reserved word "function" when declaring your function (like we do), the parentheses following the function name are optional :)

You may have noted that we haven't mentioned anything about "scope" with regards to variables and functions or subroutines. This is on purpose. We'll inevitably tackle that in a later post, but it's beyond the "scope" of this one (that was a truly painful pun for all of us... but utterly unavoidable ;)

On that note (for the palindromes), in our Bash script we've taken advantage of that "lack of scope" and simply set the "$status" variable globally within the palindrome function. In our Perl script, we used "return" to pass back either a 0 or a 1 to the calling procedure. The major difference here is that our Perl script was returning a code back to a variable which was defining its value based on the outcome of the subroutine process, so:

$status = palindrome( @string, $chars, $count );

was giving the "$status" variable a value of 0 or 1, depending on what the "palindrome" subroutine returned. In Bash, we jumped right over the middle man and just set the global "$status" variable from within the function (This isn't generally recommended, but okay for our purposes here). Assuming there are no bugs in your version of Bash that mangle the scope of variables within functions, the only risk you're taking, by defining a global variable from within a function, is that you'll forget about it and redefine it outside the function, thereby overwriting the value. But, again, that's for another day, as I can feel an essay coming on ;)

Hope you enjoy the Bash version of our simple palindrome script and that it helps you see the correlation between not only functions and subroutines, but some other aspects of coding that are either common, or unique, to both Bash and Perl.

Best wishes,

Cheers,


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/bin/bash

#
# bashpal.sh
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

function palindrome {
if [ "${string:${count}:1}" == "${string:${chars}-${count}-1:1}" ]
then
let count=$count+1;
palindrome "$string" "$chars" "$count"
fi
if [ $count -eq $chars ]
then
status=1;
else
status=0;
fi
}

printf "Enter A String: "
read string

chars=${#string}
count=0
palindrome "$string" "$chars" "$count"

if [ $status -eq 1 ]
then
printf "\n That String Is A Palindrome.\n\n"
else
printf "\n That String Is Not A Palindrome.\n\n"
fi


, Mike