parametros - bash script header
Funciones de bash: encerrar el cuerpo entre llaves vs. paréntesis (3)
¿Por qué se usan llaves por defecto para encerrar el cuerpo de la función en lugar de paréntesis?
El cuerpo de una función puede ser cualquier comando compuesto. Esto es típicamente { list; }
{ list; }
, pero técnicamente se permiten otras tres formas de comandos compuestos: (list)
, ((expression))
, y [[ expression ]]
.
C y los lenguajes de la familia C, como C ++, Java, C # y JavaScript, utilizan llaves para delimitar cuerpos de funciones. Las llaves son la sintaxis más natural para los programadores familiarizados con esos lenguajes.
¿Existen otras desventajas importantes (*) al usar paréntesis en lugar de llaves (lo que podría explicar por qué las llaves parecen ser preferibles)?
Sí. Hay muchas cosas que no puedes hacer desde un sub-shell, incluyendo:
- Cambiar variables globales. Los cambios de variables no se propagarán al shell principal.
- Salga del script. Una sentencia de salida solo saldrá del sub-shell.
Iniciar un sub-shell también puede ser un golpe de rendimiento serio. Estás iniciando un nuevo proceso cada vez que llamas a la función.
También puedes tener un comportamiento extraño si tu script es eliminado. Las señales que reciben el padre y el niño recibirán cambios. Es un efecto sutil, pero si tiene manejadores de trap
o si kill
su secuencia de comandos, esas partes no funcionan de la manera que usted desea.
¿Cuándo debo usar llaves para encerrar el cuerpo de la función, y cuándo es aconsejable cambiar a paréntesis?
Te aconsejo que siempre uses llaves. Si desea un sub-shell explícito, agregue un conjunto de paréntesis dentro de las llaves. Usar solo paréntesis es una sintaxis altamente inusual y confundiría a muchas personas que leen su script.
foo() {
(
subshell commands;
)
}
Generalmente, las funciones de bash se definen utilizando llaves para encerrar el cuerpo:
foo()
{
...
}
Al trabajar en un script de shell hoy en día haciendo un uso extensivo de las funciones, me he encontrado con problemas con variables que tienen el mismo nombre en la llamada que en la función de llamada, es decir, que esas variables son las mismas. Luego descubrí que esto se puede evitar definiendo las variables locales dentro de la función como local: local var=xyz
.
Luego, en algún momento, descubrí un hilo ( Definición del cuerpo de la función bash con paréntesis en lugar de llaves ) en el que se explica que es tan válido definir una función con paréntesis como este:
foo()
(
...
)
El efecto de esto es que el cuerpo de la función se ejecuta en una subshell, lo que tiene la ventaja de que la función tiene su propio alcance variable, que me permite definirlos sin local. Ya que tener un alcance de función local parece tener mucho más sentido y ser mucho más seguro que todas las variables siendo globales, inmediatamente me pregunto:
- ¿Por qué se usan llaves por defecto para encerrar el cuerpo de la función en lugar de paréntesis?
Sin embargo, también descubrí rápidamente un inconveniente importante para ejecutar la función en una subshell, específicamente que salir del script desde dentro de una función ya no funciona, en lugar de obligarme a trabajar con el estado de retorno en todo el árbol de llamadas (en caso de funciones anidadas). Esto me lleva a esta pregunta de seguimiento:
- ¿Existen otras desventajas importantes (*) al usar paréntesis en lugar de llaves (lo que podría explicar por qué las llaves parecen ser preferibles)?
(*) Estoy consciente (de las discusiones relacionadas con la excepción con las que me he topado con el tiempo) que algunos dirían que usar el estado de error de forma explícita es mucho mejor que poder salir de cualquier lugar, pero prefiero este último.
Al parecer ambos estilos tienen sus ventajas y desventajas. Así que espero que algunos de ustedes usuarios de bash más experimentados puedan brindarme una orientación general:
- ¿Cuándo debo usar llaves para encerrar el cuerpo de la función, y cuándo es aconsejable cambiar a paréntesis?
EDIT: para llevar de las respuestas
Gracias por sus respuestas, mi cabeza ahora es un poco más clara con respecto a esto. Así que lo que quito de las respuestas es:
Se adhieren a las llaves convencionales, aunque solo sea para no confundir a otros usuarios / desarrolladores de la secuencia de comandos (e incluso utilizar las llaves si todo el cuerpo está entre paréntesis).
La única desventaja real de las llaves es que se puede cambiar cualquier variable en el ámbito principal, aunque en algunas situaciones esto podría ser una ventaja. Esto se puede sortear fácilmente declarando las variables como
local
.El uso de paréntesis, por otro lado, puede tener algunos efectos no deseados graves, como alterar las salidas, provocar problemas con la eliminación de un script y aislar el ámbito de la variable.
Realmente importa Como las funciones de bash no devuelven valores y las variables que utilizaron son del ámbito global (es decir, pueden acceder a las variables desde "fuera" de su alcance), la forma habitual de manejar la salida de una función es almacenar el valor en una variable y luego llámala.
Cuando define una función con ()
, tiene razón: creará un sub-shell. Ese sub-shell contendrá los mismos valores que tenía el original, pero no podrá modificarlos. Por lo que está perdiendo ese recurso de cambiar variables de alcance global.
Vea un ejemplo:
$ cat a.sh
#!/bin/bash
func_braces() { #function with curly braces
echo "in $FUNCNAME. the value of v=$v"
v=4
}
func_parentheses() (
echo "in $FUNCNAME. the value of v=$v"
v=8
)
v=1
echo "v=$v. Let''s start"
func_braces
echo "Value after func_braces is: v=$v"
func_parentheses
echo "Value after func_parentheses is: v=$v"
Vamos a ejecutarlo:
$ ./a.sh
v=1. Let''s start
in func_braces. the value of v=1
Value after func_braces is: v=4
in func_parentheses. the value of v=4
Value after func_parentheses is: v=4 # the value did not change in the main shell
Tiendo a usar una subshell cuando quiero cambiar directorios, pero siempre desde el mismo directorio original, y no puedo molestarme en usar pushd/popd
o administrar los directorios yo mismo.
for d in */; do
( cd "$d" && dosomething )
done
Esto funcionaría igual de bien desde el cuerpo de una función, pero incluso si define la función con llaves, aún es posible usarla desde una subshell.
doit() {
cd "$1" && dosomething
}
for d in */; do
( doit "$d" )
done
Por supuesto, aún puede mantener el alcance variable dentro de una función definida por corchetes usando declarar o local:
myfun() {
local x=123
}
Así que diría que defina explícitamente su función como una subshell solo si no ser un subshell es perjudicial para el comportamiento correcto obvio de esa función.
Trivia: Como nota al margen, tenga en cuenta que bash en realidad siempre trata la función como un comando compuesto de llaves. Simplemente a veces tiene paréntesis en ella:
$ f() ( echo hi )
$ type f
f is a function
f ()
{
( echo hi )
}