shell-scripting

Shebang

La primer linea del script indica que interprete debe utilizar el sistema.

#! El numeral por lo general se utiliza para los comentarios y no es interpretado por el sistema, pero en el caso de la primer línea y seguido por el signo de exclamación indica que a continuación se establece la ruta al interprete que se debe utilizar, ej: /bin/bash.

En caso de no saber donde se ubica el interprete, se puede utilizar el comando which, which bash.

#!/bin/bash

Variables

Para asignar una variable se debe especificar su nombre siguiendo por un signo de igual y el valor a asignar, esto sin espacios en ninguno de los lados del signo de igualdad.

peteco="test"

Para acceder al valor de las variables se utiliza el símbolo de pesos echo $peteco.

En caso de necesitar almacenar en una variable la salida de un comando es posible mediante el tilde invertido peteco=ls /var, peteco=$(ls /var).

Para declarar constantes es posible utilizar la siguiente notación. declare -r MICONSTANTE="Valor de mi constante"

Valor por defecto

Es posible asignarle un valor por defecto a las variables.

: ${mi_variable:='valor por defecto'} : ${$1:='primer-parametro'}

mi_variable=${mi_variable:-'por defecto'}

Ejecución ./script.sh

Para ejecutar el script, este debe tener permisos de ejecución, lo más común es asignarle los permisos 755, mientras que si corresponde que sólo el propietario tenga permiso de ejecución será 700.

chmod 755 script.sh

La extensión del script no es necesaria, pero suele ser utilizada la extensión .sh.

Una vez con los permisos adecuados es posible correrlo haciendo referencia a su nombre con la ruta absoluta o relativa.

Ej: ./script.sh o /home/tecnologo/script.sh.

Para ejecutarlo con tan solo el nombre, y desde cualquier lado, sería necesario moverlo a algún path dentro de la variable de entorno PATH.

Ej: mv script.sh bin/myscript

Argumentos

  • $0 - El nombre del script.
  • $1 - $9 - Cualquier argumento pasado al programa, donde $1 es el primer argumento, $2 el segundo, y así...
  • $# - Cuantos argumentos se le han pasado al programa.
  • $* - Todos los argumentos pasados.
  • $@ - Todos los argumentos pasados.

if

if [<condicion>];
then
    <comandos>
elif [<cond>];
then
   <comandos>
fi
1
2
3
4
5
6
7

test

Los parentesis rectos [] en el if son una referencia al comando test, por lo que todos los operadores que el comando test acepta también pueden ser utilizados en un if.

OperadorDescripción
! ExpresiónLa expresión es falsa.
-n STRINGEl tamaño del string es mayor a cero.
-z STRINGEl string es vacío.
STRING1 = STRING2STRING1 es igual a STRING2
STRING1 != STRING2STRING1 no es igual a STRING2
INTEGER1 -eq INTEGER2INTEGER1 es numericamente igual a INTEGER2
INTEGER1 -gt INTEGER2INTEGER1 es numericamente mayor que INTEGER2
INTEGER1 -lt INTEGER2INTEGER1 es numericamente menor que INTEGER2
-d FILEFILE existe y es un directorio.
-e FILEFILE existe.
-r FILEFILE existe y tiene permisos de lectura.
-s FILEFILE existe y su tamaño es mayor a cero.
-w FILEFILE existe y tiene permisos de escritura.
-x FILEFILE existe y tiene permisos de ejecución.

Hay una versión más moderna de test [[]] y otra para el manejo de enteros (())

El test [[]] admite el manejo de expreciones regulares del modo string1 =~ regex tal que es verdadero si el string1 se encuentra la expreción regular.

Operadores lógicos

