Manual de Bash

Lo más básico

Al shell Bash, el intérprete de comandos estándar en Linux, le podemos pasar comandos desde una terminal o desde un script, que es un archivo de texto que contiene un pequeño programa.

‘Hola mundo’ en Bash

El script más sencillo es Hola mundo. Veamos cómo crearlo:

  • crearemos el script con un editor de texto. En Linux disponemos de numerosos editores avanzados orientados a programación, con resaltado de sintaxis, como Gedit, Kwrite, Kate, Scite, etc.
  • llamaremos a nuestro script hello.sh. A los scripts del shell se les suele poner la extensión .sh para identificarlos más fácilmente.
  • en un script podemos incluir cualquier comando que se pueda ejecutar en el intérprete de comandos. En nuestro caso, el contenido de hello.sh será el siguiente:
    #!/bin/bash
    echo "Hola, mundo."
  • la primera línea indica la aplicación que interpretará los comandos del script, en este caso Bash, cuyo ejecutable es /bin/bash.
  • le daremos al script permisos de ejecución con el comando chmod:
    $ chmod a+x hello.sh
  • para ejecutar el script añadiremos al nombre del script la ruta del directorio actual, "./", ya que el directorio donde se encuentra (nuestra carpeta personal) no está en el PATH:
    $ ./hello.sh

    También podemos ejecutar el script llamando a bash:

    $ bash hello.sh

    En ambos casos obtendremos por la salida estándar (la pantalla) lo siguiente:

    Hola mundo Bash

Comentarios

Las líneas que comienzan por el carácter "#" (excepto la primera), se consideran comentarios y son ignoradas por el intérprete de comandos.

Salir del script

El comando para terminar el script es exit. Este comando no es útil al final del script, pero sí para terminar el programa en algún punto intermedio, por ejemplo "si se da tal condición, entonces terminar". Además, el comando exit permite establecer el valor de retorno, el valor devuelto por el script.

Variables en los scripts

Para que una variable definida dentro de una función, bucle o estructura sea de ámbito local (sólo existe en esa estructura) debemos declararla como local con el comando local:

local j=8

Las variables que no se definan expresamente como locales, se definan donde se definan, serán de ámbito global, y por tanto, accesibles en cualquier lugar del programa.

Argumentos de los scripts

A un script se le pueden pasar argumentos.

  • Dentro del script estos argumentos se almacenan en las variables de nombre $0, $1, $2, …, $9.
  • La variable $0 almacena el nombre del script, mientras que $1, $2, …, $9 almacenan los argumentos propiamente dichos.
  • Sólo hay nombre específico para los nueve primeros argumentos (más el propio nombre del comando), pero el comando shift hace posible utilizar y distinguir parámetros que están más a la derecha del noveno lugar en la llamada al script. Cuando se ejecuta el comando shift dentro del script, $2 se convierte en $1, $3 en $2, etc, y lo que hubiera sido $10 en $9, es decir, que se recorta la lista de argumentos una posición quitando el primero de ellos, con lo cual ya se puede referenciar. El comando shift deja inalterado $0 y puede utilizarse tantas veces como se desee.
  • El número de argumentos que se le pasa al script está contenido en la variable $#.
  • El conjunto de todos los argumentos está almacenado en la variable $*.

Veamos por ejemplo el siguiente script, llamado args.sh:

#!/bin/bash
echo "$0 contiene $0"
echo "$1 contiene $1"
echo "$2 contiene $2"
echo "En total hay $# parametros"

Ejecutamos este script sin pasarle argumentos:

$ ./args.sh
$0 contiene ./args.sh
$1 contiene
$2 contiene
En total hay 0 parametros

Ahora ejecutamos el script pasándole dos argumentos:

$ ./args.sh buenos dias
$0 contiene ./args.sh
$1 contiene buenos
$2 contiene dias
En total hay 2 parametros

Veamos un ejemplo de script al que se le pasa sólo un parámetro. El comando de borrar de Linux rm no pide confirmar la operación de borrado si no se le pone la opción "-i". Esto es peligroso porque uno fácilmente puede olvidarse de teclear dicha opción y borrar lo que no quería borrar. Vamos a crear un script llamado del.sh que incluya dicha opción. Dicho archivo podría ser:

