Diferencia entre revisiones de «Scripts Bash»

De Jose Castillo Aliaga
Ir a la navegación Ir a la búsqueda
 
(No se muestran 31 ediciones intermedias del mismo usuario)
Línea 2: Línea 2:


La estructura básica de un script de Bash es:
La estructura básica de un script de Bash es:
 
<syntaxhighlight lang="bash">
  #!/bin/bash
  #!/bin/bash
  # Indicamos que se debe ejecutar con el shell bash
  # Indicamos que se debe ejecutar con el shell bash
Línea 11: Línea 11:
  exit 0
  exit 0
  # Con esta salida indicamos a otro posible script que lo invoque que ha terminado correctamente.
  # Con esta salida indicamos a otro posible script que lo invoque que ha terminado correctamente.
 
</syntaxhighlight>
== Redirecciones ==
== Redirecciones ==


  $ cmd > fichero
  $ cmd > fichero
Redirige la salida estandar a un fichero.
Redirige la salida estandar a un fichero.


  $ cmd 2> fichero
  $ cmd 2> fichero
Redirige la salida de error a un fichero.
Redirige la salida de error a un fichero.


  $ cmd >> fichero
  $ cmd >> fichero
Añade a un fichero la salida estandar sin borrar el contenido anterior.
Añade a un fichero la salida estandar sin borrar el contenido anterior.


  $ cmd 2>> fichero
  $ cmd 2>> fichero
Añade los errores al final de un fichero.
Añade los errores al final de un fichero.


  $ cmd &> fichero
  $ cmd &> fichero
Redirige la salida estandar y la de error a un fichero.
Redirige la salida estandar y la de error a un fichero.


  $ cmd > fichero 2>&1
  $ cmd > fichero 2>&1
{{nota|Ojo con  
{{nota|Ojo con  
  $ cmd 2>&1 >>fichero
  $ cmd 2>&1 >>fichero
Porque no funcionará, ya que evalúa de izquierda a derecha. Es decir, primero envíael error a la salida estándar 1 que es la terminal y luego la salida estándar la modifica por el fichero. Pero ya ha salido el error por pantalla}}
Porque no funcionará, ya que evalúa de izquierda a derecha. Es decir, primero envía el error a la salida estándar 1 que es la terminal y luego la salida estándar la modifica por el fichero. Pero ya ha salido el error por pantalla}}


Otra manera de redirigir las dos salidas a un fichero.  
Otra manera de redirigir las dos salidas a un fichero. En este caso, la estándar va al fichero y la de error va a la estándar.


  $ cmd > /dev/null
  $ cmd > /dev/null
Línea 48: Línea 42:


  $ cmd < fichero
  $ cmd < fichero
Redirige el contenido de un fichero a la entrada estandar del comando.
Redirige el contenido de un fichero a la entrada estandar del comando.


Línea 59: Línea 52:


  $ cmd <<< "palabra"
  $ cmd <<< "palabra"
Redirecciona el texto al comando. Se llama ''here-string''.
Redirecciona el texto al comando. Se llama ''here-string''.


  $ exec 2> fichero
  $ exec 2> fichero
 
Redirige la salida de error a un fichero para todos los comandos. El comando exec es uno de los llamados ''built-in'' de bash. Permite ejecutar un comando sustituyéndo al shell actual o, los que nos interesa ahora, gestionar las redirecciones.
Redirige la salida de error a un fichero para todos los comandos.
 
  $ exec 3< fichero
  $ exec 3< fichero
Abre un fichero para leer usando un nuevo descriptor.  
Abre un fichero para leer usando un nuevo descriptor.  


Línea 79: Línea 69:
Cierra un descriptor de fichero.
Cierra un descriptor de fichero.


<syntaxhighlight lang="bash">
  # open it
  # open it
  exec 3< input.txt
  exec 3< input.txt
   
   
  # for example: read one line from the file(-descriptor)
  # for example: read one line from the file(-descriptor)
  read -u 3 LINE
  read -u 3 LINE   # la opción -u en read permite seleccionar el descriptor de fichero
  # or
  # or
  read LINE <&3
  read LINE <&3
Línea 90: Línea 81:
  exec 3<&-
  exec 3<&-


</syntaxhighlight>
Si queremos unir las salidas de varios comandos seguidos, podemos usar las {}:
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
http://mywiki.wooledge.org/BashFAQ/032


=== read interactivo de ficheros ===
=== read interactivo de ficheros ===
Línea 96: Línea 94:


Observemos este script:
Observemos este script:
 
<syntaxhighlight lang="bash">
  #!/bin/bash  
  #!/bin/bash  
  # cada 10 linies espera a que l'usuari escriga alguna cosa
  # cada 10 linies espera a que l'usuari escriga alguna cosa
