1432 lines
24 KiB
HTML
1432 lines
24 KiB
HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
|
<HTML
|
|
><HEAD
|
|
><TITLE
|
|
>Complex Functions and Function Complexities</TITLE
|
|
><META
|
|
NAME="GENERATOR"
|
|
CONTENT="Modular DocBook HTML Stylesheet Version 1.7"><LINK
|
|
REL="HOME"
|
|
TITLE="Advanced Bash-Scripting Guide"
|
|
HREF="index.html"><LINK
|
|
REL="UP"
|
|
TITLE="Functions"
|
|
HREF="functions.html"><LINK
|
|
REL="PREVIOUS"
|
|
TITLE="Functions"
|
|
HREF="functions.html"><LINK
|
|
REL="NEXT"
|
|
TITLE="Local Variables"
|
|
HREF="localvar.html"></HEAD
|
|
><BODY
|
|
CLASS="SECT1"
|
|
BGCOLOR="#FFFFFF"
|
|
TEXT="#000000"
|
|
LINK="#0000FF"
|
|
VLINK="#840084"
|
|
ALINK="#0000FF"
|
|
><DIV
|
|
CLASS="NAVHEADER"
|
|
><TABLE
|
|
SUMMARY="Header navigation table"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
CELLPADDING="0"
|
|
CELLSPACING="0"
|
|
><TR
|
|
><TH
|
|
COLSPAN="3"
|
|
ALIGN="center"
|
|
>Advanced Bash-Scripting Guide: </TH
|
|
></TR
|
|
><TR
|
|
><TD
|
|
WIDTH="10%"
|
|
ALIGN="left"
|
|
VALIGN="bottom"
|
|
><A
|
|
HREF="functions.html"
|
|
ACCESSKEY="P"
|
|
>Prev</A
|
|
></TD
|
|
><TD
|
|
WIDTH="80%"
|
|
ALIGN="center"
|
|
VALIGN="bottom"
|
|
>Chapter 24. Functions</TD
|
|
><TD
|
|
WIDTH="10%"
|
|
ALIGN="right"
|
|
VALIGN="bottom"
|
|
><A
|
|
HREF="localvar.html"
|
|
ACCESSKEY="N"
|
|
>Next</A
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><HR
|
|
ALIGN="LEFT"
|
|
WIDTH="100%"></DIV
|
|
><DIV
|
|
CLASS="SECT1"
|
|
><H1
|
|
CLASS="SECT1"
|
|
><A
|
|
NAME="COMPLEXFUNCT"
|
|
></A
|
|
>24.1. Complex Functions and Function Complexities</H1
|
|
><P
|
|
>Functions may process arguments passed to them and return
|
|
an <A
|
|
HREF="exit-status.html#EXITSTATUSREF"
|
|
>exit status</A
|
|
> to the script
|
|
for further processing.</P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>function_name $arg1 $arg2</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><P
|
|
><A
|
|
NAME="PASSEDARGS"
|
|
></A
|
|
></P
|
|
><P
|
|
>The function refers to the passed arguments by position (as if they were
|
|
<A
|
|
HREF="internalvariables.html#POSPARAMREF"
|
|
>positional parameters</A
|
|
>),
|
|
that is, <TT
|
|
CLASS="VARNAME"
|
|
>$1</TT
|
|
>, <TT
|
|
CLASS="VARNAME"
|
|
>$2</TT
|
|
>, and
|
|
so forth.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="EX60"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-2. Function Taking Parameters</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# Functions and parameters
|
|
|
|
DEFAULT=default # Default param value.
|
|
|
|
func2 () {
|
|
if [ -z "$1" ] # Is parameter #1 zero length?
|
|
then
|
|
echo "-Parameter #1 is zero length.-" # Or no parameter passed.
|
|
else
|
|
echo "-Parameter #1 is \"$1\".-"
|
|
fi
|
|
|
|
variable=${1-$DEFAULT} # What does
|
|
echo "variable = $variable" #+ parameter substitution show?
|
|
# ---------------------------
|
|
# It distinguishes between
|
|
#+ no param and a null param.
|
|
|
|
if [ "$2" ]
|
|
then
|
|
echo "-Parameter #2 is \"$2\".-"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
echo
|
|
|
|
echo "Nothing passed."
|
|
func2 # Called with no params
|
|
echo
|
|
|
|
|
|
echo "Zero-length parameter passed."
|
|
func2 "" # Called with zero-length param
|
|
echo
|
|
|
|
echo "Null parameter passed."
|
|
func2 "$uninitialized_param" # Called with uninitialized param
|
|
echo
|
|
|
|
echo "One parameter passed."
|
|
func2 first # Called with one param
|
|
echo
|
|
|
|
echo "Two parameters passed."
|
|
func2 first second # Called with two params
|
|
echo
|
|
|
|
echo "\"\" \"second\" passed."
|
|
func2 "" second # Called with zero-length first parameter
|
|
echo # and ASCII string as a second one.
|
|
|
|
exit 0</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
><A
|
|
NAME="FSHIFTREF"
|
|
></A
|
|
></P
|
|
><DIV
|
|
CLASS="IMPORTANT"
|
|
><P
|
|
></P
|
|
><TABLE
|
|
CLASS="IMPORTANT"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="25"
|
|
ALIGN="CENTER"
|
|
VALIGN="TOP"
|
|
><IMG
|
|
SRC="../images/important.gif"
|
|
HSPACE="5"
|
|
ALT="Important"></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
><P
|
|
>The <A
|
|
HREF="othertypesv.html#SHIFTREF"
|
|
>shift</A
|
|
>
|
|
command works on arguments passed to functions (see <A
|
|
HREF="assortedtips.html#MULTIPLICATION"
|
|
>Example 36-18</A
|
|
>).</P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>But, what about command-line arguments passed to the script?
|
|
Does a function see them? Well, let's clear up the confusion.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="FUNCCMDLINEARG"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-3. Functions and command-line args passed to the script</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# func-cmdlinearg.sh
|
|
# Call this script with a command-line argument,
|
|
#+ something like $0 arg1.
|
|
|
|
|
|
func ()
|
|
|
|
{
|
|
echo "$1" # Echoes first arg passed to the function.
|
|
} # Does a command-line arg qualify?
|
|
|
|
echo "First call to function: no arg passed."
|
|
echo "See if command-line arg is seen."
|
|
func
|
|
# No! Command-line arg not seen.
|
|
|
|
echo "============================================================"
|
|
echo
|
|
echo "Second call to function: command-line arg passed explicitly."
|
|
func $1
|
|
# Now it's seen!
|
|
|
|
exit 0</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>In contrast to certain other programming languages,
|
|
shell scripts normally pass only value parameters to
|
|
functions. Variable names (which are actually
|
|
<I
|
|
CLASS="FIRSTTERM"
|
|
>pointers</I
|
|
>), if
|
|
passed as parameters to functions, will be treated as string
|
|
literals. <EM
|
|
>Functions interpret their arguments
|
|
literally.</EM
|
|
></P
|
|
><P
|
|
><A
|
|
NAME="FUNCPOINTERS"
|
|
></A
|
|
></P
|
|
><P
|
|
><A
|
|
HREF="ivr.html#IVRREF"
|
|
>Indirect variable
|
|
references</A
|
|
> (see <A
|
|
HREF="bashver2.html#EX78"
|
|
>Example 37-2</A
|
|
>) provide a clumsy
|
|
sort of mechanism for passing variable pointers to
|
|
functions.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="INDFUNC"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-4. Passing an indirect reference to a function</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# ind-func.sh: Passing an indirect reference to a function.
|
|
|
|
echo_var ()
|
|
{
|
|
echo "$1"
|
|
}
|
|
|
|
message=Hello
|
|
Hello=Goodbye
|
|
|
|
echo_var "$message" # Hello
|
|
# Now, let's pass an indirect reference to the function.
|
|
echo_var "${!message}" # Goodbye
|
|
|
|
echo "-------------"
|
|
|
|
# What happens if we change the contents of "hello" variable?
|
|
Hello="Hello, again!"
|
|
echo_var "$message" # Hello
|
|
echo_var "${!message}" # Hello, again!
|
|
|
|
exit 0</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>The next logical question is whether parameters can be
|
|
dereferenced <EM
|
|
>after</EM
|
|
> being passed to a
|
|
function.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="DEREFERENCECL"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-5. Dereferencing a parameter passed to a function</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# dereference.sh
|
|
# Dereferencing parameter passed to a function.
|
|
# Script by Bruce W. Clare.
|
|
|
|
dereference ()
|
|
{
|
|
y=\$"$1" # Name of variable (not value!).
|
|
echo $y # $Junk
|
|
|
|
x=`eval "expr \"$y\" "`
|
|
echo $1=$x
|
|
eval "$1=\"Some Different Text \"" # Assign new value.
|
|
}
|
|
|
|
Junk="Some Text"
|
|
echo $Junk "before" # Some Text before
|
|
|
|
dereference Junk
|
|
echo $Junk "after" # Some Different Text after
|
|
|
|
exit 0</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="REFPARAMS"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-6. Again, dereferencing a parameter passed to a function</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# ref-params.sh: Dereferencing a parameter passed to a function.
|
|
# (Complex Example)
|
|
|
|
ITERATIONS=3 # How many times to get input.
|
|
icount=1
|
|
|
|
my_read () {
|
|
# Called with my_read varname,
|
|
#+ outputs the previous value between brackets as the default value,
|
|
#+ then asks for a new value.
|
|
|
|
local local_var
|
|
|
|
echo -n "Enter a value "
|
|
eval 'echo -n "[$'$1'] "' # Previous value.
|
|
# eval echo -n "[\$$1] " # Easier to understand,
|
|
#+ but loses trailing space in user prompt.
|
|
read local_var
|
|
[ -n "$local_var" ] && eval $1=\$local_var
|
|
|
|
# "And-list": if "local_var" then set "$1" to its value.
|
|
}
|
|
|
|
echo
|
|
|
|
while [ "$icount" -le "$ITERATIONS" ]
|
|
do
|
|
my_read var
|
|
echo "Entry #$icount = $var"
|
|
let "icount += 1"
|
|
echo
|
|
done
|
|
|
|
|
|
# Thanks to Stephane Chazelas for providing this instructive example.
|
|
|
|
exit 0</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
></P
|
|
><DIV
|
|
CLASS="VARIABLELIST"
|
|
><P
|
|
><B
|
|
><A
|
|
NAME="EXITRETURN1"
|
|
></A
|
|
>Exit and Return</B
|
|
></P
|
|
><DL
|
|
><DT
|
|
><B
|
|
CLASS="COMMAND"
|
|
>exit status</B
|
|
></DT
|
|
><DD
|
|
><P
|
|
>Functions return a value, called an <I
|
|
CLASS="FIRSTTERM"
|
|
>exit
|
|
status</I
|
|
>. This is analogous to the <A
|
|
HREF="exit-status.html#EXITSTATUSREF"
|
|
>exit status</A
|
|
> returned by a
|
|
command. The exit status may be explicitly specified
|
|
by a <B
|
|
CLASS="COMMAND"
|
|
>return</B
|
|
> statement, otherwise it
|
|
is the exit status of the last command in the function
|
|
(<SPAN
|
|
CLASS="RETURNVALUE"
|
|
>0</SPAN
|
|
> if successful, and a non-zero
|
|
error code if not). This <A
|
|
HREF="exit-status.html#EXITSTATUSREF"
|
|
>exit
|
|
status</A
|
|
> may be used in the script by referencing it
|
|
as <A
|
|
HREF="internalvariables.html#XSTATVARREF"
|
|
>$?</A
|
|
>. This mechanism
|
|
effectively permits script functions to have a <SPAN
|
|
CLASS="QUOTE"
|
|
>"return
|
|
value"</SPAN
|
|
> similar to C functions.</P
|
|
></DD
|
|
><DT
|
|
><B
|
|
CLASS="COMMAND"
|
|
>return</B
|
|
></DT
|
|
><DD
|
|
><P
|
|
><A
|
|
NAME="RETURNREF"
|
|
></A
|
|
></P
|
|
><P
|
|
>Terminates a function. A <B
|
|
CLASS="COMMAND"
|
|
>return</B
|
|
> command
|
|
<A
|
|
NAME="AEN18474"
|
|
HREF="#FTN.AEN18474"
|
|
><SPAN
|
|
CLASS="footnote"
|
|
>[1]</SPAN
|
|
></A
|
|
>
|
|
optionally takes an <I
|
|
CLASS="FIRSTTERM"
|
|
>integer</I
|
|
>
|
|
argument, which is returned to the calling script as
|
|
the <SPAN
|
|
CLASS="QUOTE"
|
|
>"exit status"</SPAN
|
|
> of the function, and
|
|
this exit status is assigned to the variable <A
|
|
HREF="internalvariables.html#XSTATVARREF"
|
|
>$?</A
|
|
>.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="MAX"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-7. Maximum of two numbers</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# max.sh: Maximum of two integers.
|
|
|
|
E_PARAM_ERR=250 # If less than 2 params passed to function.
|
|
EQUAL=251 # Return value if both params equal.
|
|
# Error values out of range of any
|
|
#+ params that might be fed to the function.
|
|
|
|
max2 () # Returns larger of two numbers.
|
|
{ # Note: numbers compared must be less than 250.
|
|
if [ -z "$2" ]
|
|
then
|
|
return $E_PARAM_ERR
|
|
fi
|
|
|
|
if [ "$1" -eq "$2" ]
|
|
then
|
|
return $EQUAL
|
|
else
|
|
if [ "$1" -gt "$2" ]
|
|
then
|
|
return $1
|
|
else
|
|
return $2
|
|
fi
|
|
fi
|
|
}
|
|
|
|
max2 33 34
|
|
return_val=$?
|
|
|
|
if [ "$return_val" -eq $E_PARAM_ERR ]
|
|
then
|
|
echo "Need to pass two parameters to the function."
|
|
elif [ "$return_val" -eq $EQUAL ]
|
|
then
|
|
echo "The two numbers are equal."
|
|
else
|
|
echo "The larger of the two numbers is $return_val."
|
|
fi
|
|
|
|
|
|
exit 0
|
|
|
|
# Exercise (easy):
|
|
# ---------------
|
|
# Convert this to an interactive script,
|
|
#+ that is, have the script ask for input (two numbers).</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><DIV
|
|
CLASS="TIP"
|
|
><P
|
|
></P
|
|
><TABLE
|
|
CLASS="TIP"
|
|
WIDTH="90%"
|
|
BORDER="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="25"
|
|
ALIGN="CENTER"
|
|
VALIGN="TOP"
|
|
><IMG
|
|
SRC="../images/tip.gif"
|
|
HSPACE="5"
|
|
ALT="Tip"></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
><P
|
|
>For a function to return a string or array, use a
|
|
dedicated variable.
|
|
<TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>count_lines_in_etc_passwd()
|
|
{
|
|
[[ -r /etc/passwd ]] && REPLY=$(echo $(wc -l < /etc/passwd))
|
|
# If /etc/passwd is readable, set REPLY to line count.
|
|
# Returns both a parameter value and status information.
|
|
# The 'echo' seems unnecessary, but . . .
|
|
#+ it removes excess whitespace from the output.
|
|
}
|
|
|
|
if count_lines_in_etc_passwd
|
|
then
|
|
echo "There are $REPLY lines in /etc/passwd."
|
|
else
|
|
echo "Cannot count lines in /etc/passwd."
|
|
fi
|
|
|
|
# Thanks, S.C.</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="EX61"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-8. Converting numbers to Roman numerals</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
|
|
# Arabic number to Roman numeral conversion
|
|
# Range: 0 - 200
|
|
# It's crude, but it works.
|
|
|
|
# Extending the range and otherwise improving the script is left as an exercise.
|
|
|
|
# Usage: roman number-to-convert
|
|
|
|
LIMIT=200
|
|
E_ARG_ERR=65
|
|
E_OUT_OF_RANGE=66
|
|
|
|
if [ -z "$1" ]
|
|
then
|
|
echo "Usage: `basename $0` number-to-convert"
|
|
exit $E_ARG_ERR
|
|
fi
|
|
|
|
num=$1
|
|
if [ "$num" -gt $LIMIT ]
|
|
then
|
|
echo "Out of range!"
|
|
exit $E_OUT_OF_RANGE
|
|
fi
|
|
|
|
to_roman () # Must declare function before first call to it.
|
|
{
|
|
number=$1
|
|
factor=$2
|
|
rchar=$3
|
|
let "remainder = number - factor"
|
|
while [ "$remainder" -ge 0 ]
|
|
do
|
|
echo -n $rchar
|
|
let "number -= factor"
|
|
let "remainder = number - factor"
|
|
done
|
|
|
|
return $number
|
|
# Exercises:
|
|
# ---------
|
|
# 1) Explain how this function works.
|
|
# Hint: division by successive subtraction.
|
|
# 2) Extend to range of the function.
|
|
# Hint: use "echo" and command-substitution capture.
|
|
}
|
|
|
|
|
|
to_roman $num 100 C
|
|
num=$?
|
|
to_roman $num 90 LXXXX
|
|
num=$?
|
|
to_roman $num 50 L
|
|
num=$?
|
|
to_roman $num 40 XL
|
|
num=$?
|
|
to_roman $num 10 X
|
|
num=$?
|
|
to_roman $num 9 IX
|
|
num=$?
|
|
to_roman $num 5 V
|
|
num=$?
|
|
to_roman $num 4 IV
|
|
num=$?
|
|
to_roman $num 1 I
|
|
# Successive calls to conversion function!
|
|
# Is this really necessary??? Can it be simplified?
|
|
|
|
echo
|
|
|
|
exit</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>See also <A
|
|
HREF="testbranch.html#ISALPHA"
|
|
>Example 11-29</A
|
|
>.</P
|
|
><DIV
|
|
CLASS="IMPORTANT"
|
|
><P
|
|
></P
|
|
><TABLE
|
|
CLASS="IMPORTANT"
|
|
WIDTH="90%"
|
|
BORDER="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="25"
|
|
ALIGN="CENTER"
|
|
VALIGN="TOP"
|
|
><IMG
|
|
SRC="../images/important.gif"
|
|
HSPACE="5"
|
|
ALT="Important"></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
><P
|
|
>The largest positive integer a function can return is
|
|
255. The <B
|
|
CLASS="COMMAND"
|
|
>return</B
|
|
> command is closely tied
|
|
to the concept of <A
|
|
HREF="exit-status.html#EXITSTATUSREF"
|
|
>exit
|
|
status</A
|
|
>, which accounts for this particular
|
|
limitation. Fortunately, there are various <A
|
|
HREF="assortedtips.html#RVT"
|
|
>workarounds</A
|
|
> for those situations
|
|
requiring a large integer return value from a
|
|
function.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="RETURNTEST"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-9. Testing large return values in a function</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# return-test.sh
|
|
|
|
# The largest positive value a function can return is 255.
|
|
|
|
return_test () # Returns whatever passed to it.
|
|
{
|
|
return $1
|
|
}
|
|
|
|
return_test 27 # o.k.
|
|
echo $? # Returns 27.
|
|
|
|
return_test 255 # Still o.k.
|
|
echo $? # Returns 255.
|
|
|
|
return_test 257 # Error!
|
|
echo $? # Returns 1 (return code for miscellaneous error).
|
|
|
|
# =========================================================
|
|
return_test -151896 # Do large negative numbers work?
|
|
echo $? # Will this return -151896?
|
|
# No! It returns 168.
|
|
# Version of Bash before 2.05b permitted
|
|
#+ large negative integer return values.
|
|
# It happened to be a useful feature.
|
|
# Newer versions of Bash unfortunately plug this loophole.
|
|
# This may break older scripts.
|
|
# Caution!
|
|
# =========================================================
|
|
|
|
exit 0</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>A workaround for obtaining large integer <SPAN
|
|
CLASS="QUOTE"
|
|
>"return
|
|
values"</SPAN
|
|
> is to simply assign the <SPAN
|
|
CLASS="QUOTE"
|
|
>"return
|
|
value"</SPAN
|
|
> to a global variable.
|
|
|
|
<TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>Return_Val= # Global variable to hold oversize return value of function.
|
|
|
|
alt_return_test ()
|
|
{
|
|
fvar=$1
|
|
Return_Val=$fvar
|
|
return # Returns 0 (success).
|
|
}
|
|
|
|
alt_return_test 1
|
|
echo $? # 0
|
|
echo "return value = $Return_Val" # 1
|
|
|
|
alt_return_test 256
|
|
echo "return value = $Return_Val" # 256
|
|
|
|
alt_return_test 257
|
|
echo "return value = $Return_Val" # 257
|
|
|
|
alt_return_test 25701
|
|
echo "return value = $Return_Val" #25701</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
><P
|
|
><A
|
|
NAME="CAPTURERETVAL"
|
|
></A
|
|
></P
|
|
><P
|
|
>A more elegant method is to have the function
|
|
<B
|
|
CLASS="COMMAND"
|
|
>echo</B
|
|
> its <SPAN
|
|
CLASS="QUOTE"
|
|
>"return
|
|
value to <TT
|
|
CLASS="FILENAME"
|
|
>stdout</TT
|
|
>,"</SPAN
|
|
> and
|
|
then capture it by <A
|
|
HREF="commandsub.html#COMMANDSUBREF"
|
|
>command
|
|
substitution</A
|
|
>. See the <A
|
|
HREF="assortedtips.html#RVT"
|
|
>discussion
|
|
of this</A
|
|
> in <A
|
|
HREF="assortedtips.html"
|
|
>Section 36.7</A
|
|
>.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="MAX2"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-10. Comparing two large integers</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# max2.sh: Maximum of two LARGE integers.
|
|
|
|
# This is the previous "max.sh" example,
|
|
#+ modified to permit comparing large integers.
|
|
|
|
EQUAL=0 # Return value if both params equal.
|
|
E_PARAM_ERR=-99999 # Not enough params passed to function.
|
|
# ^^^^^^ Out of range of any params that might be passed.
|
|
|
|
max2 () # "Returns" larger of two numbers.
|
|
{
|
|
if [ -z "$2" ]
|
|
then
|
|
echo $E_PARAM_ERR
|
|
return
|
|
fi
|
|
|
|
if [ "$1" -eq "$2" ]
|
|
then
|
|
echo $EQUAL
|
|
return
|
|
else
|
|
if [ "$1" -gt "$2" ]
|
|
then
|
|
retval=$1
|
|
else
|
|
retval=$2
|
|
fi
|
|
fi
|
|
|
|
echo $retval # Echoes (to stdout), rather than returning value.
|
|
# Why?
|
|
}
|
|
|
|
|
|
return_val=$(max2 33001 33997)
|
|
# ^^^^ Function name
|
|
# ^^^^^ ^^^^^ Params passed
|
|
# This is actually a form of command substitution:
|
|
#+ treating a function as if it were a command,
|
|
#+ and assigning the stdout of the function to the variable "return_val."
|
|
|
|
|
|
# ========================= OUTPUT ========================
|
|
if [ "$return_val" -eq "$E_PARAM_ERR" ]
|
|
then
|
|
echo "Error in parameters passed to comparison function!"
|
|
elif [ "$return_val" -eq "$EQUAL" ]
|
|
then
|
|
echo "The two numbers are equal."
|
|
else
|
|
echo "The larger of the two numbers is $return_val."
|
|
fi
|
|
# =========================================================
|
|
|
|
exit 0
|
|
|
|
# Exercises:
|
|
# ---------
|
|
# 1) Find a more elegant way of testing
|
|
#+ the parameters passed to the function.
|
|
# 2) Simplify the if/then structure at "OUTPUT."
|
|
# 3) Rewrite the script to take input from command-line parameters.</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>Here is another example of capturing a function
|
|
<SPAN
|
|
CLASS="QUOTE"
|
|
>"return value."</SPAN
|
|
> Understanding it requires some
|
|
knowledge of <A
|
|
HREF="awk.html#AWKREF"
|
|
>awk</A
|
|
>.
|
|
|
|
<TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>month_length () # Takes month number as an argument.
|
|
{ # Returns number of days in month.
|
|
monthD="31 28 31 30 31 30 31 31 30 31 30 31" # Declare as local?
|
|
echo "$monthD" | awk '{ print $'"${1}"' }' # Tricky.
|
|
# ^^^^^^^^^
|
|
# Parameter passed to function ($1 -- month number), then to awk.
|
|
# Awk sees this as "print $1 . . . print $12" (depending on month number)
|
|
# Template for passing a parameter to embedded awk script:
|
|
# $'"${script_parameter}"'
|
|
|
|
# Here's a slightly simpler awk construct:
|
|
# echo $monthD | awk -v month=$1 '{print $(month)}'
|
|
# Uses the -v awk option, which assigns a variable value
|
|
#+ prior to execution of the awk program block.
|
|
# Thank you, Rich.
|
|
|
|
# Needs error checking for correct parameter range (1-12)
|
|
#+ and for February in leap year.
|
|
}
|
|
|
|
# ----------------------------------------------
|
|
# Usage example:
|
|
month=4 # April, for example (4th month).
|
|
days_in=$(month_length $month)
|
|
echo $days_in # 30
|
|
# ----------------------------------------------</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></P
|
|
><P
|
|
>See also <A
|
|
HREF="contributed-scripts.html#DAYSBETWEEN"
|
|
>Example A-7</A
|
|
>
|
|
and <A
|
|
HREF="contributed-scripts.html#STDDEV"
|
|
>Example A-37</A
|
|
>.</P
|
|
><P
|
|
><TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>Exercise:</B
|
|
></TT
|
|
> Using what we have
|
|
just learned, extend the previous <A
|
|
HREF="complexfunct.html#EX61"
|
|
>Roman numerals example</A
|
|
> to accept
|
|
arbitrarily large input.</P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
></DD
|
|
></DL
|
|
></DIV
|
|
><P
|
|
></P
|
|
><DIV
|
|
CLASS="VARIABLELIST"
|
|
><P
|
|
><B
|
|
><A
|
|
NAME="REDSTDINFUNC1"
|
|
></A
|
|
>Redirection</B
|
|
></P
|
|
><DL
|
|
><DT
|
|
><TT
|
|
CLASS="REPLACEABLE"
|
|
><I
|
|
>Redirecting the stdin
|
|
of a function</I
|
|
></TT
|
|
></DT
|
|
><DD
|
|
><P
|
|
>A function is essentially a <A
|
|
HREF="special-chars.html#CODEBLOCKREF"
|
|
>code block</A
|
|
>, which means its
|
|
<TT
|
|
CLASS="FILENAME"
|
|
>stdin</TT
|
|
> can be redirected (as in <A
|
|
HREF="special-chars.html#EX8"
|
|
>Example 3-1</A
|
|
>).</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="REALNAME"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 24-11. Real name from username</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# realname.sh
|
|
#
|
|
# From username, gets "real name" from /etc/passwd.
|
|
|
|
|
|
ARGCOUNT=1 # Expect one arg.
|
|
E_WRONGARGS=85
|
|
|
|
file=/etc/passwd
|
|
pattern=$1
|
|
|
|
if [ $# -ne "$ARGCOUNT" ]
|
|
then
|
|
echo "Usage: `basename $0` USERNAME"
|
|
exit $E_WRONGARGS
|
|
fi
|
|
|
|
file_excerpt () # Scan file for pattern,
|
|
{ #+ then print relevant portion of line.
|
|
while read line # "while" does not necessarily need [ condition ]
|
|
do
|
|
echo "$line" | grep $1 | awk -F":" '{ print $5 }'
|
|
# Have awk use ":" delimiter.
|
|
done
|
|
} <$file # Redirect into function's stdin.
|
|
|
|
file_excerpt $pattern
|
|
|
|
# Yes, this entire script could be reduced to
|
|
# grep PATTERN /etc/passwd | awk -F":" '{ print $5 }'
|
|
# or
|
|
# awk -F: '/PATTERN/ {print $5}'
|
|
# or
|
|
# awk -F: '($1 == "username") { print $5 }' # real name from username
|
|
# However, it might not be as instructive.
|
|
|
|
exit 0</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>There is an alternate, and perhaps less confusing
|
|
method of redirecting a function's
|
|
<TT
|
|
CLASS="FILENAME"
|
|
>stdin</TT
|
|
>. This involves redirecting the
|
|
<TT
|
|
CLASS="FILENAME"
|
|
>stdin</TT
|
|
> to an embedded bracketed code
|
|
block within the function.
|
|
|
|
<TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
># Instead of:
|
|
Function ()
|
|
{
|
|
...
|
|
} < file
|
|
|
|
# Try this:
|
|
Function ()
|
|
{
|
|
{
|
|
...
|
|
} < file
|
|
}
|
|
|
|
# Similarly,
|
|
|
|
Function () # This works.
|
|
{
|
|
{
|
|
echo $*
|
|
} | tr a b
|
|
}
|
|
|
|
Function () # This doesn't work.
|
|
{
|
|
echo $*
|
|
} | tr a b # A nested code block is mandatory here.
|
|
|
|
|
|
# Thanks, S.C.</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
><DIV
|
|
CLASS="NOTE"
|
|
><P
|
|
></P
|
|
><TABLE
|
|
CLASS="NOTE"
|
|
WIDTH="90%"
|
|
BORDER="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="25"
|
|
ALIGN="CENTER"
|
|
VALIGN="TOP"
|
|
><IMG
|
|
SRC="../images/note.gif"
|
|
HSPACE="5"
|
|
ALT="Note"></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
><P
|
|
>Emmanuel Rouat's <A
|
|
HREF="sample-bashrc.html"
|
|
>sample <TT
|
|
CLASS="FILENAME"
|
|
>bashrc</TT
|
|
>
|
|
file</A
|
|
> contains some instructive examples of
|
|
functions.</P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
></DD
|
|
></DL
|
|
></DIV
|
|
></DIV
|
|
><H3
|
|
CLASS="FOOTNOTES"
|
|
>Notes</H3
|
|
><TABLE
|
|
BORDER="0"
|
|
CLASS="FOOTNOTES"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
WIDTH="5%"
|
|
><A
|
|
NAME="FTN.AEN18474"
|
|
HREF="complexfunct.html#AEN18474"
|
|
><SPAN
|
|
CLASS="footnote"
|
|
>[1]</SPAN
|
|
></A
|
|
></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
WIDTH="95%"
|
|
><P
|
|
>The <B
|
|
CLASS="COMMAND"
|
|
>return</B
|
|
> command is a
|
|
Bash <A
|
|
HREF="internal.html#BUILTINREF"
|
|
>builtin</A
|
|
>.</P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><DIV
|
|
CLASS="NAVFOOTER"
|
|
><HR
|
|
ALIGN="LEFT"
|
|
WIDTH="100%"><TABLE
|
|
SUMMARY="Footer navigation table"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
CELLPADDING="0"
|
|
CELLSPACING="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="left"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="functions.html"
|
|
ACCESSKEY="P"
|
|
>Prev</A
|
|
></TD
|
|
><TD
|
|
WIDTH="34%"
|
|
ALIGN="center"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="index.html"
|
|
ACCESSKEY="H"
|
|
>Home</A
|
|
></TD
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="right"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="localvar.html"
|
|
ACCESSKEY="N"
|
|
>Next</A
|
|
></TD
|
|
></TR
|
|
><TR
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="left"
|
|
VALIGN="top"
|
|
>Functions</TD
|
|
><TD
|
|
WIDTH="34%"
|
|
ALIGN="center"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="functions.html"
|
|
ACCESSKEY="U"
|
|
>Up</A
|
|
></TD
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="right"
|
|
VALIGN="top"
|
|
>Local Variables</TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
></BODY
|
|
></HTML
|
|
> |