#!/bin/bash
echo "Quiere borrar el archivo $1?"
rm -i $1

Después de darle a este archivo el correspondiente permiso de ejecución con el comando chmod, podríamos borrar con confirmación el archivo file1 tecleando:

$ del.sh file1

Dentro del archivo de comandos, $0 valdría del y $1 valdría file.

Veamos ahora un script al que se le pasan dos parámetros. El programa cambio.sh intercambia el nombre de dos archivos:

#!/bin/bash
mv $1 archivotemp
mv $2 $1
mv archivotemp $2

Para ejecutar este programa haremos:

$ cambio.sh file1 file2

En este ejemplo $0 es cambio.sh, $1 es file1 y $2 es file2.

Veamos un ejemplo con mútiples argumentos. Si el programa del.sh que hemos hecho previamente lo hubiéramos utilizado en la forma:

$ del.sh *.f

teóricamente debería borrar, con confirmación, todos los archivos Fortran del directorio (*.f). En la práctica no es así, porque $1 sólo almacena el primer archivo Fortran y es el único que borra. Para borrar todos el script del.sh podría ser:

#!/bin/bash
echo "Hay # programas para borrar n"
rm -i *

Valor de retorno

Cuando se ejecuta un programa o comando devuelve un valor numérico, que será:

  • cero (0) si el programa finalizó con éxito.
  • distinto de cero si el programa no finalizó con éxito. Cada uno de los valores distintos de cero va asociado a una causa distinta: sabiendo este valor podemos saber por qué razón falló el programa.

El valor de retorno no se imprime por la pantalla, es decir, cuando un programa termina no saca por pantalla un cero u otro número, sino que el valor de retorno del último programa ejecutado queda almacenado en la variable especial $?.

Veamos un ejemplo:

#!/bin/bash
echo "Listando archivo existente..."
ls archivo_existente
echo "El valor de retorno fue: $?"
echo " "
echo "Listando archivo inexistente..."
ls archivo_inexistente
echo "El valor de retorno fue: $?"

Primero listamos un archivo existente: lo lista con éxito y por tanto el valor de retorno es 0. A continuación, listamos un archivo inexistente: como eso no es posible, el comando no termina con éxito y el valor de retorno es distinto de cero (en este caso 1).

Utilizaremos el valor de retorno para saber si los comandos del script han tenido éxito o no, pudiendo emplear esta información en sentencias condicionales.

El comando para terminar el script, exit, permite establecer el valor de retorno (el valor devuelto por el script), para así obtener información acerca de si el script ha tenido éxito o no y porqué. Por ejemplo:

#!/bin/bash
if [ expresion1 ]; then
    Bloque 1
    exit 0
else
    Bloque 2
    exit 1
fi

Evaluar expresiones

El comando que permite evaluar expresiones es test: el valor de retorno será cero (0) si la expresión es VERDADERO (TRUE), y uno (1) si la expresión es FALSO (FALSE). La sintaxis es:

test <expresion>

Veamos un ejemplo:

#!/bin/bash
a=7
b=8
test $a -eq $a
echo $?
test $a -eq $b
echo $?

Si ejecutamos el script obtendremos:

0
1

El primer comando quiere decir: ¿es $a igual a $a? Vemos que el valor de retorno es cero (0), lo cual indica que la expresión es VERDADERO: efectivamente $a es igual a $a. El segundo comando comprueba si $a igual a $b: el valor de retorno de test es uno (1), ya que la expresión es FALSO: $a no es igual a $b.

Existe otra sintaxis para evaluar una expresión, algo más clara:

[ expresion ]

Veamos un ejemplo:

#!/bin/bash
a=7
b=8
[ $a -eq $b ]
echo $?

Si ejecutamos el script obtendremos:

1