Línea 106: Línea 104:
  ((ContLin % 10)) > /dev/null || read  
  ((ContLin % 10)) > /dev/null || read  
  done < numeros  
  done < numeros  
 
</syntaxhighlight>
El resultado es este:
El resultado es este:


Línea 114: Línea 112:


Esto es porque el read de dentro del bucle lee también del fichero. Se puede forzar a que lea de la terminal:
Esto es porque el read de dentro del bucle lee también del fichero. Se puede forzar a que lea de la terminal:
 
<syntaxhighlight lang="bash">
  #!/bin/bash  
  #!/bin/bash  
  # cada 10 linies espera a que l'usuari escriga alguna cosa
  # cada 10 linies espera a que l'usuari escriga alguna cosa
Línea 124: Línea 122:
  ((ContLin % 10)) > /dev/null || read '''< /dev/tty'''
  ((ContLin % 10)) > /dev/null || read '''< /dev/tty'''
  done < numeros  
  done < numeros  
 
</syntaxhighlight>
O se puede crear un descriptor de fichero y modificar el read del while:
O se puede crear un descriptor de fichero y modificar el read del while:
 
<syntaxhighlight lang="bash">
  #!/bin/bash  
  #!/bin/bash  
  # cada 10 linies espera a que l'usuari escriga alguna cosa
  # cada 10 linies espera a que l'usuari escriga alguna cosa
   
   
  '''exec 3< numeros'''
  exec 3< numeros
  while read Num '''<&3'''
  while read Num '''<&3'''
  do  
  do  
Línea 137: Línea 135:
  ((ContLin % 10)) > /dev/null || read
  ((ContLin % 10)) > /dev/null || read
  done
  done
</syntaxhighlight>


=== Process Substitution ===
=== Process Substitution ===
Línea 146: Línea 145:
  $ diff <(ls $first_directory) <(ls $second_directory)
  $ diff <(ls $first_directory) <(ls $second_directory)


== Variables y argumentos ==
{{nota|Obsérvese que delante de los paréntesis no hay un $, si lo hubiera, el resultado lo pasaría literal, como una variable. De esta manera, el resultado de los ls lo encapsula en una especie fichero virtual temporal y así lo entiende el comando diff. Esto se puede ver si se lo pasamos al echo. Da un fichero situado en /dev/fd/ donde, casualmente, hay otros con el nombre de 1, 2, 255 ... esos son los descriptores de la salida estándar o de error. Los definidos por exec también se ven ahí.}}
 
== Parámetros, variables y argumentos ==
 
Un parámetro es una entidad referenciada por su nombre, un número o un carácter especial.


* '''$0''': Nombre del script.
* Si tiene nombre se llama '''Variable'''
* '''$1,$2,$3...$9''' Argumentos. En principio no se puede poner más de 9 argumentos. Si se quiere poner 10 o más, se puede recurrir al shift o poniéndolo como ${12}
* Si es un número se considera parámetros '''posicionales''' y tienen que ver con los '''argumentos''' que se le pasa a una función o script.
** '''$0''': Nombre del script.
** '''$1,$2,$3...$9''' Argumentos. En principio no se puede poner más de 9 argumentos. Si se quiere poner 10 o más, se puede recurrir al shift o poniéndolo como ${12}
* Si son caracteres especiales tienen diversos significados:
* '''$#''' Candidad de argumentos
* '''$#''' Candidad de argumentos
* '''$*''' Todos los argumentos
* '''$*''' Todos los argumentos. Es equivalente a $@ si se ponen sin dobles comillas. Con dobles comillas, $* son todos los argumentos seguidos y como separador el primer caracter del $IFS y $@ son todos los argumentos separados.
* '''$?''' : a 0 si el comando anterior ha terminado sin dar error. A 1 o más si ha dado error.  
$ function f(){ IFS='-'; echo $*; echo "$*"; echo $@; echo "$@";}; f 2 3 4 5 "6 7"
* '''$_''' : El primer argumento del comando anterior
* '''$?''' : a 0 si el comando anterior ha terminado con éxito. A 1 o más si ha dado erroro a terminado sin éxito. Es el '''Exit Status'''
* '''$_''' : El último argumento del comando anterior
* '''$$''' : El PID del proceso actual
* '''$!''' : El PID del último proceso ejecutado en background.
$ sleep 1000 & ps; echo -e "\nProceso actual: $$\nProceso en background: $!";
* '''$-''' : La lista de opciones del proceso actual.
 
 


===shift===
===shift===
Línea 160: Línea 173:


