Shell scripting :funciones ,subshells y variables de entorno


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 está 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

close up code coding computer

 

 

 

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:

#! / bin / bash
# Uso: get_password VARNAME 
# Le pide al usuario una contraseña y lo guarda como $ VARNAME. 
# Devuelve un estado de salida distinto de cero si la entrada estándar no es un terminal, o si el 
comando # "leer" devuelve un estado de salida distinto de cero. 
get_password ()  { 
  si  [[ -t 0  ]]  ;  then
    read -r -p 'Contraseña:' -s "  $ 1  "  &&  echo 
  else 
    return  1
   fi
 }

get_password PASSWORD &&  echo  "  $ PASSWORD  "

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 return , 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.

El 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.function get_password ( )get_password ()

Subshells 

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

foo  = barra
 echo  "  $ foo  "  # imprime 'barra'

# subshell:
 (
  echo  "  $ foo  "  # imprime 'barra' - la subshell hereda las variables de sus padres 
  baz  = bip
   echo  "  $ baz  "  # imprime 'bip' - la subshell puede crear sus propias variables 
  foo  = foo
   echo  "  $ foo  "  # imprime ' foo '- la subshell puede modificar variables heredadas
 )

echo  "  $ baz  "  # no imprime nada (solo una nueva línea) - se pierden las nuevas variables de la subshell 
echo  "  $ foo  "  # imprime 'barra' - los cambios de la subshell a las variables antiguas se pierden

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:

  #! / bin / bash

cd /
 pwd  # imprime '/'

# subshell:
 (
  pwd  # prints '/' - la subshell hereda el directorio de trabajo 
  cd home
   pwd  # prints '/ home' - la subshell puede cambiar el directorio de trabajo 
)  # end of subshell

pwd  # prints '/': los cambios de la subshell en el directorio de trabajo se pierden

imprime esto:

/ / /casa /

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:

  #! / bin / bash
(  exit  0  )  &&  echo  'subshell successed' 
(  exit 1  )  ||  echo  'subshell failed'

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 que 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 :

#! / bin / bash
echo  "  $ 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:

#! / bin / bash
foo  = bar
bash -c 'echo $ foo'

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 :

#! / bin / bash
export  foo  = bar
bash -c 'echo $ foo'

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:

#! / bin / bash
foo  = bar
bash -c 'echo $ foo'  # no imprime nada 
export foo
bash -c 'echo $ foo'  # imprime 'bar' 
foo  = baz
bash -c 'echo $ foo'  # imprime 'baz'

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 barexport -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:

#! / bin / bash
export  foo  = bar
bash -c 'foo = baz'  # no tiene efecto 
echo  "  $ foo  "  # print 'bar'

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. $ varvar =value commandya existe, y se desea incluir su valor real en el entorno para un solo comando, que se puede escribir como .var = " $var " command

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í:

 foo  = función de barra
 baz ()
 {
  echo  "  $ @  "
 }

entonces este script:

#! / bin / bash
. header.sh
baz "  $ foo  "

imprimirá 'barra' .

Alcance 

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).local var1 = val1 var2 = val2)
  • 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 lenguajes de programación, ya que son vistos por llamadas de funciones secundarias. Por ejemplo, este script:

#! / bin / bash

foo  = bar

function f1 ()
 {
  echo  "  $ foo  "
 }

function f2 ()
 {
  local  foo  = baz
  f1 # imprime 'baz'
 }

 f2

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:

 #! / bin / bash

function f ()
 {
  foo  = baz
   local  foo  = bip
 }

foo  = bar
 F
echo  "  $ foo  "

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:

#! / bin / bash

function f ()
 {
  si  [[  "  $ 1  "  ==  'sí'  ]]  ;  entonces
    Foo
   local fi
  foo  = baz
 }

foo  = bar
f yes # modifica un $ foo localizado, por lo que no tiene ningún efecto 
echo  "  $ foo  "  # imprime 'barra' 
f # modifica el $ foo no localizado, configurándolo en 'baz' 
echo  "  $ foo  "  # imprime 'baz'

Este script en realidad se imprimirá

 bar
baz

 

Anuncios

Shell scripting : variables y operaciones


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 está ampliamente disponible, siendo el shell predeterminado en muchas distribuciones de GNU / Linux y en Mac OSX, con puertos existentes para muchos otros sistemas.

Además del modo interactivo, donde el usuario escribe un comando a la vez, con ejecución y respuesta inmediatas, Bash (como muchos otros shells) también tiene la capacidad de ejecutar un script completo de comandos, conocido como “Bash shell script” (o “Bash script” o “shell script” o simplemente “script”)  que es justo   lo que vamos a tratar en este post como continuación de un  post anterior introductorio donde exponimos ale tipico Hello world en c-shell

close up code coding computer

Tuberías y sustitución de comandos 

Como hemos visto, el valor de retorno de un comando, tomado estrictamente, es solo un pequeño entero no negativo destinado a indicar el éxito o el fracaso. Su salida real es lo que escribe en el flujo de salida estándar. De forma predeterminada, el texto escrito en el flujo de salida estándar se imprime en el terminal, pero hay algunas formas en que se puede “capturar” y usar como el verdadero valor de retorno del comando.

Tuberías 

Cuando una secuencia de comandos se vincula entre sí en una tubería, la salida de cada comando se pasa como entrada al siguiente. Esta es una técnica muy poderosa, ya que nos permite combinar varios programas de utilidad pequeños para crear algo complejo.

Sustitución de comandos 

La sustitución de comandos es un poco como expansión de variable, pero ejecuta un comando y captura su salida, en lugar de simplemente recuperar el valor de una variable. Por ejemplo, considere nuestro ejemplo de get_password anterior:

#! / bin / bash

función get_password (  ) 
# Uso: get_password VARNAME 
#Le pide al usuario una contraseña; lo guarda como $ VARNAME.
# Devuelve un estado de salida distinto de cero si la entrada estándar no #es un terminal, o si el comando  "leer" devuelve un estado de salida #tinto de cero.
{
  if [[ -t 0 ]] ; then
    read -r -p 'Password:' -s "$1" && echo
  else
    return 1
  fi
}
 
get_password PASSWORD && echo "$PASSWORD"

Realmente no hay razón para que la persona que llama deba guardar la contraseña en una variable.

Si get_password simplemente imprimió la contraseña en su salida estándar, entonces la persona que llama podría usar la sustitución de comandos y usarla directamente:

 #! / bin / bash
 
función get_password (  ) 
# Uso: get_password 
# Pide al usuario una contraseña; Imprime para capturar llamando al código. 
# Devuelve un estado de salida distinto de cero si la entrada estándar no es un terminal, o si 
# salida estándar * es * un terminal, o si el comando "leer" devuelve un 
estado de salida distinto de cero #.
 {
  if [[ -t 0 ]] && ! [[ -t 1 ]] ; then
    local PASSWORD
    read -r -p 'Password:' -s PASSWORD && echo >&2
    echo "$PASSWORD"
  else
    return 1
  fi
}
 