Algunos de los operadores que admite test son:

  • para comparar enteros:
    [ integer1 -eq integer2 ]
    • -eq igual (equal).
    • -ne distinto (not equal).
    • -ge mayor o igual (greater than or equal).
    • -gt mayor (greater than).
    • -le menor o igual (less than or equal).
    • -lt menor (less than).
  • para comparar o aplicar a cadenas:
    [ string1 = string2 ], [ -z string1 ]
    • = igual.
    • != distinto.
    • -z la longitud de la cadena es cero.
  • para comparar o aplicar a archivos:
    [ file1 -nt file2] , [ -e file1 ]
    • -nt fecha de modificación más reciente (newer than).
    • -ot fecha de modificación más antigua (old than).
    • -e el archivo existe.
    • -s el archivo existe y tiene un tamaño mayor que cero.
    • -d el archivo existe y es un directorio.
    • -h el archivo existe y es un enlace simbólico.
    • -x el archivo existe y es ejecutable.

Estructuras de control

Estructuras de control Bash: IF

  • if

    Las estructuras condicionales if permiten que nuestros scripts se comporten de una forma u otra según la condición que especifiquemos, siendo condición un comando test. La sintaxis es:

    if condicion1; then
        Bloque1
    elif condicion2; then
        Bloque2
    else
        Bloque3
    fi

    En el siguiente ejemplo pedimos al usuario un número que se almacena en la variable $input. Dependiendo de si el número es menor, igual o mayor que 5, el script se comporta de una forma u otra. Si el usuario no introduce nada o introduce una cadena de texto, se ejecutará lo que hay tras else, pues ninguna de las condiciones anteriores se cumple.

    #!/bin/bash
    echo "Introduzca un numero: "
    read input
    if [ $input -lt 5 ]; then
        echo "El numero era menor que 5"
    elif [ $input -eq 5 ]; then
        echo "El numero era 5"
    elif [ $input -gt 5 ]; then
        echo "El numero era mayor que 5"
    else
        echo "No introdujo un numero"
    fi

    En el siguiente ejemplo, primero comprobamos que nos han pasado dos argumentos (el archivo donde buscar y la cadena de búsqueda): si no es así, le indicamos al usuario cómo debe ejecutar el script y salimos. A continuación comprobamos que el archivo existe: si no es así, advertimos y salimos. Una vez verificadas estas dos condiciones, mediante cat y el pipe, grep cuenta el número de veces que el valor de $BUSQUEDA está en el archivo, que queda guardado en $NUM_VECES.

    #!/bin/bash
    if [ $# -ne 2 ]; then
        echo "Necesito dos argumentos, el primero"
        echo "es el archivo donde debo buscar y"
        echo "el segundo es lo que quieres que busque."
        echo " "
        echo "Uso: $0 <archivo> <patron_busqueda>"
        echo " "
        exit
    fi
    ARCHIVO=$1
    BUSQUEDA=$2
    if [ ! -e $ARCHIVO ]; then
        echo "El archivo no existe"
        exit
    fi
    NUM_VECES='cat "$ARCHIVO" | grep --count "$BUSQUEDA"'
    if [ $NUM_VECES -eq 0 ]; then
        echo "El patron de busqueda "$BUSQUEDA" no fue encontrado"
        echo "en el archivo $ARCHIVO "
    else
        echo "El patron de busqueda "$BUSQUEDA" fue encontrado"
        echo "en el archivo $ARCHIVO $NUM_VECES veces"
    fi

Estructuras de control Bash: FOR

  • for

    El bucle for viene a solucionar el problema de "Para cada uno de los elementos siguientes, quiero que mi script haga esto". El bucle for ejecuta las sentencias de Bloque tantas veces como elementos haya en LISTA: la primera vez VARIABLE valdrá el primer elemento de la lista, la segunda vez VARIABLE valdrá el segundo elemento de la lista, y así sucesivamente. El elemento LISTA del bucle for puede ser una variable que contenga una lista de elementos:
    lista = "pedro pepe juan jose"
    for i in $lista; do
    … una lista de elementos:
    for i in pedro pepe juan jose; do
    … una lista de archivos dada utilizando comodines:
    for i in $DIR/.*; do
    … o un comando tal que su salida sea una lista de elementos:
    for i in ‘ls’; do
    La sintaxis del bucle for es:

    for VARIABLE in LISTA ; do
        Bloque
    done

    Veamos un ejemplo. Si ejecutamos el siguiente script:

    #!/bin/bash
    for i in pedro pepe juan jose; do
        echo "Hola $i"
    done

    obtenemos:

    Hola pedro
    Hola pepe
    Hola juan
    Hoja jose

    El siguiente ejemplo lista los archivos y directorios ocultos. Una vez comprobado que la cadena que se le ha pasado como argumento no tiene longitud cero, que existe y que es un directorio, lista cada archivo que pertenezca a la lista, es decir, que verifique que su primer carácter es un punto (.):

    #!/bin/bash
    DIR=$1
    if [ -z $DIR ]; then
        echo "El primer argumento debe tener longitud mayor que 0"
        exit 1
    fi
    if [ ! -d $DIR ]; then
        echo "El directorio debe existir y ser un directorio"
        exit 1
    fi
    for i in $DIR/.*; do
        echo "Encontrado el elemento oculto $i"
    done
    echo "Finalizado"

    En el bucle for podemos usar break para salir del bucle en cualquier momento.

Estructuras de control Bash: CASE

  • case

    La estructura case sirve para actuar de una u otra manera en función del valor de una variable: esto podríamos hacerlo con if, pero case es más eficiente y cómodo. La sintaxis es:

    case $VARIABLE in
        valor1)
            Bloque 1
            ;;
        valor2)
            Bloque 2
            ;;
        ...
        *)
            Bloque default
            ;;
    esac

    Si $VARIABLE no coincide con ninguno de los valores valor1, valor2… sí coindirá con el comodín *, ejecutándose el Bloque default. Veamos un ejemplo:

    #!/bin/bash
    if [ $# -lt 1 ]; then
        echo "Error. Esperaba al menos un parametro"
        exit 1
    fi
    case $1 in
        windows)
            echo "El logo de windows es una ventana"
            exit 0
            ;;
        mac)
            echo "El logo de mac es una manzana"
            exit 0
            ;;
        linux)
            echo "El logo de linux es un pinguino"
            exit 0
            ;;
        *)
            echo "Logo sin definir"
            exit 1
            ;;
    esac

