Scripts Bash

De Jose Castillo Aliaga
Ir a la navegación Ir a la búsqueda

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

Otra manera de redirigir las dos salidas a un fichero.

$ 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.

$ 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
# or
read LINE <&3

# finally, close it
exec 3<&-


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)

Variables y argumentos

  • $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}
  • $# Candidad de argumentos
  • $* Todos los argumentos
  • $? : a 0 si el comando anterior ha terminado sin dar error. A 1 o más si ha dado error.
  • $_ : El primer argumento del comando anterior

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

Arrays

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]}.

Ejemplos de uso de arrays:

#!/bin/bash

area[11]=23
area[13]=37
area[51]=UFOs
# No hace falta que sean consecutivos.

echo -n "area[11] = "
echo ${area[11]}    #  Se necesitan {llaves}.

echo -n "area[13] = "
echo ${area[13]}

echo "Contents of area[51] are ${area[51]}."

area[5]=$((${area[11]}+${area[13]}))

area2=( zero one two three four )
# otra manera de inicializar un array
echo ${area2[0]}

area3=([17]=seventeen [24]=twenty-four)
# y otra manera, esta vez, indicando la posición de los elementos
echo ${area3[17]}

echo ${#area3[@]}
# Longitud del array

Un ejemplo práctico y error común puede producirse al querer guardar nombres de ficheros en un array:

$ ficheros=$(ls) # MAL. Se guarda en una variable una lista de ficheros, pero no un array
$ ficheros=($(ls)) # Todavía MAL. Si hay ficheros con espacios en sus nombres dará problemas.

$ files=(*)      # Bien!. El asterisco * permite guardar cada nombre de fichero en una posición del array, aunque tenga espacios.

Una manera de recorrer los elementos de un array:

$ for file in "${myfiles[@]}"; do
>     cp "$file" /backups/
> done
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.


http://stackoverflow.com/questions/1063347/passing-arrays-as-parameters-in-bash

Arrays

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

via: [3]

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