Diferencia entre revisiones de «Scripts Bash»
(→Arrays) |
(→Arrays) |
||
Línea 267: | Línea 267: | ||
En este caso, $ancho es la anchura que queremos simular en la matriz. | 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. | |||
http://stackoverflow.com/questions/1063347/passing-arrays-as-parameters-in-bash | |||
== Arrays == | == Arrays == |
Revisión del 15:01 28 may 2013
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
$ cmd 2>&1 >>ficheroPorque 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
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.
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"
via:[2]
Expresiones aritméticas
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://www.pixelbeat.org/programming/shell_script_mistakes.html
http://serverfault.com/questions/7503/how-to-determine-if-a-bash-variable-is-empty