Estructuras de control Bash: WHILE

  • while

    El otro tipo de bucle posible en los scripts de bash es el bucle while. Este bucle viene a solucionar el problema de "quiero que mi script haga esto mientras que se dé esta condición". Su sintaxis es:

    while test CONDICION; do
        Bloque
    done

    En el siguiente ejemplo la condición para que el bucle se siga ejecutando es "que $j valga menos de 10", y en cada ciclo sumamos 1 a la variable $j:

    #!/bin/bash
    j=0
    while [ $j -lt 10 ]; do
        echo "j vale $j"
        j=$[$j+1]
    done

    En el bucle while también podemos usar break para salir del bucle en cualquier momento. En el siguiente ejemplo la condición se cumple siempre (test 1 siempre retornará TRUE), pero break hará que termine el bucle cuando el número introducido sea igual a $MINUM.

    #!/bin/bash
    MINUM=8
    while [ 1 ]; do
        echo "Introduzca un numero: "
        read USER_NUM
        if [ $USER_NUM -lt $MINUM ]; then
            echo "El numero introducido es menor que el mio"
            echo " "
        elif [ $USER_NUM -gt $MINUM ]; then
            echo "El numero introducido es mayor que el mio"
            echo " "
        elif [ $USER_NUM -eq $MINUM ]; then
            echo "Acertaste: Mi numero era $MINUM"
            break
        fi
    done
    echo "El script salio del bucle. Finalizado."

Introducir datos por el teclado

Para introducir datos por el teclado usaremos el comando read. Para ver cómo funciona, veamos el siguiente script:

#!/bin/bash
frase="Introduzca lo que usted quiera:"
echo $frase
read entrada_del_usuario
echo " "
echo "Usted introdujo: $entrada_del_usuario"

Cuando se ejecute el script, al llegar al comando read esperará a que el usuario escriba algo y presione la tecla <INTRO>. A continuación, almacenará la entrada del usuario en la variable $entrada_del_usuario.

Funciones