En este ejemplo se ve bastante bien. [http://www.ehu.es/ehusfera/davidfernandez/2010/03/01/bash-captura-de-parametros-en-un-script/ Via]
En este ejemplo se ve bastante bien. [http://www.ehu.es/ehusfera/davidfernandez/2010/03/01/bash-captura-de-parametros-en-un-script/ Via]
 
<syntaxhighlight lang="bash">
  while test -n "$1"; do
  while test -n "$1"; do
     case "$1" in
     case "$1" in
Línea 186: Línea 199:
     shift
     shift
  done
  done
 
</syntaxhighlight>
El while recorre todos los argumentos, aunque sólo comprueba el $1 en cada iteración, se queda con $2 y pasa al siguiente. De esta manera, el órden de los argumentos y sus opciones no es relevante.
El while recorre todos los argumentos, aunque sólo comprueba el $1 en cada iteración, se queda con $2 y pasa al siguiente. De esta manera, el órden de los argumentos y sus opciones no es relevante.


Línea 195: Línea 208:


El shell se encarga de sustituir los caracteres de sustitución y las variables antes de pasar los argumentos a un comando o script. Esto se puede ver con este script:
El shell se encarga de sustituir los caracteres de sustitución y las variables antes de pasar los argumentos a un comando o script. Esto se puede ver con este script:
 
<syntaxhighlight lang="bash">
  #!/bin/bash  
  #!/bin/bash  
   
   
  echo $*
  echo $*
  echo $#
  echo $#
 
</syntaxhighlight>
Si se invoca de estas maneras, por ejemplo:
Si se invoca de estas maneras, por ejemplo:
  $ ./argumentos *
  $ ./argumentos *
Línea 207: Línea 220:
  $ ./argumentos $HOME
  $ ./argumentos $HOME


=== Arrays ===
A esto se le llama '''Expansión de parámetros'''. Se pueden hacer muchas cosas manipulando esta expansión:


Las nuevas versiones de Bash soportan matrices unidimensionales. Los elementos de matriz se pueden inicializar con la notación variable[xx].


Alternativamente, un script puede introducir todo el conjunto con una declaración explícita de variables. Para recuperar el contenido de un elemento del array, se usan las llaves y corchetes de la siguiente manera: ${elemento[xx]}.
Uso simple:
 
*$PARAMETER
Ejemplos de uso de arrays:
*${PARAMETER}
 
Indirección:
#!/bin/bash
*${!PARAMETER}
   
  $ a=100
area[11]=23
  $ b=a
  area[13]=37
  $ echo "$b=${!b}"
area[51]=UFOs
Modificación de mayúsculas:
# No hace falta que sean consecutivos.
*${PARAMETER^} # La primera
*${PARAMETER^^} # todas
  echo -n "area[11] = "
*${PARAMETER,} # la primera en minúscula
echo ${area[11]}   #  Se necesitan {llaves}.
*${PARAMETER,,} # todas
*${PARAMETER~} # invertir
echo -n "area[13] = "
*${PARAMETER~~}
echo ${area[13]}
Expansión del nombre de la variable:
*${!PREFIX*} # todas las variables que tengan ese prefijo
echo "Contents of area[51] are ${area[51]}."
*${!PREFIX@}
Quitar substrings:
area[5]=$((${area[11]}+${area[13]}))
*${PARAMETER#PATTERN} #Quita el prefijo más corto que cumpla el patrón
*${PARAMETER##PATTERN} # Quita el prefijo más largo que cumpla el patrón
area2=( zero one two three four )
*${PARAMETER%PATTERN} # Quita sufijo más corto
# otra manera de inicializar un array
*${PARAMETER%%PATTERN} # quita el sufijo más largo
echo ${area2[0]}
Buscar y reemplazar
*${PARAMETER/PATTERN/STRING} # Una vez
area3=([17]=seventeen [24]=twenty-four)
*${PARAMETER//PATTERN/STRING} # Todas las ocurrencias
# y otra manera, esta vez, indicando la posición de los elementos
*${PARAMETER/PATTERN} # Lo quita una vez
echo ${area3[17]}
*${PARAMETER//PATTERN} # Lo quita todas las ocurrencias
Longitud de la cadena
echo ${#area3[@]}
*${#PARAMETER}
# Longitud del array
Substrings:
 
*${PARAMETER:OFFSET}
Un ejemplo práctico y error común puede producirse al querer guardar nombres de ficheros en un array:
*${PARAMETER:OFFSET:LENGTH}
 
Usar valor por defecto
$ ficheros=$(ls) # MAL. Se guarda en una variable una lista de ficheros, pero no un array
*${PARAMETER:-WORD} # Si es nulo o vacío
$ ficheros=($(ls)) # Todavía MAL. Si hay ficheros con espacios en sus nombres dará problemas.
*${PARAMETER-WORD} # Si es nulo. Si la variable está vacía no imprime el valor por defecto.
Asignar valor por defecto
$ files=(*)      # Bien!. El asterisco * permite guardar cada nombre de fichero en una posición del array, aunque tenga espacios.
*${PARAMETER:=WORD} # Si es nulo o vacío.
 
*${PARAMETER=WORD} # Sólo asigna valor si es nulo.
Una manera de recorrer los elementos de un array:
Valor alternativo
 
*${PARAMETER:+WORD} # Si está declarada y con contenido da un valor alternativo.
$ for file in "${myfiles[@]}"; do
*${PARAMETER+WORD} # Da un valor alternativo sólo si está declarada aunque su contenido sea nulo .
>    cp "$file" /backups/
Error si no está declarado
> done
*${PARAMETER:?WORD} # Error si no está declarado o es nulo.
 
*${PARAMETER?WORD} # error si no está declarado, si es nulo no da error.
{{nota|Hay que usar la @ y las " " para tener todos los elementos separados entre sí aunque tengan espacios. Si usamos un asterisco dará problemas si algún elemento tiene espacio.}}
 
Es posible simular los arrays multidimensionales con bash. Lo que en [[c]] o [[java]] podríamos así:
 
matriz[3][5]=34;
 
En Bash lo ponemos así:
 
$ matriz[5*$ancho+3]=34
 
En este caso, $ancho es la anchura que queremos simular en la matriz.
 
'''Arrays como parámetros'''
 
Si pasamos un array como parámetro, una función puede interpretarlo como una sucesión de parámetros, algo que no entiende o una cadena con espacios, pero no un array:
 
#!/bin/bash
func()
{
echo $1
echo $@
}  
a=(1 2 3 4)
func $a # MAL, retorna 1
func ${a[@]} # MAL, retorna 1
func "${a[@]}" # MAL, retorna 1
 
Ninguna de las anteriores opciones han servido para pasar correctamente el array. Podríamos usar $@ si sólo enviamos un array, pero si queremos enviar dos o más, nos dará problemas.
 
#!/bin/bash
func()
{
array=("${!1}")
echo ${array[@]}
}  
a=(1 2 3 4)
func $a # MAL
func ${a[@]} # MAL
func "${a[@]}" # MAL
func 'a[@]' # Bien
 
Lo que ha pasado es que le pasamos el nombre del array con la @, entonces el ! expande la variable $1 antes de interpretarla. Es decir, ("${!1}") se convierte en ("${a[@]}") que se interpreta como (1 2 3 4) y se le pasa a la variable nueva array.
 
Observa porqué pasa esto:
 
$ baba=booba
$ variable=baba
$ echo ${variable} # baba
$ echo ${!variable} # booba
 
Se trata de una ''referencia indirecta''.  
 
¿Cual es el problema?
 
<div class="toccolours mw-collapsible mw-collapsed" style="overflow: hidden;">
El problema es que a la función se le pasa el nombre de la variable, no su valor. En realidad ¿Qué diferencia hay con eso y con usarla directamente?
array=("${a[@]}")
En cualquier caso hay otro problema. Si el array no está completo, es decir, hay huecos entre los índices, el array resultante quedará condensado y no conservará esta peculiaridad.
 
Estudiemos este ejemplo:
 
#!/bin/bash
ar=( a b c )
ar[10]=10
test() {
    local ref=$1[@]
    echo ${!ref}
    local array=("${!ref}")
    echo ${array[10]} # no va
    echo ${array[3]}
    local f=$1[10]
    echo ${!f}
    eval $f=5
    echo ${!f}
}
test ar
echo ${ar[10]}
 
Es otra manera de enviarlo de como referencia indirecta. El último echo se usa para mostrar que, realmente, siempre estamos trabajando con el mismo array.
 
 
</div>


== Arrays ==


 
Ver [[Arrays en Bash]]
http://stackoverflow.com/questions/1063347/passing-arrays-as-parameters-in-bash
 
http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
 
== Arrays ==


== Tests ==
== Tests ==
Línea 468: Línea 387:
  a=$((a+7))        # POSIX-compatible version of previous code.
  a=$((a+7))        # POSIX-compatible version of previous code.
  if test $((a%4)) = 0; then ...
  if test $((a%4)) = 0; then ...
Las expressions aritmèticas tambien aceptan resultados condicionales:
echo $((2<3?4:5))
No se recomienda usar $[...] porque se considera obsoleta.
via: [http://mywiki.wooledge.org/ArithmeticExpression]
via: [http://mywiki.wooledge.org/ArithmeticExpression]
== if ==
El if en bash lo que hace es evaluar la variable $? resultante de la ejecución de un comando. Por tanto, el if se puede poner antes de cualquier comando.
{{nota|$? es una variable que guarda un 0 si el comando anterior se ha ejecutado con éxito (no es lo mismo que sin errores) y un 1 o más si ha finalizado sin éxito, ya sea por un error o porque no ha dado resultado}}
Ejecuta esto después de un comando exitoso o no exitoso:
<syntaxhighlight lang="bash">
$ echo -e ":\\0$(($??50:51))";
</syntaxhighlight>
Para hacer un if tradicional como en [[C]] que evalúa una expresión lógica se puede hacer con los comandos [] o [[]]. El [ es un comando que tiene hasta manual. Es otra forma del comando test, pero más parecido sintácticamente a otros lenguajes de programación.
Así, se pueden hacer muchos if:
<syntaxhighlight lang="bash">
$ if test 1 -le 3; then echo "1 es menor que 3"; fi
$ if [ 1 -le 3 ]; then echo "1 es menor que 3"; fi
$ if [[ 1 -le 3 ]]; then echo "1 es menor que 3"; fi
$ if grep lliurex /etc/passwd > /dev/null; then echo "El usuario Lliurex existe"; fi
</syntaxhighlight>
En ocasiones se puede evitar el if, uniendo comandos con '''&&''' o '''||'''. En el caso de &&, el segundo comando sólo se ejecuta si el primero ha acabado con éxito. En el caso de || sólo se ejecuta si el primero ha terminado sin éxito.
Estos son los comandos anteriores sin if:
<syntaxhighlight lang="bash">
$ test 1 -le 3 && echo "1 es menor que 3"
$ [ 1 -le 3 ] && echo "1 es menor que 3"
$ [[ 1 -le 3 ]] && echo "1 es menor que 3"
$ grep lliurex /etc/passwd > /dev/null && echo "El usuario Lliurex existe"
</syntaxhighlight>
== while ==
El comportamiento es igual que el if, evalúa el éxito del comando que va a continuación.
== for ==
Este comando se comporta de manera distinta a lenguajes como C. El for de bash sólo recorre una lista que le has pasado. Por ejemplo:
$ for i in 1 2 3; do echo $i; done
Si queremos un rango más ámplio se puede usar {..}:
$ for i in {1..1000}; do echo $i; done
Si queremos aumentar la i de 2 en 2, por ejemplo:
$ for i in $(seq 1 2 1000); do echo $i; done
Si necesitamos una sintaxis parecida a C:
$ for ((i=0;i<10;i++)); do echo $i; done
==Funciones==
ftp://ftp.monash.edu.au/pub/linux/docs/LDP/abs/html/functions.html
http://blog.joncairns.com/2013/08/what-you-need-to-know-about-bash-functions/


== Enlaces ==
== Enlaces ==
Línea 479: Línea 461:


http://serverfault.com/questions/7503/how-to-determine-if-a-bash-variable-is-empty
http://serverfault.com/questions/7503/how-to-determine-if-a-bash-variable-is-empty
http://www.kfirlavi.com/blog/2012/11/14/defensive-bash-programming
http://tldp.org/LDP/abs/html/writingscripts.html
http://mywiki.wooledge.org/BashPitfalls
https://lucasfcosta.com/2019/04/07/streams-introduction.html

Revisión actual - 09:42 15 abr 2019

Estructura de un script

La estructura básica de un script de Bash es:

 #!/bin/bash
 # Indicamos que se debe ejecutar con el shell bash
 
 # comandos del script
 # Tenemos a nuestra disposición las variables $1, $2.. $*, $#, $@, $0
 
 exit 0
 # Con esta salida indicamos a otro posible script que lo invoque que ha terminado correctamente.

Redirecciones

$ cmd > fichero

Redirige la salida estandar a un fichero.

$ cmd 2> fichero

Redirige la salida de error a un fichero.

$ cmd >> fichero

Añade a un fichero la salida estandar sin borrar el contenido anterior.

$ cmd 2>> fichero

Añade los errores al final de un fichero.

$ cmd &> fichero

Redirige la salida estandar y la de error a un fichero.

$ cmd > fichero 2>&1
Ojo con
$ cmd 2>&1 >>fichero
Porque no funcionará, ya que evalúa de izquierda a derecha. Es decir, primero envía el error a la salida estándar 1 que es la terminal y luego la salida estándar la modifica por el fichero. Pero ya ha salido el error por pantalla

Otra manera de redirigir las dos salidas a un fichero. En este caso, la estándar va al fichero y la de error va a la estándar.

$ cmd > /dev/null
$ cmd 2> /dev/null
$ cmd &> /dev/null

Descartar la salida estándar, la de error o las dos.

$ cmd < fichero

Redirige el contenido de un fichero a la entrada estandar del comando.

$ cmd << FIN 
linea1 
linea2 
FIN 

Redirige una serie de líneas a un comando. Esto se llama Here-Document. La palabra FIN sólo es para indicar el final de las líneas y puede ser cualquier otra.

$ cmd <<< "palabra"

Redirecciona el texto al comando. Se llama here-string.

$ exec 2> fichero

Redirige la salida de error a un fichero para todos los comandos. El comando exec es uno de los llamados built-in de bash. Permite ejecutar un comando sustituyéndo al shell actual o, los que nos interesa ahora, gestionar las redirecciones.

$ exec 3< fichero

Abre un fichero para leer usando un nuevo descriptor.

$ exec 3> fichero 

Abre un fichero para escribir usando un nuevo descriptor.

$ exec 3<> fichero 

Abre un fichero para leer o escribir usando el descriptor 3.

$ exec 3>&-

Cierra un descriptor de fichero.

 # open it
 exec 3< input.txt
 
 # for example: read one line from the file(-descriptor)
 read -u 3 LINE   # la opción -u en read permite seleccionar el descriptor de fichero
 # or
 read LINE <&3
 
 # finally, close it
 exec 3<&-

Si queremos unir las salidas de varios comandos seguidos, podemos usar las {}:

ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )

http://mywiki.wooledge.org/BashFAQ/032

read interactivo de ficheros

$ seq 30 > numeros

Observemos este script:

 #!/bin/bash 
 # cada 10 linies espera a que l'usuari escriga alguna cosa
 
 while read Num 
 do 
 	let ContLin++ # Contando... 
 	echo -n "$Num " # -n para no saltar línea 
 	((ContLin % 10)) > /dev/null || read 
 done < numeros

El resultado es este:

1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 18 19 20 21 23 24 25 26 27 28 29 30

Y no se espera a que el usuario ponga nada.

Esto es porque el read de dentro del bucle lee también del fichero. Se puede forzar a que lea de la terminal:

 #!/bin/bash 
 # cada 10 linies espera a que l'usuari escriga alguna cosa
 
 while read Num 
 do 
 	let ContLin++ # Contando... 
 	echo -n "$Num " # -n para no saltar línea 
 	((ContLin % 10)) > /dev/null || read '''< /dev/tty'''
 done < numeros

O se puede crear un descriptor de fichero y modificar el read del while:

 #!/bin/bash 
 # cada 10 linies espera a que l'usuari escriga alguna cosa
 
 exec 3< numeros
 while read Num '''<&3'''
 do 
 	let ContLin++ # Contando... 
 	echo -n "$Num " # -n para no saltar línea 
 	((ContLin % 10)) > /dev/null || read
 done

Process Substitution

Los comandos que necesitan un fichero, pueden ejecutarse con el |, por ejemplo el wc puede hacerse:

$ cat /etc/passwd | wc -l

A veces un comando acepta como entrada dos ficheros. En ese caso, el ejemplo anterior no se puede seguir. Para solucionarse, se pueden usar los () después de una < [1]

$ diff <(ls $first_directory) <(ls $second_directory)
Obsérvese que delante de los paréntesis no hay un $, si lo hubiera, el resultado lo pasaría literal, como una variable. De esta manera, el resultado de los ls lo encapsula en una especie fichero virtual temporal y así lo entiende el comando diff. Esto se puede ver si se lo pasamos al echo. Da un fichero situado en /dev/fd/ donde, casualmente, hay otros con el nombre de 1, 2, 255 ... esos son los descriptores de la salida estándar o de error. Los definidos por exec también se ven ahí.

Parámetros, variables y argumentos

Un parámetro es una entidad referenciada por su nombre, un número o un carácter especial.

  • Si tiene nombre se llama Variable
  • Si es un número se considera parámetros posicionales y tienen que ver con los argumentos que se le pasa a una función o script.
    • $0: Nombre del script.
    • $1,$2,$3...$9 Argumentos. En principio no se puede poner más de 9 argumentos. Si se quiere poner 10 o más, se puede recurrir al shift o poniéndolo como ${12}
  • Si son caracteres especiales tienen diversos significados:
  • $# Candidad de argumentos
  • $* Todos los argumentos. Es equivalente a $@ si se ponen sin dobles comillas. Con dobles comillas, $* son todos los argumentos seguidos y como separador el primer caracter del $IFS y $@ son todos los argumentos separados.
$ function f(){ IFS='-'; echo $*; echo "$*"; echo $@; echo "$@";}; f 2 3 4 5 "6 7"
  • $? : a 0 si el comando anterior ha terminado con éxito. A 1 o más si ha dado erroro a terminado sin éxito. Es el Exit Status
  • $_ : El último argumento del comando anterior
  • $$ : El PID del proceso actual
  • $! : El PID del último proceso ejecutado en background.
$ sleep 1000 & ps; echo -e "\nProceso actual: $$\nProceso en background: $!"; 
  • $- : La lista de opciones del proceso actual.


shift

Este comando permite usar un mismo número de argumento para ir recorriéndolos.

En este ejemplo se ve bastante bien. Via

 while test -n "$1"; do
    case "$1" in
        -a)
            opciona=$2
            shift
            ;;
        -b)
            opcionb=$2
            shift
            ;;
        -c)
            opcionc=$2
            shift
            ;;
        -d)
            opciond=$2
            shift
            ;;
        *)
            echo "Unknown argument: $1"
            exit 0
            ;;
    esac
    shift
 done

El while recorre todos los argumentos, aunque sólo comprueba el $1 en cada iteración, se queda con $2 y pasa al siguiente. De esta manera, el órden de los argumentos y sus opciones no es relevante.

# miscript -a opciona -b opcionb -d opciond
# miscript -b opcionb -c opcionc

Interpretación de los argumentos por el shell

El shell se encarga de sustituir los caracteres de sustitución y las variables antes de pasar los argumentos a un comando o script. Esto se puede ver con este script:

 #!/bin/bash 
 
 echo $*
 echo $#

Si se invoca de estas maneras, por ejemplo:

$ ./argumentos *
$ ./argumentos p[a-z]
$ ./argumentos {1..100}
$ ./argumentos $HOME

A esto se le llama Expansión de parámetros. Se pueden hacer muchas cosas manipulando esta expansión:


Uso simple:

  • $PARAMETER
  • ${PARAMETER}

Indirección:

  • ${!PARAMETER}
$ a=100
$ b=a
$ echo "$b=${!b}"

Modificación de mayúsculas:

  • ${PARAMETER^} # La primera
  • ${PARAMETER^^} # todas
  • ${PARAMETER,} # la primera en minúscula
  • ${PARAMETER,,} # todas
  • ${PARAMETER~} # invertir
  • ${PARAMETER~~}

Expansión del nombre de la variable:

  • ${!PREFIX*} # todas las variables que tengan ese prefijo
  • ${!PREFIX@}

Quitar substrings:

  • ${PARAMETER#PATTERN} #Quita el prefijo más corto que cumpla el patrón
  • ${PARAMETER##PATTERN} # Quita el prefijo más largo que cumpla el patrón
  • ${PARAMETER%PATTERN} # Quita sufijo más corto
  • ${PARAMETER%%PATTERN} # quita el sufijo más largo

Buscar y reemplazar

  • ${PARAMETER/PATTERN/STRING} # Una vez
  • ${PARAMETER//PATTERN/STRING} # Todas las ocurrencias
  • ${PARAMETER/PATTERN} # Lo quita una vez
  • ${PARAMETER//PATTERN} # Lo quita todas las ocurrencias

Longitud de la cadena

  • ${#PARAMETER}

Substrings:

  • ${PARAMETER:OFFSET}
  • ${PARAMETER:OFFSET:LENGTH}

Usar valor por defecto

  • ${PARAMETER:-WORD} # Si es nulo o vacío
  • ${PARAMETER-WORD} # Si es nulo. Si la variable está vacía no imprime el valor por defecto.

Asignar valor por defecto

  • ${PARAMETER:=WORD} # Si es nulo o vacío.
  • ${PARAMETER=WORD} # Sólo asigna valor si es nulo.

Valor alternativo

  • ${PARAMETER:+WORD} # Si está declarada y con contenido da un valor alternativo.
  • ${PARAMETER+WORD} # Da un valor alternativo sólo si está declarada aunque su contenido sea nulo .

Error si no está declarado

  • ${PARAMETER:?WORD} # Error si no está declarado o es nulo.
  • ${PARAMETER?WORD} # error si no está declarado, si es nulo no da error.

Arrays

Ver Arrays en Bash

Tests

El [ para hacer test se puede usar en todos los shell que cumplen con POSIX. Mientras que el doble [[ está en bash y otros shells modernos.

#POSIX
[ "$variable" ] || echo 'variable is unset or empty!' >&2
[ -f "$filename" ] || printf 'File does not exist: %s\n' "$filename" >&2

Saber más:

$ whereis [
[: /usr/bin/[ /usr/share/man/man1/[.1.gz
$ man [

Los dobles [[ no son un comando, sino que forman parte del bash.

Funciones

Función Expresión Ejemplo
Comparación de strings < , > , = , == !=
 [[ a > b ]] || echo "a does not come before b" 
 [[ az < za ]] && echo "az comes before za"
Comparación de integers -ge , -gt , -lt , -le , -eq , -ne
 [[ 6 -ne 20 ]] && echo "6 is not equal to 20"
Condicional || , &&
 [[ -n $var && -f $var ]] && echo "$var is a file"
Agrupar (...) (obsoleto)
 [[ $var = img* && ($var = *.png || $var = *.jpg) ]] && echo "$var starts with img and ends with .jpg or .png"
Patrones = o ==
 [[ $name = a* ]] || echo "name does not start with an 'a': $name"
Expresión regular =~
 [[ $(date) =~ ^Fri\ ...\ 13 ]] && echo "It's Friday the 13th"
Negación !
 [[ ! -u $file ]] && echo "$file is not a setuid file"

Ejemplos con ficheros:

[[ -e $config ]] && echo "config file exists: $config"
[[ $file0 -nt $file1 ]] && echo "$file0 is newer than $file1"
[[ $input -ef $output ]] && { echo "will not overwrite input file: $input"; exit 1; }

Se recomienda usar [[ si el nombre de los ficheros puede tener espacios en blanco:

file="file name"
 [[ -f $file ]] && echo "$file is a file"


Como norma general se recomienda usar [[ para ficheros y cadenas. Para números se puede usar expresiones aritméticas con(())

via:[2]

Expresiones aritméticas

Bash sólo puede trabajar con enteros. Si quieres cálculos en coma flotante puedes usar bc

El comando típico para hacer operaciones matemáticas es let

let a=17+23
let a="17 + 23"

Pero es más útil usar (()), ya que puedes usar un $ delante $(()). Permite usar espacios en blanco y no necesita usar $ en las variables porque no están permitidas cadenas dentro.

((a=$a+7))         # Add 7 to a
((a = a + 7))      # Add 7 to a.  Identical to the previous command.
((a += 7))         # Add 7 to a.  Identical to the previous command.

((a = RANDOM % 10 + 1))     # Choose a random number from 1 to 10.
                           # % is modulus, as in C.

# (( )) may also be used as a command.  > or < inside (( )) means
# greater/less than, not output/input redirection.
if ((a > 5)); then echo "a is more than 5"; fi

Los (()) sin $ delante sólo va en bash. Los siguientes ejemplos son compatibles con todos los POSIX

a=$((a+7))         # POSIX-compatible version of previous code.
if test $((a%4)) = 0; then ...

Las expressions aritmèticas tambien aceptan resultados condicionales:

echo $((2<3?4:5))

No se recomienda usar $[...] porque se considera obsoleta.

via: [3]

if

El if en bash lo que hace es evaluar la variable $? resultante de la ejecución de un comando. Por tanto, el if se puede poner antes de cualquier comando.

$? es una variable que guarda un 0 si el comando anterior se ha ejecutado con éxito (no es lo mismo que sin errores) y un 1 o más si ha finalizado sin éxito, ya sea por un error o porque no ha dado resultado

Ejecuta esto después de un comando exitoso o no exitoso:

 $ echo -e ":\\0$(($??50:51))";

Para hacer un if tradicional como en C que evalúa una expresión lógica se puede hacer con los comandos [] o [[]]. El [ es un comando que tiene hasta manual. Es otra forma del comando test, pero más parecido sintácticamente a otros lenguajes de programación.


Así, se pueden hacer muchos if:

$ if test 1 -le 3; then echo "1 es menor que 3"; fi
$ if [ 1 -le 3 ]; then echo "1 es menor que 3"; fi
$ if [[ 1 -le 3 ]]; then echo "1 es menor que 3"; fi
$ if grep lliurex /etc/passwd > /dev/null; then echo "El usuario Lliurex existe"; fi

En ocasiones se puede evitar el if, uniendo comandos con && o ||. En el caso de &&, el segundo comando sólo se ejecuta si el primero ha acabado con éxito. En el caso de || sólo se ejecuta si el primero ha terminado sin éxito.

Estos son los comandos anteriores sin if:

$ test 1 -le 3 && echo "1 es menor que 3"
$ [ 1 -le 3 ] && echo "1 es menor que 3"
$ [[ 1 -le 3 ]] && echo "1 es menor que 3"
$ grep lliurex /etc/passwd > /dev/null && echo "El usuario Lliurex existe"

while

El comportamiento es igual que el if, evalúa el éxito del comando que va a continuación.

for

Este comando se comporta de manera distinta a lenguajes como C. El for de bash sólo recorre una lista que le has pasado. Por ejemplo:

$ for i in 1 2 3; do echo $i; done

Si queremos un rango más ámplio se puede usar {..}:

$ for i in {1..1000}; do echo $i; done

Si queremos aumentar la i de 2 en 2, por ejemplo:

$ for i in $(seq 1 2 1000); do echo $i; done

Si necesitamos una sintaxis parecida a C:

$ for ((i=0;i<10;i++)); do echo $i; done

Funciones

ftp://ftp.monash.edu.au/pub/linux/docs/LDP/abs/html/functions.html http://blog.joncairns.com/2013/08/what-you-need-to-know-about-bash-functions/

Enlaces

http://mywiki.wooledge.org/BashGuide/Practices

http://tldp.org/LDP/abs/html/

http://www.pixelbeat.org/programming/shell_script_mistakes.html

http://serverfault.com/questions/7503/how-to-determine-if-a-bash-variable-is-empty

http://www.kfirlavi.com/blog/2012/11/14/defensive-bash-programming

http://tldp.org/LDP/abs/html/writingscripts.html

http://mywiki.wooledge.org/BashPitfalls

https://lucasfcosta.com/2019/04/07/streams-introduction.html