echo "$(get_password)"

Para evaluar "$ (get_password)" , Bash ejecuta el comando get_password en una subshell, capturando su salida estándar y reemplaza $ (get_password) por la salida capturada.

Además de la notación $ (...) , también se admite una notación más antigua  (con comillas inversas), y aún se encuentra con bastante frecuencia. Las dos notaciones tienen el mismo efecto, pero la sintaxis de  es más restrictiva y, en casos complejos, puede ser más complicado acertar.

La sustitución de comandos permite anidar como vemos .  Se permite, expresiones  "$ (b" $ (c) ")" . (es decir ,ejecuta el comando c , usando su salida como un argumento para b , y usando la salida de eso como un argumento para a .)

Una sustitución de comando puede contener una secuencia de comandos, en lugar de un solo comando de modo que se captura la salida de todos estos comandos.

Como hemos visto anteriormente, se pueden usar puntos y coma en lugar de líneas nuevas para separar los comandos, lo cual  es particularmente común en la sustitución de comandos.

Una sustitución de comando puede incluso contener asignaciones de variables y definiciones de funciones (aunque, como los comandos sustituidos se ejecutan dentro de una subshell, las asignaciones de variables y las definiciones de funciones dentro del comando no se verán fuera de ella; solo son útiles si se usan dentro de los comandos sustituidos ).

Aritmética de shell 

Las expresiones aritméticas en Bash se modelan estrechamente en las de C, por lo que son muy similares a las de otros lenguajes derivados de C, como C ++, Java, Perl, JavaScript, C # y PHP.

Una diferencia importante es que Bash solo admite aritmética de enteros (números enteros), no aritmética de punto flotante (decimales y fracciones); Por ejemplo  algo como 3 + 4 significa lo que esperarías (7), pero algo como 3.4 + 4.5 es un error de sintaxis. Algo así como 13/5 está bien, pero realiza una división de enteros, por lo que se evalúa en 2 en lugar de en 2.6.

Expansión aritmética 

Quizás la forma más común de usar expresiones aritméticas es en la expansión aritmética , donde el resultado de una expresión aritmética se usa como un argumento para un comando

. La expansión aritmética se denota $ ((...)) . Por ejemplo, este comando:

 echo  $ ((  3  +  4  *  (  5  -  1  )  ))

impresiones 19 .

expr (en desuso) 

Otra forma de usar expresiones aritméticas es mediante el programa Unix “expr”, que era popular antes de que Bash admitiera las matemáticas.  Similar a la expansión aritmética, este comando:

 echo  ` expr 3 + 4  \ *  \ (  5 - 1  \)  `

impresiones 19 .

Tenga en cuenta que el uso de “expr” requiere un carácter de escape “\” antes del operador de multiplicación “*” y los paréntesis. Tenga en cuenta los espacios entre los símbolos de cada operador, incluidos los paréntesis.

Operadores numéricos

Además de las notaciones familiares + (adición) y - (resta), las expresiones aritméticas también son compatibles con * (multiplicación), / (división entera, descrita anteriormente), % (división de módulo, la operación “resto”; por ejemplo, 11 dividido por 5 es 2 el resto 1, entonces 11% 5 es 1 ), y ** (“exponenciación”, es decir, involución; por ejemplo, 2 4 = 16, entonces 2 ** 4 es 16 ).

Los operadores + y - , además de sus sentidos “binarios” (dos operandos) de “suma” y “resta”, tienen sentidos “unarios” (un operando) de “positivo” y “negativo”. Unary + tiene básicamente ningún efecto; unary - invierte el signo de su operando. Por ejemplo, - (3 * 4) evalúa a -12 , y - (- (3 * 4)) evalúa a 12 .

Referente a variables 

Dentro de una expresión aritmética, se puede hacer referencia a las variables de shell directamente, sin usar expansión variable (es decir, sin el signo de dólar $ ).

Por ejemplo, esto:

 i  =  2 +3
 echo  $ ((  7  * i ))

