Revista 100% Verde

Shell scripting :funciones ,subshells y variables de entorno

Por Soloelectronicos @soloelectronico
Bash , c-shell o simplemente shell scripting es un lenguaje de script creado a fines de la década de 1980 por un programador llamado Brian Fox, que trabajaba para la Free Software Foundation . Fue pensado como una alternativa de software libre para el shell Bourne (de hecho, su nombre es un acrónimo de Bourne Again SHell ), e incorpora todas las características de ese shell, así como nuevas características como la aritmética de enteros y el control de trabajo Bash es un "shell de Unix", es decir una interfaz de línea de comandos para interactuar con el sistema operativo por lo que es tá ampliamente disponible, siendo el shell predeterminado en muchas distribuciones de GNU / Linux y en Mac OSX, con puertos existentes para muchos otros sistemas. En post anteriores hemos hablado en una primera aproximación al lenguaje c-sheall con el primer ejemplo famoso de Hello world y avanzando comandos ,tuberías ,variables ,parámetros y salidas posibles y en un segundo post sobre el uso de las Tuberías , sustitución de comandos,operadores ,asignación de variables ,Bucles , literales , variables y aritmética no entera En este post vamos a continuar avanzando en el conocimiento de este lenguaje con el uso de las funciones, subshells y las variables de entorno

Shell scripting  :funciones ,subshells   y variables de entorno

Funciones de shell

Una función de shell es un tipo especial de variable que es esencialmente un script dentro de otro script. Gracias a estas funciones c-shell nos permite agrupar una secuencia de comandos en un solo comando con nombre, lo cual es particularmente útil si la secuencia de comandos necesita ejecutarse desde muchos lugares dentro del script .

Como una función de shell puede incluso consistir en un solo comando; esto puede ser útil si el comando es particularmente complicado, o si su significado no sería inmediatamente obvio para un lector,es decir, las funciones de shell pueden servir para dos propósitos:

  • pueden guardar la escritura
  • Pueden permitir un código más legible mediante la creación de comandos con nombres intuitivos

Como ejemplo considere la siguiente secuencia de comandos:

El script anterior crea una función de shell llamada get_password que le pide al usuario que escriba una contraseña y almacena el resultado en una variable específica. Luego ejecuta get_password PASSWORD para almacenar la contraseña como $ PASSWORD ; y por último, si la llamada a get_password tuvo éxito (según lo determinado por su estado de salida), la contraseña recuperada se imprime en la salida estándar (que obviamente no es un uso realista pues el objetivo aquí es simplemente demostrar el comportamiento de get_password ).

La función get_password no hace nada que no se pueda hacer sin una función de shell, pero el resultado es mucho más legible. La función invoca la lectura de comando incorporada (que lee una línea de entrada del usuario y la guarda en una o más variables) con varias opciones con las que la mayoría de los programadores de Bash no estarán familiarizados:

  • La opción -r desactiva un significado especial para el carácter de barra diagonal inversa;
  • la opción -p hace que aparezca un mensaje específico, en este caso Contraseña:, al principio de la línea;
  • la opción -s evita que se muestre la contraseña a medida que el usuario lo escribe. Desde la -s la opción también evita que se muestre la nueva línea del usuario,

El comando echo proporciona una nueva línea y además, la función usa la expresión condicional -t 0 para asegurarse de que la entrada del script proviene de un terminal (una consola) y no de una archivo o de otro programa que no sabría que se está solicitando una contraseña. (Esta última característica es discutible; dependiendo de la funcionalidad general del script,pues puede ser mejor aceptar una contraseña de entrada estándar independientemente de su origen, suponiendo que la fuente se diseñó teniendo en cuenta el script). El punto general es que darle un nombre a la secuencia de comandos - get_password - hace que sea mucho más fácil para un programador saber qué hace.