Operacióntest[[ ]] y (( ))
AND-a&&
OR-o`
NOT!!

Exit status

Los comandos, incluyendo los scripts, retornan un valor al sistema cuando finalizan, llamado estado de salida o código de retorno, exit status. Este valor es un entero en el rango del 0 al 255, indicando éxito o fallo en la ejecución.

exit 0 o exit 1

Por convención, cero indica una ejecución exitosa, mientras que cualquier otro valor fallo.

Es posible determinar en un script el valor retornado por un comando mediante $?.

[tecnologo@tisj ~]$ ls -d /usr/bin
/usr/bin
[tecnologo@tisj ~]$ echo $?
0
[tecnologo@tisj ~]$ ls -d /bin/usr
ls: cannot access /bin/usr: No such file or directory
[tecnologo@tisj ~]$ echo $?
2
1
2
3
4
5
6
7
8

Funciones

Las funciones tienen 2 posibles sintaxis:

function nombre {
    comandos
    return
}
1
2
3
4
nombre () {
    comandos
    return
}
1
2
3
4

Para luego ejecutar las funciones basta con utilizar su nombre.

case

En bash al igual que en otros lenguajes de programación existe el componente multiopción llamado case, el cual puede ser utilizado por ejemplo para un menú, sirviendo de buena alternativa a sucesivos if anidados.

case texto in
[patron [| patron]...) comandos ;;]...
esac
1
2
3

loops

for

for i in "${arrayName[@]}"; do
  echo $i
done
1
2
3

while

count=1
while [[ "$count" -le 5 ]]; do
    echo "$count"
    count=$((count + 1))
done
1
2
3
4
5

Parametros posicionales

usage () {
    echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
    return
}

# process command line options
interactive=
filename=
while [[ -n "$1" ]]; do
    case "$1" in
        -f | --file)
            shift
            filename="$1"
    ;;
        -i | --interactive)
            interactive=1
    ;;
    -h | --help)
        usage
        exit
    ;;
    *)
        usage >&2
        exit 1
    ;;
    esac
    shift
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Buenas ubicaciones para los Scripts

El directorio ~/bin es un buen lugar para colocar scripts de uso personal. En caso de que se genere un script para ser utilizado por todos los usuarios del sistema, el lugar tradicional es /usr/local/bin. Para los scripts que son utilizados únicamente por el administrador, se suele utilizar el directorio /usr/local/sbin.

Notas

Material basado en el curso de Ryan Chadwickopen in new window y del libro The Linux Command Lineopen in new window.

Playground Shell scripting

#!/bin/bash

# La primera línea del script es el [shebang](http://en.wikipedia.org/wiki/Shebang_(Unix)) que le indica al sistema 
# cómo ejecutar el script.
# Como te habrás dado cuenta, los comentarios en shell empiezan con #.
# El shebang también es un comentario.

# Ejemplo sencillo de hola mundo:
echo ¡Hola mundo!

# Cada comando empieza con una nueva línea, o después de un punto y coma:
echo 'Esta es la primera línea'; echo 'Esta es la segunda línea'

# Para declarar una variable se hace lo siguiente:
VARIABLE="Mi string"

# Pero no así:
VARIABLE = "Mi string"

# Bash decidirá que VARIABLE es un comando a ejecutar, dando un error.

# Usando la variable:
echo $VARIABLE
echo "$VARIABLE"
echo '$VARIABLE'

# Cuando la variable es usada - o asignada, exportada, etcétera - se
# escribe su nombre sin $. Si se quiere saber el valor de la variables,
# entonces sí se usa $. Note que ' (comilla simple) no expandirá las 
# variables.

# Sustitución de strings en variables.
echo ${VARIABLE/Mi/Una}
# Esto sustituirá la primera cadena "Mi" con "Una".

# Substring de una variable.
echo ${VARIABLE:0:7}
# Esto va a regresar sólo los primeros 7 caracteres del valor.

# Valor por defecto de una variable
echo ${FOO:-"DefaultValueIfFOOIsMissingOrEmpty"}
# Esto trabaja para null (VARIABLE=), string vacío (VARIABLE=""), }
# cero (VARIABLE=0) regresa 0

# Variables del sistema:
# Aquí hay algunas variables incluídas en el sistema:
echo "El valor de regreso del último programa: $?"
echo "PID del sistema: $"
echo "Número de argumentos: $#"
echo "Argumentos del script: $@"
echo "Argumentos del script separados en variables: $1 $2..."

# Para leer un valor del input:
echo "¿Cuál es tu nombre?"
read NOMBRE # Note que no necesitamos declarar una variable
echo ¡Hola, $NOMBRE!

# Tenemos la estructura 'if' usual:
# use 'man test' para más información sobre condicionales
if [ $NOMBRE -ne $USER ]
then
    echo "Tu nombre es tu usuario."
else
    echo "Tu nombre no es tu usuario."
fi

# También hay ejecuciones condicionadas.
echo "Siempre ejecutado" || echo "Sólo ejecutado si el primer comando falla"
echo "Siempre ejecutado" && echo "Sólo ejecutado si el primer comando NO falla"

# Para usar && y || con condicionales, se necesitan 
# múltiples pares de corchetes:
if [ $NOMBRE == "Steve" ] && [ $EDAD -eq 15 ]
then
    echo "Esto correrá si $NOMBRE es Steve Y $EDAD es 15."
fi

if [ $NOMBRE == "Daniya" ] || [ $NOMBRE == "Zach" ]
then
    echo "Esto correrá si $NOMBRE es Daniya O Zach."
fi

# Las expresiones se denotan con el siguiente formato:
echo $(( 10 + 5 ))

# A diferencia de otros lenguajes de programación, bash es shell , así que
# funciona en un contexto de directorio actual. Puedes listar archivos y
# directorios en un directorio actual con el comando 'ls':
ls

# Estos comandos tienen opciones que controlan su ejecución:
ls -l # Lista todos los archivos y directorios en líneas distintas.

# Los resultados del comando anterior pueden ser pasados al siguiente 
# como input. El comando 'grep' filtra el input con los comandos provistos.
# Así es como podemos listar archivos .txt en el directorio actual:
ls -l | grep "\.txt"

# Puedes también redireccionar el input y el error lanzado de algún comando.
python2 hello.py < "input.in"
python2 hello.py > "output.out"
python2 hello.py 2> "error.err"

# El error lanzado eliminará el contenido del archivo si es que existe,
# para después escribir el error. Para que se concatene (en lugar de eliminar)
# use el comando ">>".

# Los comandos pueden ser sustituidos dentro de otros comandos usando $():
# El siguiente ejemplo despliega el número de archivos y directorios en el
# directorio actual.
echo "Hay $(ls | wc -l) elementos aquí."

# Lo mismo puede ser hecho usando comillas invertidas `` pero no pueden ser
# anidadas. El método preferido es $().
echo "Hay `ls | wc -l` elementos aquí."

# Bash usa una estructura de casos similar al switch de Java o C++:
case "$VARIABLE" in 
    # Lista de patrones que las condiciones deben cumplir: 
    0) echo "Hay un cero.";;
    1) echo "Hay un uno.";;
    *) echo "No es null.";;
esac

# Para los ciclos, se usa la estructura 'for'. Cicla para cada argumento dado:
# El contenido de $VARIABLE se imprime tres veces.
for VARIABLE in {1..3}
do
    echo "$VARIABLE"
done

# ciclos while:
while [true]
do
    echo "cuerpo del ciclo..."
    break
done

# También se pueden definir sub-rutinas (funciones)
# Definición:
function miFuncion ()
{
    echo "Los argumentos trabajan igual que argumentos de script: $@"
    echo "Y: $1 $2..."
    echo "Esto es una función"
    return 0
}

# O simplemente:
miOtraFuncion ()
{
    echo "¡Otra forma de declarar funciones!"
    return 0
}

# Para llamar a tu función
foo "Mi nombre es:" $NOMBRE

# Hay muchos comandos útiles que puedes aprender:
# imprime las últimas 10 líneas del archivo file.txt
tail -n 10 file.txt
# imprime las primeras 10 líneas del archivo file.txt
head -n 10 file.txt
# ordena las líneas del archivo file.txt
sort file.txt
# identifica u omite las líneas repetidas, con -d las reporta
uniq -d file.txt
# imprime sólo la primera columna antes de cada ',' en el archivo|
cut -d ',' -f 1 file.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169

Enlaces

Last Updated: 1/19/2023, 11:23:31 PM
Contributors: Diego Mascheroni