Una función es un conjunto de comandos a los que se da un nombre, de manera que puedan ser ejecutados desde cualquier punto del script llamando a la función mediante ese nombre.

  • Antes de llamar una función debemos definirla. La sintaxis es:
    function nombre_funcion () {
        Bloque
    }
  • Una vez definida la función se convierte en un "comando posible" dentro del script, al que se le pueden pasar argumentos que se procesan de la misma forma: $1, $2, etc. se corresponden con el primer parámetro, segundo, etc.
  • Al igual que un script devuelve un valor que podemos establecer con exit, podemos hacer que una función devuelva un valor con el comando return, que se usa igual que exit.

En el siguiente ejemplo definimos una función que se ejecuta si se le pasa un número de argumentos distinto de 2, o si el segundo argumento no es un archivo existente: si los argumentos no son adecuados llamamos a la función uso que explica al usuario cómo se debe llamar el script. Esta estrategia es muy útil y frecuente en scripts complejos con muchas opciones:

#!/bin/bash
function uso () {
    echo "Este script recibe dos argumentos."
    echo "El primero debe ser windows o linux ,"
    echo "y el segundo debe ser un archivo existente."
    echo " "
    }
if [ $# -ne 2 ]; then
    uso
    exit 1
fi
case $1 in
    windows)
        if [ -e $2 ]; then
           echo "El script windows termino con exito"
           exit 0
        else
            uso
            exit 1
        fi
        ;;
    linux)
        if [ -e $2 ]; then
            echo "El script linux termino con exito"
            exit 0
        else
            uso
            exit 1
        fi
        ;;
    *)
        uso
        exit 1
        ;;
esac

En el siguiente ejemplo vamos a ver cómo utilizar el comando shift para trabajar con argumentos. La función lista_parametros recibe varios parámetros y usando shift recortamos la lista de argumentos una posición quitando el primero de ellos: $1 vale $2, $2 vale $3, etc. De esta manera, imprimimos el primer argumento, ejecutamos shift y volvemos a imprimir el primer argumento (que ahora es el segundo) y así hasta el final, habiendo conseguido imprimir todos los argumentos.

#!/bin/bash
function lista_parametros () {
    echo "Se ha llamado a lista_parametros, con $#"
    echo "parametros. Mostrando los parametros:"
    echo " "
    b=7
    numparms=$#
    local j=1
    while [ $j -le $numparms ]; do
        echo "El parametro $j contiene $1"
        shift
        local j=$[$j+1]
        done
}
lista_parametros a b c d dsfafd d
echo $b
echo $j

3 Comments:

  1. QUIERO RESOLUCION DE 2 SCRIPTS IMPORTANTES

    1. Crear un script llamado uno.sh que busque dentro de un fichero las líneas que contienen una palabra, tanto el nombre de fichero, como la palabra que se quiere buscar se pasarán por la línea de comandos. Se debe comprobar:

    • Que se pasen exactamente dos parámetros.
    • Que el fichero debe existir.

    Ejemplo:

    Miserver @ usuario$ uno.sh /home/usuario/jfsfj.txt redes

    2. Crear un script llamado dos.sh que pida por pantalla dos números y muestre por pantalla la secuencia de números que hay entre el menor y el mayor, al lado de cada número debe aparecer su cuadrado.

    Ejemplo 1
    Introduce el primer número: 5
    Introduce el segundo número: 1
    1 1
    2 4
    3 9
    4 16
    5 25
    Ejemplo 2
    Introduce el primer número: 2
    Introduce el segundo número: 6
    2 4
    3 9
    4 16
    5 25
    6 36

  2. en la comparación con enteros tienes
    “-t menor (less than)”
    y lo correcto es
    “-lt menor (less than)”

    puede ser importante, a mi me ha llevado un rato darme cuenta, en todo caso gracias por contribuir publicando estas cosas.

    Un saludo,
    Iván.

  3. A CUTI:

    1.- uno.sh
    —————————————————————————————–
    #!/bin/bash
    if [ $# -ne 2 ]; then
    echo “No se han introducido dos parámetros”; exit 1
    fi
    # Esto comprueba el nº de parámetros
    if [ -e $1 ];then
    echo “”
    else
    echo “El fichero no existe”; exit 2
    fi
    # Esto comprueba la existencia del fichero
    grep –file=”$1″ $2
    —————————————————————————————–
    El fichero dos.sh depende de la versión de BASH que tengas.