Muchos scripts de Bash están escritos en un estilo estrictamente imperativo: ejecuta un comando, luego ejecuta otro, y así sucesivamente. A veces necesitas agrupar una secuencia de comandos que, en conjunto, cumplen un solo propósito. Aquí es donde entran en juego las funciones.
Una función se define así:
my_function () {
COMMANDS
}
El par de paréntesis vacío simplemente indica que estás definiendo una función. Dentro de ellos no va nada.
Una vez definidas, las funciones actúan como cualquier otro comando (ya sea integrado o no).
Como con cualquier comando, puedes pasarle argumentos a tus funciones.
Dentro de la función, accedes a los argumentos usando los parámetros posicionales, $1, $2, etc.
(Recuerda que aprendimos sobre los parámetros posicionales en el concepto de Variables).
El parámetro especial $0 no cambia dentro de una función; sigue siendo el nombre del script que se está ejecutando.
La función que se está ejecutando en ese momento puede acceder a su nombre con la variable $FUNCNAME.
Consulta 3.4.2 Parámetros especiales en el manual.
Puedes definir variables dentro de una función.
Si declaras las variables con el comando local, el ámbito de la variable se limita a la función actual (y a cualquier función que esta llame).
De lo contrario, la variable se coloca en el ámbito global.
Las variables locales pueden tener el mismo nombre que una variable global. En ese caso, la variable local «oculta» a la variable global. Por ejemplo, una variable local declarada en una función oculta a una variable global del mismo nombre: las referencias y las asignaciones apuntan a la variable local, y dejan la variable global sin modificar. Cuando la función termina, la variable global vuelve a ser visible.
x=5
myfunc () {
local x=100
echo "in my function, $x == 100"
}
echo "in the global scope, $x == 5"
myfunc
echo "back in the global scope, $x == 5"
Esto produce la salida
in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5
Dentro de una función, puedes acceder a variables del ámbito de la función que la llama. Eso significa que puedes usar variables globales, así como variables locales que se declararon en la función que la llama (o en alguna función que llama a esa).
Técnicamente, «global» no es la palabra adecuada. Para expandir una variable dentro de una función, Bash recorre la pila de llamadas hacia arriba, hasta el ámbito global, en busca de una función donde se haya declarado ese nombre de variable.
Este ejemplo está adaptado de la sección Funciones de shell del manual:
func1() {
local var='func1 local'
func2
}
func2() {
echo "In func2, var = $var"
}
var=global
func1
func2
La salida es:
In func2, var = func1 local
In func2, var = global
De manera similar, asignar un valor a una variable lo asigna en el ámbito donde se declaró. Esta «acción a distancia» puede producir código difícil de seguir, ya que no siempre es obvio dónde se le asignó un valor a una variable.
La pila de llamadas se puede examinar usando la variable de array FUNCNAME.
Una función, como cualquier comando, tiene un estado de salida. De forma predeterminada, el estado de una función es el estado de salida del último comando ejecutado.
Puedes usar el comando return para salir de una función con un estado de salida específico.
check_password () {
if [[ $1 == "secret" ]]; then
return 0
else
return 1
fi
}
read -sp "Enter your password: " pass
if check_password "$pass"; then
echo "Correct!"
else
echo "Wrong password."
fi
Usar return sin argumentos devuelve el estado del último comando ejecutado.
Observa que la función check_password se puede simplificar así:
check_password () { [[ $1 == "secret" ]]; }
[[...]] tiene un estado de salida: 0 para «verdadero», 1 para «falso».{...} debe tener una nueva línea o un punto y coma antes de la llave de cierre.El estado de salida de una función es solo un número. ¿Cómo puede una función producir una salida?
Tu función puede imprimir en la salida estándar. Usa la conocida sustitución de comandos para capturarla:
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }
die=$( d6 )
echo "You rolled a $die."
El estado de salida de una función está disponible incluso cuando estás capturando la salida.
roll () {
local n=$1
if (( 4 <= n && n <= 20 )); then
echo "$(( 1 + RANDOM % n ))" # exit status is 0
else
return 1
fi
}
read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
echo "You rolled a $die."
else
echo "I can't roll a die with $faces faces."
fi
Las funciones pueden llamarse a sí mismas de forma recursiva. De forma predeterminada, no hay límite para la profundidad de la recursión.
Un ejemplo:
fibonacci() {
local n=$1
if (( n <= 1 )); then
echo "1"
else
local a=$(fibonacci "$(( n - 1 ))")
local b=$(fibonacci "$(( n - 2 ))")
echo "$(( a + b ))"
fi
}
for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
La profundidad de la recursión se puede controlar con la variable FUNCNEST.
bash -c '
recur() {
echo $1
recur $(($1 + 1))
}
FUNCNEST=5
recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)