impresiones 35 . (Tenga en cuenta que primero se evalúa i , que produce 5 y luego se multiplica por 7. Si hubiéramos escrito $ i en lugar de i , se habría realizado una mera sustitución de cadenas; 7 * 2 + 3 es igual a 14 + 3, es decir, 17 – Probablemente no sea lo que queremos.

El ejemplo anterior se muestra usando “expr”:

 i  =  ` expr 2 + 3  ' 
eco  ' expr 7  \ *  $ i  `

impresiones 35 .

Asignación a variables 

Las variables de shell también se pueden asignar dentro de una expresión aritmética. La notación para esto es similar a la asignación de variables regulares, pero es mucho más flexible.

Por ejemplo, el ejemplo anterior podría reescribirse así:

 echo  $ ((  7  *  (  i  =  2  +  3  )  ))

excepto que esto establece $ i a 5 en lugar de a 2 + 3 .

Tenga en cuenta que, aunque la expansión aritmética parece un poco a la sustitución de comandos, se no se ejecuta en un subnivel; este comando realmente establece $ i a 5 , y los comandos posteriores pueden usar el nuevo valor. (Los paréntesis dentro de la expresión aritmética son solo el uso matemático normal de paréntesis para controlar el orden de las operaciones).

Además del operador de asignación simple = , Bash también admite operadores compuestos como + = , - = , * = , / = y % = , que realizan una operación seguida de una asignación. Por ejemplo, ((i * = 2 + 3)) es equivalente a ((i = i * (2 + 3))) . En cada caso, la expresión en su conjunto se evalúa al nuevo valor de la variable; por ejemplo, si $ i es 4 , entonces ((j = i * = 3)) establece tanto $ i como $ j en 12 .

Por último, Bash soporta operadores de incremento y decremento.

El operador incremental ++ incrementa el valor de una variable en 1; si precede al nombre-variable (como el operador “pre-incremento”), entonces la expresión se evalúa al nuevo valor de la variable , y si sigue al nombre-variable (como el operador “post-incremento”), entonces la expresión se evalúa al valor antiguo de la variable .

Por ejemplo, si $ i es 4 , entonces ((j = ++ i)) establece tanto $ i como $ j en 5 , mientras que ((j = i ++)) establece $ i en 5$ j a 4 . El operador de disminución - es exactamente el mismo, excepto que disminuye el valor de la variable en 1. La reducción previa y posterior de la reducción son completamente análogas al incremento previo y al incremento posterior.

Las expresiones aritméticas como sus propios comandos 

Un comando puede consistir completamente en una expresión aritmética, usando cualquiera de las siguientes sintaxis:

 ((  i  =  2 + 3  ))
 let  'i = 2 + 3'

Cualquiera de estos comandos establecerá $ i en 5 . Ambos estilos de comando devuelven un estado de salida de cero (“exitoso” o “verdadero”) si la expresión se evalúa como un valor distinto de cero, y un estado de salida de uno (“falla” o “falso”) si la expresión se evalúa como cero. Por ejemplo, esto:

 ((  0  ))  ||  echo zero
 ((  1  ))  &&  echo non-zero

imprimirá esto:

zero
non-zero

La razón de este comportamiento contraintuitivo es que en C, cero significa “falso” y los valores distintos de cero (especialmente uno) significan “verdadero”. Bash mantiene ese legado dentro de las expresiones aritméticas, luego lo convierte en la convención habitual de Bash al final.

El operador de coma 

Las expresiones aritméticas pueden contener múltiples sub-expresiones separadas por comas , . El resultado de la última sub-expresión se convierte en el valor general de la expresión completa. Por ejemplo, esto:

 echo  $ ((  i  =  2 , j  =  2  + i, i * j ))

establece $ i en 2 , establece $ j en 4 e imprime 8 .

De hecho , la función incorporada admite múltiples expresiones directamente sin necesidad de una coma; por lo tanto, los siguientes tres comandos son equivalentes:

 ((  i  =  2 , j  =  2 + i, i * j ))
 let  'i = 2, j = 2 + i, i * j'
 let  'i = 2'  'j = 2 + i'  'i * j'

Operadores de comparación, booleanos y condicionales 

Las expresiones aritméticas son compatibles con los operadores de comparación de enteros < , > , <= (significado ≤), > = (significado ≥), == (significado =) y ! = (Significado). Cada uno evalúa a 1 para “verdadero” o 0 para “falso”.

También son compatibles con los operadores booleanos:

  • && (“and”), que se evalúan como 0 si cualquiera de sus operandos es cero, y 1 de lo contrario;
  •  ||(“or”), que se evalúa en 1 si alguno de sus operandos es distinto de cero, y en 0 en caso contrario;
  • ! (“not”), que se evalúa en 1 si su operando es cero, y en 0 en caso contrario.

Aparte de que utilizan cero para significar valores “falsos” y valores distintos de cero para significar “verdaderos”, son como los operadores && , || , y ! que hemos visto fuera de expresiones aritméticas. Al igual que esos operadores, estos son operadores “abreviados” que no evalúan su segundo argumento si su primer argumento es suficiente para determinar un resultado. Por ejemplo, (((i = 0) &&(j = 2))) no evaluará el (j = 2)parte, y por lo tanto no establecerá $ j en 2 , porque el operando izquierdo de && es cero (“falso”).

¿Y soportan el operador condicional b ? e1 : e2 . Este operador evalúa e1 y devuelve su resultado, si b es distinto de cero; de lo contrario, evalúa e2 y devuelve su resultado.

Estos operadores se pueden combinar de formas complejas:

 ((  i  =  (  ( a> b && c <d + e ||  f  == g + h ) ? j: k )  ))

Aritmética de bucles 

Arriba, vimos un estilo de for-loop, que se veía así:

# imprimir todos los enteros del 1 al 20: 
for i in {1..20} ; do
  echo $i
done

Bash también admite otro estilo, modelado en los bucles for de C y lenguajes relacionados, usando aritmética de shell:

# imprimir todos los enteros del 1 al 20: 
for (( i = 1 ; i <= 20 ; ++i )) ; do
  echo $i
done

Este bucle for utiliza tres expresiones aritméticas separadas, separadas por punto y coma (y no comas , son expresiones completamente separadas, no solo subexpresiones):

  • La primera es una expresión de inicialización, se ejecuta antes de que comience el bucle.
  • El segundo es una expresión de prueba; se evalúa antes de cada posible iteración del bucle (incluida la primera), y si se evalúa a cero (“falso”), entonces el bucle sale.
  • El tercero es una expresión de conteo; Se evalúa al final de cada iteración de bucle. En otras palabras, este bucle for es exactamente equivalente a este bucle while:
# imprimir todos los enteros del 1 al 20: 
(( i = 1 ))
while (( i <= 20 )) ; do
  echo $i
  (( ++i ))
done

pero, una vez que se acostumbre a la sintaxis, se hace más claro lo que está sucediendo.

Operadores bitwise 

Además de aritméticas y booleanas operadores regulares, Bash también ofrece a los operadores bit a bit “”, significa que los operadores que operan sobre números enteros qua cadenas de bits en lugar de qua enteros.

Si aún no está familiarizado con este concepto, puede ignorarlo de manera segura.

Al igual que en C, los operadores bitwise son :

  • & (bitwise “and”),
  • (bitwise “or”),
  • ^ (bitwise “exclusive or”),
  • ~ (bitwise “not”),
  •  << (desplazamiento a la izquierda en modo bit), y
  • >> (desplazamiento a la derecha en modo bit), así como
  •  & =
  •  | =
  • ^ = (que incluyen la asignación, al igual que + = ).

Literales enteros 

Una constante entera se expresa como un entero literal . Ya hemos visto muchos de estos; 34 , por ejemplo, es un literal entero que denota el número 34.

Todos los ejemplos anteriores  han sido decimales (base diez) literales enteros, que es el valor predeterminado; pero, de hecho, los literales pueden expresarse en cualquier base en el rango 2–64, utilizando el valor de base de notación # (la propia base se expresa en base diez).

Por ejemplo, esto:

 echo $ (( 12 )) # usa el valor predeterminado de base diez (decimal)
 echo $ (( 10 # 12 )) # especifica explícitamente base diez (decimal)
 echo $ (( 2 # 1100 )) # base dos (binario)
 echo $ (( 8 # 14 )) # base ocho (octal)
 echo $ (( 16 # C )) # base dieciseis (hexadecimal)
 eco $ (( 8 + 2 # 100 )) # ocho en base diez (decimal), más cuatro en base dos (binario)

Imprimirá 12 seis veces. (Tenga en cuenta que esta notación solo afecta a cómo se interpreta un literal entero. El resultado de la expansión aritmética todavía se expresa en base diez, independientemente).

Para las bases 11 a 36, ​​las letras inglesas A a Z se usan para los valores de dígitos 10 a 35. Esto no distingue entre mayúsculas y minúsculas. Sin embargo, para las bases 37 a 64, son las letras inglesas en minúsculas las que se usan para los valores de dígitos 10 a 35, mientras que las letras mayúsculas se usan para los valores de dígitos 36 a 61, el signo at @ se usa para las cifras el valor 62, y el subrayado _ se usa para el valor de dígitos 63. Por ejemplo, 64 # @ A3 indica 256259 ( 62 × 64 2 + 36 × 64 + 3 ).

También hay dos notaciones especiales: el prefijo de un literal con 0 indica la base ocho (octal), y el prefijo de 0x o 0X indica la base dieciséis (hexadecimal). Por ejemplo, 030 es equivalente a 8 # 30 , y 0x6F es equivalente a 16 # 6F .

Variables enteras 

Una variable se puede declarar como una variable entera, es decir, su “atributo entero” se puede “establecer”, usando esta sintaxis:

declare -i n
Después de ejecutar el comando anterior, cualquier asignación subsiguiente a n hará que el lado derecho se interprete automáticamente como una expresión aritmética. Por ejemplo, esto:
declare -i n
n='2 + 3 > 4'
 Es más o menos equivalente a esto:
 n  =  $ ((  2  +  3 > 4  ))

excepto que la primera versión declare -incontinuará afectando las tareas posteriores también.

En la primera versión, note el uso de comillas en el lado derecho de la tarea. Si hubiéramos escrito n = 2 + 3> 4 , habría significado “ejecutar el comando + con el argumento 3 , pasando la variable de entorno n configurada a 2 y redirigiendo la salida estándar al archivo 4 “; es decir, establecer el atributo entero de una variable no afecta el análisis global de las declaraciones de asignación, sino que simplemente controla la interpretación del valor que finalmente se asigna a la variable.

Podemos “anular” el atributo entero de una variable, desactivando este comportamiento, usando el comando opuesto:


declare +i n

El comando declare incorporado también tiene otros usos: hay algunos otros atributos que una variable puede tener, y declare tiene algunas otras características además de activar y desactivar los atributos. Además, algunas de sus propiedades destacan:

  • Al igual que con local y export , el argumento puede ser una asignación de variable; por ejemplo, establece el atributo entero de $ n y lo establece en 5 .declare -i n = 2 +3
  • Al igual que con local y export , se pueden especificar múltiples variables (y / o asignaciones) a la vez; por ejemplo, declare -i n=2+3 establece tanto el atributo entero de $ m como el de $ n .
  • Cuando se usa dentro de una función, declare implícitamente localiza la variable (a menos que la variable ya sea local), lo que también tiene el efecto de desarmarla localmente (a menos que se use la sintaxis de asignación).

Aritmética no entera 

Como se mencionó anteriormente, Bash shell arithmetic solo admite aritmética de enteros. Sin embargo, los programas externos a menudo se pueden usar para obtener una funcionalidad similar para valores no enteros.

En particular, la utilidad de Unix común bc se usa a menudo para esto. El siguiente comando:

 echo  "  $ (  echo  '3.4 + 2.2'  | bc )  "

impresiones 5.6 .

No hace falta decir que, dado que bc no está tan estrechamente integrado con Bash como lo es la aritmética de shell, no es tan conveniente; por ejemplo, algo como esto:

# imprimir las potencias de dos, de 1 a 512: 
for (( i = 1 ; i < 1000 ; i *= 2 )) ; do
  echo $i
done

sería, para soportar no enteros, convertirse en algo como esto:

# imprimir los poderes de la mitad, de 1 a 1/512: 
i=1
while [ $( echo "$i > 0.001" | bc ) = 1 ] ; do
  echo $i
  i=$( echo "scale = scale($i) + 1 ; $i / 2" | bc )
done

Parte de esto se debe a que ya no podemos usar un aritmética for-loop; parte de esto se debe a que hacer referencia a las variables y asignarlas a las variables es más complicado ahora (ya que bc no es consciente de las variables de la shell, solo de las suyas, no relacionadas); y parte de ello se debe a que bc se comunica con el shell solo a través de entrada y salida.

!Y  esto  es todo por hoy!Pero no se preocupe en proximos post trataremos otrso temas como entrada/salida, funcionaes complejas y mucho mas

Mas informacion en  https://en.wikibooks.org/wiki/Bash_Shell_Scripting

Introduccion al shell scripting o c-shell


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 está ampliamente disponible, siendo el shell predeterminado en muchas distribuciones de GNU / Linux y en Mac OSX, con puertos existentes para muchos otros sistemas.

Además del modo interactivo, donde el usuario escribe un comando a la vez, con ejecución y respuesta inmediatas, Bash (como muchos otros shells) también tiene la capacidad de ejecutar un script completo de comandos, conocido como “Bash shell script” (o “Bash script” o “shell script” o simplemente “script”)  que es justo   lo que vamos a tratar en este post

 

 

Un scriptl  cinstituye un fichero normalmente con extensión .sh  que  puede contener solo una lista muy simple de comandos, o incluso un solo comando, aunque   los normal es que  contenga funciones, bucles, construcciones condicionales y todas las demás características de la programación imperativa.

Por otra parte los scripts de shell se pueden llamar desde línea de comandos interactiva descrita anteriormente o  bien, se pueden llamar desde otras partes del sistema. Por ejemplo se puede configurar un script para que se ejecute cuando se inicie el sistema; otro podría configurarse para funcionar todos los días de la semana a las 2:30 AM; otro podría ejecutarse cada vez que un usuario inicia sesión en el sistema.

Los scripts de shell se usan comúnmente para muchas tareas de administración del sistema, como realizar copias de seguridad en disco, evaluar registros del sistema, etc. También se utilizan comúnmente como scripts de instalación para programas complejos. Son especialmente adecuados para todo esto porque alejan la complejidad sin necesidad de casi hacer nada  si un script solo necesita ejecutar dos programas externos, puede ser un script de dos líneas, y si necesita toda la capacidad de toma de decisiones y poder de un lenguaje de programación imperativo Turing-completo, entonces puede tener eso también.

Cuando esté experimentando, es probable que le resulte útil consultar la documentación de varios comandos. Para los comandos que están integrados en Bash, puede usar el comando de ayuda incorporado(comando help ) ; por ejemplo, help echo “imprimirá” (es decir, mostrará) información sobre el comando echo incorporado. Para programas externos, es probable que sus páginas de manual estén instaladas en su sistema, en cuyo caso puede verlas a través del comando man (“manual”); por ejemplo, para información sobre el comando cp(“copiar”), puede escribir man cp . Además, la mayoría de los programas, cuando se ejecutan con el argumento --help , imprimirán información de ayuda; por ejemplo, cp --help proporciona casi tanta información como man cp . (Sin embargo, con muchos programas, el enfoque de ayuda no brinda tanta información como el enfoque del comando  man ).

 

close up code coding computer

Photo by Lorenzo Cafaro on Pexels.com

Hello world en bash script

Empecemos con un programa simple “hola mundo”:

  echo 'Hola mundo!'

Podemos escribir esto directamente en el indicador de Bash, o bien guardar esto como un archivo (por ejemplo, hello_world.sh ) y ejecutarlo escribiendo bash hello_world.sh en el indicador de Bash  o también  mendiante el comando Kash  (kash hello_world.sh  ).

En cualquier caso, se imprimirá Hello, world! :

 $ echo 'Hello, world!'  ¡Hola Mundo!

Aquí hemos utilizado el símbolo $ para indicar el indicador de Bash: después de $ , el resto de la línea muestra el comando que escribimos, y la siguiente línea muestra la salida del comando.

Aquí hay un script un poco más complejo:

  if [[ -e readme.txt ]] ;  then 
   echo 'El archivo "readme.txt" existe.'
 else
   echo 'El archivo "readme.txt" no existe.'
 fi

Esta secuencia de comandos comprueba si existe un archivo llamado readme.txt en el directorio actual y utiliza una instrucción if para controlar, en función de esa prueba, qué comandos se ejecutan. También se puede escribir directamente en el indicador de comandos (cualquier script), pero en este caso no es probable que sea útil.

Es decir todo lo  anterior es  completamente “dentro de Bash”, ya que no requiern que Bash ejecute ningún programa externo. (Los comandos echo , if ... then ... else ... fi , y [[-e ...]] son comandos incorporados, implementados por Bash.) Pero, al ser un lenguaje de shell-scripting, una gran parte del propósito de Bash es para ejecutar programas externos.

La siguiente secuencia de comandos demuestra esta capacidad:

  if [[ -e config.txt ]] ;  then
   echo 'El archivo "config.txt" ya existe.  Comparando con el predeterminado.  .  .'
   diff -u config-default.txt config.txt> config-diff.txt
   echo 'A diff se ha escrito en "config-diff.txt".'
 else
   echo 'El archivo "config.txt" no existe.  Copia por defecto.  .  .'
   cp config-default.txt config.txt
   echo '.  .  .  hecho.'
 fi

Aquí diff y cp son dos programas de utilidad comunes que, aunque no forman parte de Bash, se encuentran en la mayoría de los sistemas que tienen Bash.

La secuencia de comandos anterior asume la presencia de un archivo de configuración predeterminado llamado config-default.txt , y verifica la presencia de un archivo de configuración llamado config.txt . Si existe config.txt , entonces el script usa el programa externo diff para producir un “dif” (un informe de las diferencias entre, en este caso, dos archivos), para que el usuario pueda ver qué configuraciones no predeterminadas están en su lugar. . Si config.txt no existe, entonces el script usa el programa externo cp (“copiar”) para copiar el archivo de configuración predeterminado en config.txt .

Como puede ver, los programas externos se ejecutan utilizando el mismo tipo de sintaxis que los comandos incorporados ( ambos son solo “comandos”).

La versión anterior de este script es muy “detallada”, ya que genera una gran cantidad de resultados. Es probable que una secuencia de comandos más típica no incluya los comandos de eco , ya que es poco probable que los usuarios necesiten este nivel de información. En ese caso, podríamos usar la notación # para incluir comentarios que Bash ignore por completo y que no aparezcan al usuario. Tales comentarios son simplemente notas informativas para alguien que lee el script en sí:

  if [[ -e config.txt ]] ;  then
   # si config.txt existe:
   diff -u config-default.txt config.txt> config-diff.txt # ver qué ha cambiado
 else
   # si config.txt no existe:
   cp config-default.txt config.txt # toma el valor predeterminado
 fi

Pero lo anterior es simplemente por el bien de la demostración. En realidad, un script tan simple no requiere ningún comentario.

Comandos simples 

Un comando simple consiste en una secuencia de palabras separadas por espacios o tabulaciones. La primera palabra se toma como el nombre de un comando, y las palabras restantes se pasan como argumentos al comando. Ya hemos visto una serie de ejemplos de comandos simples

Aquí algunos comandos más  usuales:

  • cd ..
    • Este comando usa cd (“cambiar directorio”; un comando incorporado para navegar por el sistema de archivos) para navegar “hacia arriba” en un directorio.
    • La notación .. significa “directorio padre”. Por ejemplo, /foo/bar/../baz.txt es equivalente a /foo/baz.txt .
  • rm foo.txt bar.txt baz.txt
    • Suponiendo que el programa rm (“remove”) está instalado, este comando elimina los archivos foo.txt , bar.txt y baz.txt en el directorio actual.
    • Bash encuentra el programa rm buscando en una lista configurable de directorios un archivo llamado rm que sea ejecutable (según lo determinen sus permisos de archivos).
  • /foo/bar/baz bip.txt
    • Este comando ejecuta el programa ubicado en / foo / bar / baz , pasando bip.txt como único argumento.
    • / foo / bar / baz debe ser ejecutable (según lo determinado por sus permisos de archivo). Por cierto ,asegúrese de que NO HAY ESPACIO entre la barra inclinada y los archivos que la siguen:por ejemplo, asumiendo que la carpeta “foo” existe en el directorio “raíz”, luego ejecute el siguiente comando: “rm -r / foo” destruirá su computadora si se realiza con el acceso “sudo”. Usted ha sido advertido. Si no entiende lo anterior, no se preocupe por el momento.
    • Si / foo / bar / baz es un archivo de texto en lugar de un programa binario, y su primera línea comienza con #! , luego, el resto de esa línea determina el intérprete a usar para ejecutar el archivo. Por ejemplo, si la primera línea de / foo / bar / baz es #! / Bin / bash , entonces el comando anterior es equivalente a / bin / bash / foo / bar / baz bip.txt .

Ese ejemplo con / foo / bar / baz tiene una nota especial, ya que ilustra cómo se puede crear un script Bash que se pueda ejecutar como un programa ordinario: simplemente incluya #! / Bin / bash como la primera línea del script (suponiendo ahí es donde se encuentra Bash en su sistema; de lo contrario, ajuste según sea necesario) y asegúrese de que el script tenga los permisos de archivo correctos para ser legible y ejecutable. Para el resto des ejemplos de scripts de shell completos comenzarán con la línea #! / Bin / bash .

El problema con los espacios 

Vimos anteriormente que el comando rm foo.txt bar.txt baz.txt elimina tres archivos separados: foo.txt , bar.txt y baz.txt . Esto sucede porque Bash divide el comando en cuatro palabras separadas basadas en espacios en blanco, y tres de esas palabras se convierten en argumentos para el programa rm . Pero, ¿y si necesitamos eliminar un archivo cuyo nombre contiene un espacio?

Bash ofrece varios mecanismos de cotización que son útiles para este caso; las más utilizadas son comillas simples y comillas dobles.

Cualquiera de estos comandos eliminará un archivo llamado this file.txt :

  rm 'este archivo.txt'
  rm "este archivo.txt"

Dentro de las comillas, el carácter de espacio pierde su significado especial como separador de palabras. Normalmente envolvemos una palabra completa entre comillas, como se muestra arriba, pero de hecho, solo el espacio en sí necesita ser encerrado; este '' archivo.txt o este "" archivo.txt es equivalente a 'este archivo.txt' .

Otro mecanismo de citación comúnmente usado es la barra invertida \ , pero funciona de manera ligeramente diferente; Cita (o “escapa”) un solo carácter. Este comando, por lo tanto, es equivalente al anterior:

  rm este \ archivo.txt

En todos estos casos, los mismos caracteres de cita no se pasan al programa. (Esto se denomina eliminación de comillas ). Como resultado, rm no tiene forma de saber si se invocó, por ejemplo, como rm foo.txt o como rm 'foo.txt' .

Comodines de nombre de archivo y expansión de tilde

Bash admite una serie de notaciones especiales, conocidas como expansiones , para pasar tipos de argumentos de uso común a los programas.

Uno de ellos es la expansión del nombre de archivo , donde un patrón como * .txt se reemplaza con los nombres de todos los archivos que coinciden con ese patrón. Por ejemplo, si el directorio actual contiene los archivos foo.txt , bar.txt , este archivo.txt y algo.else , entonces este comando:

  echo * .txt

es equivalente a este comando:

  echo 'bar.txt' 'foo.txt' 'este archivo.txt'

Aquí el asterisco  significa “cero o más caracteres”; hay algunos otros caracteres de patrón especiales (como el signo de interrogación ?, que significa “exactamente un carácter”), y algunas otras reglas de coincidencia de patrón, pero este uso de  es, con mucho, el uso más común de patrones.

La expansión del nombre de archivo no se limita necesariamente a los archivos en el directorio actual. Por ejemplo, si queremos listar todos los archivos que coincidan con t * .sh dentro del directorio / usr / bin , podemos escribir esto:

  echo /usr/bin/t*.sh

que puede expandirse a algo como esto:

  echo /usr/bin/test.sh /usr/bin/time.sh

Si no hay archivos que coincidan con un patrón específico, no se realizará ninguna sustitución; por ejemplo, este comando:

  echo asfasefasef * avzxv

probablemente solo imprima asfasefasef * avzxv .

 

Si algún nombre de archivo comienza con un guión, entonces la expansión del nombre del archivo a veces puede tener consecuencias sorprendentes. Por ejemplo, si un directorio contiene dos archivos, llamados -n y tmp.txt , cat * se expande a cat -n tmp.txt , y cat interpretará -n como una opción en lugar de un nombre de archivo; en su lugar, es mejor escribir cat ./* o cat - * , que se expande a cat ./-n ./tmp.txt o cat - -n tmp.txt , eliminando este problema.

 

 

¿Qué sucede si tenemos un archivo real llamado * .txt al que queremos referirnos? (es decir  si los nombres de archivo pueden contener asteriscos). Pues podemos usar cualquiera de los estilos de cita que vimos anteriormente. Cualquiera de estos:

  cat '* .txt'
  gato "* .txt"

imprimirá el archivo real * .txt , en lugar de imprimir todos los archivos cuyos nombres terminen en .txt .

Otra expansión similar es la expansión de tilde . La expansión de tilde tiene muchas características, pero la principal es esta: en una palabra que consiste únicamente en una tilde ~ , o en una palabra que comienza con ~ / (tilde-barra), la tilde se reemplaza con la ruta completa a directorio de inicio del usuario actual. Por ejemplo, este comando:

  echo ~ / *. txt

imprimirá los nombres de todos los archivos nombrados * .txt en el directorio de inicio del usuario actual.

 

 

Uso de las llaves

Similar a la expansión de nombre de archivo es la expansión de refuerzo , que es una forma compacta de representar múltiples argumentos similares. Los siguientes cuatro comandos son equivalentes:

  ls file1.txt file2.txt file3.txt file4.txt file5.txt
  ls archivo { 1 , 2,3,4,5 } .txt
  ls archivo { 1 ..5..1 } .txt
  ls archivo { 1 ..5 } .txt

El primer comando enumera cada argumento explícitamente. Los otros tres comandos utilizan la expansión de refuerzo para expresar los argumentos de forma más concisa: en el segundo comando, se dan todas las posibilidades 1 a 5 , separadas por comas; en el tercer comando, se da una secuencia numérica (“de 1 a 5, incrementando en 1”); y el cuarto comando es el mismo que el tercero, pero deja el … 1 implícito.

También podemos listar los archivos en el orden opuesto:

  ls file5.txt file4.txt file3.txt file2.txt file1.txt
  ls archivo { 5 , 4,3,2,1 } .txt
  ls archivo { 5 ..1 ..- 1 } .txt
  ls archivo { 5 ..1 } .txt

siendo el tamaño de incremento predeterminado -1, cuando el punto final de la secuencia es menor que el punto de inicio.

Como en Bash, la primera palabra de un comando es el programa que se ejecuta, también podríamos escribir el comando de esta manera:

  { ls, archivo { 1 ..5 } .txt }

pero obviamente eso no es propicio para la legibilidad. (El mismo tipo de cosas, por cierto, se puede hacer con la expansión del nombre de archivo).

La expansión del refuerzo, como la expansión del nombre de archivo, se puede desactivar por cualquiera de los mecanismos de cotización; '{' , "{" , o \ { produce una llave literal real.

 

Redireccionando la  salida 

Bash permite que la salida estándar de un comando (descriptor de archivo 1) se envíe a un archivo, en lugar de a la consola. Por ejemplo, el programa de utilidad común cat escribe un archivo en una salida estándar; Si redirigimos su salida estándar a un archivo, tenemos el efecto de copiar el contenido de un archivo en otro archivo.

Si queremos sobrescribir el archivo de destino con la salida del comando, usamos esta notación:

  cat input.txt> output.txt

Si queremos mantener los contenidos existentes del archivo de destino como están y simplemente agregar la salida del comando al final, usamos esta notación:

  cat input.txt >> output.txt

Sin embargo, no todo lo que un programa escribe en la consola pasa por la salida estándar. Muchos programas utilizan el error estándar (descriptor de archivo 2) para los mensajes de error y algunos tipos de mensajes de “registro” o “canal lateral”. Si deseamos que el error estándar se combine con la salida estándar, podemos usar cualquiera de estas notaciones:

  cat input.txt y >> output.txt
  cat input.txt >> output.txt 2 > & 1

Si deseamos que el error estándar se adjunte a un archivo diferente de la salida estándar, usamos esta notación:

  cat input.txt >> output.txt 2 >> error.txt

De hecho, podemos redirigir solo el error estándar, dejando solo la salida estándar:

  cat input.txt 2 >> error.txt

En todos los ejemplos anteriores, podemos reemplazar >> con > si queremos sobrescribir el objetivo de redireccionamiento en lugar de agregarlo.

Más adelante veremos algunas cosas más avanzadas que podemos hacer con la redirección de salida.

Redireccionando la entrada 

Así como Bash permite que la salida de un programa se envíe a un archivo, también permite que la entrada de un programa se tome de un archivo. Por ejemplo, la utilidad común de Unix cat copia su entrada a su salida, de manera que este comando:

  cat <input.txt

Escribirá el contenido de input.txt en la consola.

Como ya hemos visto, este truco no es necesario en este caso, ya que a Cat se le puede pedir que copie un archivo específico a su salida; el comando anterior es simplemente equivalente a este:

  cat input.txt

Esta es la regla en lugar de la excepción; Las utilidades más comunes de Unix que pueden tomar entrada desde la consola también tienen la funcionalidad incorporada para tomar su entrada de un archivo en su lugar. De hecho, muchos, incluido cat , pueden recibir información de varios archivos, lo que los hace aún más flexibles que los anteriores. El siguiente comando imprime input1.txt seguido de input2.txt :

  cat input1.txt input2.txt

No obstante, la redirección de entrada tiene sus usos, algunos de los cuales veremos más adelante.

 

Tuberías (pipes) 

Una canalización  o  Pipe es una serie de comandos separados por el carácter de canalización | . Cada comando se ejecuta al mismo tiempo, y la salida de cada comando se usa como entrada para el siguiente comando.

Por ejemplo, considere esta tubería:

  cat input.txt |  grep foo |  grep -v bar

Ya hemos visto la utilidad de cat cat input.txt simplemente escribe el archivo input.txt en su salida estándar. El programa grep es una utilidad común de Unix que filtra (“greps”, en el lenguaje de Unix) según un patrón; por ejemplo, el comando grep foo imprimirá en su salida estándar cualquier línea de entrada que contenga la cadena foo . El comando grep -v bar usa la opción -v para invertir el patrón; El comando imprime las líneas de entrada que no contienen la barra de cadena. Dado que la entrada de cada comando es la salida del comando anterior, el resultado neto es que la canalización imprime cualquier línea de input.txt que contenga foo y no contenga barra .

Variables 

En un script de Bash, hay algunos tipos diferentes de parámetros que pueden contener valores . Un tipo importante de parámetro son las variables : parámetros nombrados. Si está familiarizado con casi cualquier otro lenguaje de programación imperativo (como C, BASIC, Fortran o Pascal), entonces ya está familiarizado con las variables. La siguiente secuencia de comandos simple utiliza la ubicación variable para mantener el mundo del valor, e imprime un “¡Hola, mundo!” mensaje:

  location = world # store "world" en la variable "location"
 echo "Hola, $ { ubicación } !"  # imprimir "¡Hola mundo!"

Como puede ver, la cadena $ {ubicación} en la segunda línea fue reemplazada por world antes de que se ejecutara ese comando. Esta sustitución se conoce como expansión variable , y es más flexible de lo que podría sospechar. Por ejemplo, incluso puede contener el nombre del comando para ejecutar:

  cmd_to_run = echo # store "echo" en la variable "cmd_to_run"
 " $ { cmd_to_run } " '¡Hola mundo!'  # imprimir "¡Hola mundo!"

En los dos ejemplos anteriores, hemos usado la notación $ { variable_name } para realizar la expansión de la variable. La notación más breve nombre_variable , sin llaves, tendría el mismo efecto en estos casos. A veces, los corchetes son necesarios (por ejemplo, $ {foo} bar no se puede escribir como $ foobar , porque este último se interpretaría como $ {foobar} ), pero generalmente se pueden omitir, y los guiones del mundo real por lo general los omiten .

Por supuesto, no hace falta decir que los anteriores no son ejemplos muy realistas; solo demuestran cómo usar las variables, no por qué o cuándo usarlas. Si está familiarizado con otros lenguajes de programación imperativos, entonces probablemente ya sea obvio por qué y cuándo usaría las variables; si no, entonces esto debería quedar claro a medida que lee este libro y vea ejemplos que los usan de manera más realista.

Es posible que haya notado que hemos usado comillas dobles " , en lugar de comillas simples cadenas de caracteres que incluyen expansiones variables':en general, es una buena idea envolver las expansiones variables entre comillas dobles; por ejemplo, use "$ var" en lugar de $ var .  . Esto se debe a que las comillas simples evitan la expansión de variables; un comando como echo' $ {location} ' imprimirá la cadena real $ {ubicación} , en lugar de imprimir el valor de una variable llamada ubicación .

En general, es una buena idea envolver las expansiones de las variables con comillas dobles, porque de lo contrario, los resultados de la expansión de las variables sufrirán la expansión del nombre de archivo, así como la división de palabras (donde se utiliza el espacio en blanco para separar las palabras que forman un comando). Por ejemplo, este script:

  foo = 'ab *' # tienda "ab *" en la variable "foo"
 echo $ foo

es probable que imprima algo como ba.txt bd.sh , que probablemente no sea lo que queremos. Las secuencias de comandos del mundo real con frecuencia no incluyen comillas dobles, excepto cuando son claramente necesarias, pero esta práctica a veces conduce a errores confusos.

Una serie de variables tienen un significado especial. Por ejemplo, la variable PATH determina la lista de directorios en los que Bash debería buscar cuando intenta ejecutar un programa externo; si está configurado en / usr / bin: / bin , entonces el comando cp src.txt dst.txt buscará ejecutar / usr / bin / cp o / bin / cp . La variable HOME está preinicializada en el directorio inicial del usuario actual y determina el comportamiento de la expansión de tilde. Por ejemplo, si una secuencia de comandos establece HOME = / foo , echo ~/bar imprimirá / foo / bar . (Sin embargo, esto no cambiará el directorio de inicio del usuario).

Parámetros posicionales 

En la mayoría de los comandos anteriores, tanto los que ejecutan un comando integrado como los que usan un programa externo, hemos suministrado uno o más argumentos , que indican en qué debe funcionar el comando. Por ejemplo, cuando invocamos la utilidad común de Unix mkdir (“make directory”) para crear un nuevo directorio, lo invocamos con un comando como este:

  mkdir tmp

donde tmp es el nombre del nuevo directorio para crear.

Y como hemos visto, los scripts de Bash son programas que pueden ejecutarse. Así que no hace falta decir que ellos también pueden tomar argumentos. Estos argumentos están disponibles para el programa como sus parámetros posicionales . Anteriormente, vimos que las variables son un tipo de parámetro. Los parámetros posicionales son muy similares, pero se identifican por números en lugar de por nombres. Por ejemplo, $ 1 (o $ {1} ) se expande al primer argumento del script. Supongamos que queremos crear un script simple llamado mkfile.sh que tome dos argumentos, un nombre de archivo y una línea de texto, y cree el archivo especificado con el texto especificado. Podemos escribirlo de la siguiente manera:

  #! / bin / bash
 echo " $ 2 " > " $ 1 "

(Observe la línea #!/bin/bash al principio del archivo; cubrimos esa línea en los comandos básicos . Cuando ejecute este código, esa línea garantizará que será interpretada por el shell Bash, incluso si está ejecutándose desde otro programa o su computadora tiene una configuración no estándar.)

y (después de hacerlo ejecutable ejecutando chmod + x mkfile.sh ) podemos ejecutarlo de la siguiente manera:

  ./mkfile.sh file-to-create.txt 'línea para poner en el archivo'

También podemos referirnos a todos los argumentos a la vez usando $ @ , que se expande a todos los parámetros posicionales, en orden. Cuando se envuelven en comillas dobles, como "$ @" , cada argumento se convierte en una palabra separada. (Nota: la alternativa $ * es quizás más común, pero "$ *" se convierte en una sola palabra, con espacios entre los parámetros originales. "$ @" Casi siempre es preferible a $ @ o $ * , lo que permite un argumento para dividirse en varias palabras si contiene espacios en blanco, y para "$ *" , que combina múltiples argumentos en una sola palabra.) Esto suele ser útil en concierto con el comando incorporado shift , que elimina el primer parámetro posicional, como que $ 2 se convierte en $ 1 , $ 3 se convierte en $ 2 , y así sucesivamente. Por ejemplo, si cambiamos mkfile.sh de la siguiente manera:

  #! / bin / bash
 file = " $ 1 " # guarda el primer argumento como "$ file"
 shift # suelta el primer argumento de "$ @"
 echo " $ @ " > " $ file " # escribe los argumentos restantes en "$ file"

entonces podemos ejecutarlo de la siguiente manera:

  ./mkfile.sh file-to-create.txt línea para poner en el archivo

y todos los argumentos, excepto el nombre de archivo, se escribirán en el archivo.

El número de parámetros posicionales está disponible como $ # ; por ejemplo, si $ # es 3 , entonces los parámetros posicionales son $ 1 , $ 2 y $ 3 .

Tenga en cuenta que los parámetros posicionales más allá de $ 9 requieren las llaves; Si necesita referirse al décimo argumento, por ejemplo, debe escribir $ {10} en lugar de $ 10 . (Este último se interpretaría como $ {1} 0 ). Dicho esto, no suele ser una buena idea tener tantos argumentos con significados específicos, ya que es difícil para los usuarios realizar un seguimiento de ellos. Si se encuentra específicamente refiriéndose al décimo argumento de su guión, puede valer la pena volver a evaluar su enfoque.

Si tiene alguna experiencia con Bash, o con las utilidades de Unix, lo más probable es que haya notado que muchos comandos pueden tomar varias “opciones”, indicadas con un guión principal, además de sus argumentos habituales. Por ejemplo, rm " $filename" elimina un archivo individual, mientras que rm -r " $dirname " elimina un directorio completo con todo su contenido. (La -r es la abreviatura de “recursivo”: el comando “recursivamente” elimina un árbol completo de directorios). Estas opciones son en realidad solo argumentos. En rm " $filename " , solo hay un argumento ( "$ filename" ), mientras que en rm -r " $dirname " hay dos ( -r y "$ dirname" ). En cierto sentido, no hay nada intrínsecamente especial en estos argumentos, pero esta notación para las opciones está tan extendida que se considera estándar; muchos o la mayoría de los comandos incorporados de Bash pueden aceptar varias opciones, y más adelante veremos varias técnicas para respaldar las opciones como argumentos para nuestros scripts de Bash.

Salida de un script 

Cuando se completa un proceso, devuelve un pequeño valor entero no negativo, llamado su estado de salida o su estado de retorno , al sistema operativo. Por convención, devuelve cero(es decir un exit 0  )  si se completó con éxito, y un número positivo si falló con un error ( es decir un exit <>0)  y de este modo se puede distinguir varios errores diferentes mediante el uso de diferentes números positivos

 Un script Bash puede obedecer esta convención mediante el uso del comando integrado exit . El siguiente comando:

  exit 4

termina el script de shell, devolviendo un estado de salida de cuatro, indicando algún tipo de error. Cuando no se especifica ningún estado de salida (ya sea porque la salida se ejecuta sin argumentos, o porque la secuencia de comandos finaliza sin llamar a exit ), la secuencia de comandos devuelve el estado de salida del último comando que ejecutó.

Una forma en que se utilizan los estados de salida es con los operadores Bash && (“y”) y || (“o”). Si dos comandos están separados por && , entonces el comando de la izquierda se ejecuta primero, y el comando de la derecha solo se ejecuta si el primer comando tiene éxito. Por el contrario, si están separados por || , entonces el comando de la derecha solo se ejecuta si el comando de la izquierda falla.

Por ejemplo, supongamos que queremos eliminar el archivo file.txt y volver a crearlo como un archivo en blanco. Podemos eliminarlo utilizando la utilidad común de Unix rm (“eliminar”), y volver a crearlo utilizando la utilidad común de Unix touch ; así, podríamos escribir esto:

  rm file.txt
 touch file.txt

Pero realmente, si rm falla, no queremos ejecutar el comando  touch : no queremos volver a crear el archivo si no pudimos eliminarlo para empezar. Entonces, podemos escribir esto en su lugar:

  rm file.txt && touch file.txt

Esto es lo mismo que antes, excepto que no intentará ejecutar touch a menos que rm haya tenido éxito.

Un tercer operador booleano similar, (“no”), invierte el estado de salida de un comando. Por ejemplo, este comando:

  ! rm file.txt

es equivalente a rm file.txt, excepto que indicará éxito si rm indica error, y viceversa. (Esto no suele ser útil cuando los estados de salida se usan como estados de salida reales, lo que indica éxito o fracaso, pero pronto veremos algunos usos extendidos de los estados de salida donde una operación “no” es más útil).

El estado de salida de un comando está (breve mente) disponible como $? .Esto puede ser útil cuando es necesario distinguir entre varios estados de falla diferentes; por ejemplo, el comando grep (que busca líneas en un archivo que coinciden con un patrón específico) devuelve 0 si encuentra una coincidencia, 1 si no encuentra coincidencias y 2 si se produce un error genuino.

emacs1

 

¿A  que no es tan difícil programar en c-shell?Le invitamos a que si tiene un terminal Unix o Linux ( por ejemplo una Raspberry Pi)   lo intente!seguro  que se le ocurren mil ideas interesantes que hacer !