Dentro de una función de shell, los parámetros posicionales ( $ 1 , $ 2 , etc., así como $ @ , $ * y $ # ) se refieren a los argumentos con los que se llamó a la función, no a los argumentos del script que contiene la función. Si se necesitan estos últimos, entonces deben pasarse explícitamente a la función, usando "$ @" . (incluso entonces, shift y set solo afectarán a los parámetros posicionales dentro de la función, no a los de la persona que llama).

Una llamada de función devuelve un estado de salida, al igual que un script (o casi cualquier comando). Para especificar explícitamente un estado de salida, use el comando , que finaliza la llamada a la función y devuelve el estado de salida especificado. (El comando de salida no se puede usar para esto, ya que terminaría la secuencia de comandos completa, como si se llamara desde fuera de una función). Si no se especifica ningún estado de salida, ya sea porque no se da ningún argumento al comando de devolución o porque se llega al final de la función sin haber ejecutado un comando de retorno , a función devolverá el estado de salida del último comando que se ejecutó.

Incidentalmente, cualquiera de las funciones o () pueden omitirse de una declaración de función, pero al menos una debe estar presente. En lugar de , muchos programadores escriben de manera similar, la notación {...} que no es exactamente necesaria y no es específica de las funciones; es simplemente una notación para agrupar una secuencia de comandos en un solo comando compuesto.

E l cuerpo de una función debe ser un comando compuesto, como un bloque {...} o una instrucción if ; {...} Es la opción convencional, incluso cuando todo lo que contiene es un comando compuesto único y, por lo tanto, teóricamente podría prescindirse de él. get_password ()

En Bash, uno o más comandos se pueden envolver entre paréntesis, lo que hace que esos comandos se ejecuten en una "subshell"(también hay algunas formas en que se pueden crear subshells implícitamente) Un subshell recibe una copia del "entorno de ejecución" del contexto circundante, que incluye cualquier variable, entre otras cosas; pero cualquier cambio que haga a subshell al entorno de ejecución no se vuelve a copiar cuando se completa la subshell.

Así, por ejemplo, este script:#! / bin / bash

La ejecución imprimirá esta salida:

bar

bar

bip

foo

bar

Si necesitase llamar a una función que modifica una o más variables, pero en realidad no desea que esas variables se modifiquen, puede ajustar la llamada a la función entre paréntesis, para que tenga lugar en una subshell. Esto "aislará" las modificaciones y evitará que afecten el entorno de ejecución circundante.Dicho esto: cuando sea posible, es mejor escribir funciones de tal manera que este problema no se presente para comenzar, pues la palabra clave local puede ayudar con esto.

Lo mismo ocurre con las definiciones de funciones; al igual que una variable regular, una función definida dentro de una subshell no es visible fuera de la subshell.

Una subshell también delimita los cambios a otros aspectos del entorno de ejecución; en particular, el comando cd ("cambiar directorio") solo afecta a la subshell. Así, por ejemplo, este script:

imprime esto:

Si su script necesita cambiar el directorio de trabajo antes de ejecutar un comando dado, es una buena idea usar una subshell si es posible,. de lo contrario, puede resultar difícil hacer un seguimiento del directorio de trabajo al leer un script. Alternativamente, los comandos incorporados pushd y popd se pueden usar para un efecto similar.

Una declaración de salida dentro de una subshell termina solo esa subshell. Por ejemplo, este script:

imprime esto:

 subshell tuvo éxito
subshell falló

Al igual que en una secuencia de comandos en su conjunto, exit de los valores predeterminados devuelve el estado de salida del comando de última ejecución, y una subshell que no tiene una instrucción de salida explícita devolverá el estado de salida del comando de última ejecución.

Variables de entorno

Ya hemos visto que, cuando se llama a un programa, recibe una lista de argumentos que se enumeran explícitamente en la línea de comandos. Lo que no hemos mencionado es qu e también recibe una lista de pares nombre-valor denominados "variables de entorno".

Diferentes lenguajes de programación ofrecen diferentes formas para que un programa acceda a una variable de entorno; Los programas C pueden usar getenv (" variable_name ") (y / o aceptarlos como un tercer argumento para main ), los programas Perl pueden usar $ ENV {' variable_name '} , los programas Java pueden usar System.getenv (). Get (" variable_name ") , y así sucesivamente.

En Bash, las variables de entorno se convierten simplemente en variables regulares de Bash. Así, por ejemplo, la siguiente secuencia de comandos imprime el valor de la variable de entorno HOME :

Sin embargo, lo contrario no es cierto: las variables regulares de Bash no se convierten automáticamente en variables de entorno. Así, por ejemplo, este script:

Esto no imprimirá la barra , porque la variable foo no se pasa al comando bash como una variable de entorno. ( De bash -c scripts argumentos ... corre el Bash script de una línea de la escritura ).

Para convertir una variable Bash normal en una variable de entorno, tenemos que "exportarla" al entorno. La siguiente secuencia de comandos hace la impresión de barras :

Tenga en cuenta que la exportación no solo crea una variable de entorno; en realidad marca la variable Bash como una variable exportada, y las asignaciones posteriores a la variable Bash también afectarán a la variable de entorno. Ese efecto es ilustrado por este script:

El comando de exportación también se puede usar para eliminar una variable de un entorno, incluyendo la opción -n ; por ejemplo, export -n foodeshace el efecto de export foo. Y múltiples variables pueden ser exportadas o no exportadas en un solo comando, como export foo baro export -n foo bar.

Es importante tener en cuenta que las variables de entorno solo se pasan a un comando; nunca se reciben de vuelta de un comando. En este sentido, son similares a las variables y subshells regulares de Bash. Así, por ejemplo, este comando:

barra de estampados ; el cambio a $ foo dentro del script de una línea no afecta el proceso que lo invocó. (Sin embargo, podría afectar a cualquier script que fueron llamados a su vez por ese guión.)

Si se desea una variable de entorno dada para un solo comando, se puede usar la sintaxis, con la sintaxis de una asignación de variable (o múltiples asignaciones de variables) que precede a un comando en la misma línea. (Tenga en cuenta que, a pesar de usar la sintaxis de una asignación de variable, esto es muy diferente de una asignación de variable Bash normal, en que la variable se exporta automáticamente al entorno y en que solo existe para el comando. Si desea evitar la confusión de sintaxis similar hacer las cosas diferentes, se puede utilizar la común utilidad Unix env para el mismo efecto que la utilidad también hace que sea posible. eliminar una variable de entorno para un comando - o incluso para eliminar todas las variables de entorno para un comando) Si. $ var ya existe, y se desea incluir su valor real en el entorno para un solo comando, que se puede escribir como .

Aparte: a veces es útil colocar definiciones de variables, o definiciones de funciones, en un script de Bash (por ejemplo, header.sh ) que puede ser llamado por otro script de Bash (por ejemplo, main.sh ). Podemos ver que simplemente invocar ese otro script Bash, como ./header.sh o como bash ./header.sh , no funcionará: las definiciones de variables en header.sh no serán vistas por main.sh , ni siquiera si "exportado" esas definiciones. (Este es un punto de confusión común: exportar las variables de exportación al entorno para que otros procesos puedan verlas, pero solo las ven los procesos secundarios , no los padres.) Sin embargo, podemos usar el comando incorporado Bash . ("punto") o fuente , que ejecuta un archivo externo casi como si fuera una función de shell. Si header.sh se ve así:

entonces este script:

imprimirá 'barra' .

Ahora hemos visto algunos de los caprichos del alcance variable en Bash.

Para resumir lo que hemos visto hasta ahora:

  • Las variables regulares de Bash están orientadas al shell que las contiene, incluidas las subshells en ese shell.
    • No son visibles para ningún proceso secundario (es decir, para programas externos).
    • Si se crean dentro de una subshell, no son visibles para el shell principal.
    • Si se modifican dentro de una subshell, esas modificaciones no son visibles para el shell principal.
    • Esto también se aplica a las funciones, que en muchos aspectos son similares a las variables regulares de Bash.
  • Las llamadas de función no se ejecutan inherentemente en subshells.
      Una modificación de variable dentro de una función generalmente es visible para el código que llama a la función.
  • Las variables de Bash que se exportan al entorno tienen un alcance al shell que las contiene, incluidas las subshells o procesos secundarios en ese shell.
    • El comando incorporado de exportación se puede utilizar para exportar una variable al entorno. (También hay otras formas, pero esta es la forma más común).
    • Difieren de las variables no exportadas solo en que son visibles para los procesos secundarios. En particular, todavía no son visibles para shells principales o procesos primarios.
  • Los scripts de Bash externos, como otros programas externos, se ejecutan en procesos secundarios. El . o el comando integrado de origen se puede utilizar para ejecutar un script de este tipo internamente, en cuyo caso no se ejecuta de forma inherente en una subshell.

Ademas a esto añadimos ahora:

  • Las variables de Bash que están localizadas en una función-llamada están sujetas a la función que las contiene, incluyendo cualquier función llamada por esa función.
  • El comando incorporado local se puede usar para localizar una o más variables a una llamada de función, usando la sintaxis local var1 var2o . (también hay otras formas, por ejemplo, el comando declarar incorporado tiene el mismo efecto, pero esta es probablemente la forma más común).
  • Se diferencian de las variables no localizadas en que desaparecen cuando finaliza su función-llamada. En particular, todavía son visibles para subshells y llamadas de función hijo. Además, al igual que las variables no localizadas, se pueden exportar al entorno para que también las vean los procesos secundarios.

En efecto, usar local para localizar una variable en una función-llamada es como poner la función-llamada en una subshell, excepto que solo afecta a una variable; otras variables pueden dejarse sin ser "locales".

Una variable que se establece dentro de una función (ya sea mediante asignación o mediante un comando for-loop u otro comando incorporado) debe marcarse como "local" utilizando el comando incorporado local , para evitar que se afecte accidentalmente el código fuera del función, a menos que se desee específicamente que la persona que llama vea el nuevo valor.

Es importante tener en cuenta que, aunque las variables locales en Bash son muy útiles, no son tan locales como las variables locales en la mayoría de los otros lenguaje s de programación, ya que son vistos por llamadas de funciones secundarias. Por ejemplo, este script:

En realidad se imprimirá baz en lugar de barra . Esto se debe a que el valor original de $ foo está oculto hasta que devuelve f2 . (En la teoría del lenguaje de programación, una variable como $ foo se dice que tiene un "ámbito dinámico" en lugar de un "ámbito léxico").

Una diferencia entre local y subshell es que mientras que un subshell toma inicialmente sus variables de su shell principal, una declaración como local foo oculta inmediatamente el valor anterior de $ foo ; es decir, $ foo se desestabiliza localmente. Si se desea inicializar el $ foo local al valor del $ foo existente , debemos especificarlo explícitamente, mediante el uso de una declaración como local foo = "$ foo" .

Cuando una función sale, las variables recuperan los valores que tenían antes de sus declaraciones locales (o simplemente se anulan, si no se habían anulado). Curiosamente, esto significa que un script como este:

Realmente imprimirá baz : la declaración foo = baz en la función surte efecto antes de que la variable se localice, por lo que el valor baz es lo que se restaura cuando la función regresa.Y dado que local es simplemente un comando ejecutable, una función puede decidir en tiempo de ejecución si localizar una variable dada, por lo que este script:

Este script en realidad se imprimirá

 bar
baz

Volver a la Portada de Logo Paperblog