
El Shell Bash en OS X, como dominarlo.
En este tutorial pretendemos enseñar el manejo de Bash, el Bourne Again Shell de GNU. Este shell es el que proporcionan por defecto muchos sistemas UNIX entre ellos Mac OS X o Linux. Los ejemplos se explicarán sobre Mac OS X, pero debido a la interoperatividad que caracteriza a Bash, estos ejemplos deberían ser exactamente igual de útiles en otros sistemas UNIX. Cuando existan diferencias las indicaremos para que usuarios de otros sistemas puedan seguir correctamente este documento.
El tutorial asume que el lector conoce los aspectos más básicos de qué es, y para qué sirve un terminal. No pretendemos enseñar cuales son los muchos y útiles comandos a los que podemos acceder, sólo pretendemos centrarnos en el manejo, personalización y programación de scripts con el shell Bash. Aun así, a lo largo del documento comentaremos gran cantidad de comandos que están relacionados con el shell, y que ayudan a hacer que los ejemplos resulten útiles.
Al acabar este tutorial el lector debería de haber aprendido a usar las principales teclas rápidas, personalizar mucho más su terminal para hacerlo más manejable, y modificar o crear los scripts que configuran su sistema.
Nota legal
Este tutorial ha sido escrito por Fernando López Hernández para macprogramadores.org y de acuerdo a las leyes internacionales sobre propiedad intelectual, a la Directiva 2001/29/CE del Parlamento Europeo de 22 de mayo de 2001 y al artículo 5 de la Ley 22/1987 de 11 de Noviembre de Propiedad Intelectual Española, el autor prohíbe la publicación de este documento en cualquier otro servidor web, así como su venta, o difusión en cualquier otro medio sin autorización previa.
Sin embargo el autor anima a todos los servidores web a colocar enlaces a este documento. El autor también anima a cualquier persona interesada en conocer el shell Bash, y que ventajas que aporta tanto al usuario como al programador, a bajarse o imprimirse este tutorial.
Madrid, Septiembre 2005
Para cualquier aclaración contacte con:
fernando@macprogramadores.org
Tema 1
Introducción a Bash
Sinopsis:
Como se justifica en el acerca de, este tutorial va a omitir los aspectos más básicos del shell que es normal conocer por parte de cualquier persona que haya usado mínimamente un shell UNIX.
En este primer tema vamos a repasar un conjunto de temas básicos que, aunque en parte puede conocer el lector, creemos que conviene aclarar antes de profundizar.
Por consiguiente, recomendamos empezar leyendo este primer tema, ya que sino pueden quedar ciertos aspectos sin concretar que luego podrían hacer falta para seguir más cómodamente las explicaciones.
Debido a sus objetivos, este tema está escrito avanzando de forma considerablemente más rápida y superficial que en resto de temas.
1. El shell que estamos usando
Mac OS X trae preinstalado el shell Bash desde la versión 10.2, antes traía instalado el shell tcsh, pero debido a que Bash es el shell que GNU eligió para el software libre, Apple decidió dar el salto. Linux lógicamente también usa este shell, con lo cual parece ser que Bash es el shell de los sistemas UNIX más utilizados, y tiene un futuro muy prometedor.
Si queremos saber que versión de shell tenemos instalado podemos usar el comando:
$ echo $SHELL
/bin/bash
Este comando nos indica que shell estamos usando y en que directorio está instalado.
Si queremos conocer la versión de Bash podemos usar el comando:
$ echo $BASH_VERSION
2.05b.0(1)-release
También podemos saber donde está instalado Bash con el comando:
$ whereis bash
/bin/bash
Puede conocer todos los shell de que dispone su máquina con el comando:
$ cat /etc/shells
/bin/bash
/bin/csh
/bin/sh
/bin/tcsh
/bin/zsh
Si por alguna razón no está usando Bash, pero lo tiene instalado (o lo acaba de instalar) en su máquina, puede hacer que Bash sea el shell por defecto de su cuenta usando el comando:
$ chsh -s /bin/bash
Si prefiere usar una versión más moderna de shell que la que viene preinstalada con Mac OS X puede bajársela del proyecto Fink:
$ fink list bash
Information about 4975 packages read in 12 seconds.
bash 3.0-2 The GNU Bourne Again Shell
bash-completion 20041017-1 Command-line completions …
bash-doc 3.0-1 Extra documentation for …
$ fink install bash
Y cambiar a este shell con:
$ chsh -s /sw/bin/bash
Pero antes deberá introducir este shell en /etc/shells, o chsh no se lo aceptará como un shell válido.
Si ahora nos logamos de nuevo con el comando login y preguntamos por la versión de Bash:
$ echo $BASH
/sw/bin/bash
$ echo $BASH_VERSION
3.00.0(1)-release
Vemos que estamos trabajando con Bash 3.0. En este tutorial supondremos que tenemos la versión 3.0 de Bash, si alguien está usando la versión 2.05, o alguna anterior, puede que no le funcionen todos los ejemplos que hagamos.
2. Expansión de nombres de ficheros y directorios
2.1. Los comodines
Para referirnos a varios ficheros es muy típico usar los comodines de la Tabla 1.1. Un sitio típico donde se usan los comodines es el comando ls. Este comando sin argumentos lista todos los ficheros del directorio, pero le podemos pasar como argumentos los nombres de los ficheros que queremos listar:
$ ls carta.txt leeme.txt
Si lo que le damos son los nombres de uno o más directorios lo que hace es listar su contenido.
Comodín
Descripción
?
Uno y sólo un carácter
*
Cero o más caracteres
[conjunto]
Uno los caracteres de conjunto
[!conjunto]
Un carácter que no este en conjunto
Tabla 1.: Comodines de fichero
Muchas veces queremos referirnos a un conjunto de ficheros para lo cual usamos comandos de la forma:
$ ls *.txt
Que lista todos los ficheros acabados en .txt.
* representa cero o más caracteres, con lo que *ed encontraría el fichero ed.
Otro comodín menos usado es ? que sustituye por un sólo carácter, por ejemplo:
$ ls carta?.txt
Listaría ficheros como carta1.txt, carta2.txt, pero no carta10.txt.
El tercer comodín permite indicar un conjunto de caracteres que son válidos para hacer la sustitución, p.e. c[ao]sa encontraría el fichero casa y cosa, pero no cesa. Además podemos indicar un conjunto de caracteres ASCII consecutivos, por ejemplo [a-z] serían todas las letras minúsculas, [!0-9] serían todos los caracteres ASCII excepto los dígitos, y [a-zA-Z0-9] serían todas las letras mayúsculas, minúsculas y los dígitos.
La razón por la que este comodín no ha sido tan usado como se esperaba es que expande por un, y sólo un dígito, por ejemplo programa.[co] encontraría programa.c y programa.o, pero no programa.cpp.
Es importante tener en cuenta que los comandos cuando se ejecutan no ven los comodines sino el resultado de la expansión. Por ejemplo si ejecutamos el comando:
$ cp g* /tmp
g* se expande por todos los ficheros que cumplen el patrón, y esto es lo que se pasa al comando cp, pero si no existiera ningún fichero cuyo nombre cumpliese el patrón g*, este valor no se expande sino que se pasa tal cual al comando, y éste será el que fallará:
$ cp g* /tmp/
cp: g*: No such file or directory
Es decir, como podríamos pensar, al fallar no se pasa una lista vacía al comando. Piense por un momento lo que ocurriría con algunos comandos si se hubiese diseñado así.
Este funcionamiento es ligeramente distinto al de tcsh, donde si no se expande el comodín no se ejecuta el comando, en Bash se ejecuta el comando aunque luego éste produzca un error.
2.2. El comodín tilde
El comodín tilde ~ se usa para referirse al directorio home de los usuarios (/Users en Mac OS X o /home en la mayoría de las máquinas UNIX), por ejemplo si usamos ~carol/carta.txt nos lo expande por /Users/carol/carta.txt.
Además podemos usar el comodín tilde para referirnos a nuestro propio directorio, el cuyo caso debemos de precederlo por una barra, p.e. ~/carta.txt se expande por el nombre de mi directorio, en mi caso /Users/fernando/carta.txt.
Observe la diferencia entre poner la barra y no ponerla, si no la hubiera puesto (hubiera puesto ~carta.txt), me habría expandido por la ruta /Users/carta.txt, y si no existe un usuario con el nombre carta.txt hubiera producido un error indicando que no existe el directorio.
2.3. El comodín llaves
El comodín llaves, a diferencia de los anteriores, no estudia el nombre de los ficheros existentes en disco para nada, simplemente expande una palabra por cada una de las cadenas de caracteres que contiene, por ejemplo:
$ echo c{ami,ontamina,}on
camion contaminaon con
Es posible incluso anidarlo y genera el producto cartesiano de combinaciones:
$ echo c{a{min,nt}a,ose}r
caminar cantar coser
En el apartado 2.1 comentamos que un problema que tenía el comodín corchete es que expandía por un y sólo un carácter, lo cual era problemático si queríamos referirnos por ejemplo a todos los ficheros de un programa, ya que *.[coh] nos permite referirnos a los fichero .c, .o y .h, pero no a los .cpp.
Usando el comodín llave podemos superar esta dificultad: *.{h,c,cpp,o} espadería en todos los ficheros con estas extensiones, aunque ahora surge un nuevo problema y es que, debido a que el comodín llave no mira que ficheros hay en disco, si no existe un fichero con alguna de las extensiones indicadas, la expansión del * no se produce, y encontraríamos un mensaje de error. Por ejemplo:
$ ls *.{h,c,cpp,o}
ls: *.c: No such file or directory
ls: *.o: No such file or directory
clave.cpp clave.h
Se puede usar .. para hacer algo similar a lo que hacen los corchetes obteniendo todos lo caracteres ASCII entre dos letras. Por ejemplo:
$ echo l{a..e}
la lb lc ld le
Obsérvese que, en el caso de los corchetes, lo que obtendríamos no son un conjunto de cinco palabras, sino una expansión por un fichero existente:
$ echo cl[a-e]ve.h
clave.h
O la cadena sin expandir si no se encuentra el fichero:
$ echo cl[e-i]ve.h
cl[e-i]ve.h
Por último comentar que la llave debe contener al menos dos cadenas, sino no se realiza la expansión:
$ echo ca{a}sa
ca{a}sa
De nuevo este comportamiento difiere con el de tcsh, donde la expansión se realiza aunque haya una sola cadena dentro de las llaves.
2.4. Comodines extendidos
Bash permite usar un conjunto de comodines extendidos, pero para poder usarlos debemos de activar la opción ext_glob de Bash (véase el apartado Error! Reference source not found. del Tema 3) con el comando:
$ shopt -s extglob
En este caso se pueden usar uno de estos cinco nuevos tipos de patrones:
?(pattern-list)
Cero o una ocurrencia de pattern-list
*(pattern-list)
Cero o más ocurrencias de pattern-list
+(pattern-list)
Una o más ocurrencias de pattern-list
@(pattern-list)
Exactamente uno de los patrones de la lista
!(pattern-list)
Cualquier cosa excepto uno de los patrones de la lista
pattern-list recibe uno o más patrones separados por |. Cada patrón de esta lista puede contener comodines, por ejemplo +([0-9]) busca cadenas formadas por uno o más dígitos.
En el apartado 2.1 vimos que un problema que presentaba el comodín ? era que carta?.txt listaría ficheros como carta1.txt, carta2.txt, pero no carta10.txt. Esto lo podemos solucionar con el comodín extendido +(pattern-list) de la forma: carta+([0..9]).txt
También vimos en el apartado 2.1 que *.[cho] encontraría los ficheros con extensión .c, .o y .h, pero no había forma de encontrar los .cpp ya que el corchete sólo aceptaba un carácter. Ahora podemos usar el comodín @(pattern-list) para indicar la lista de extensiones a aceptar. Por ejemplo .@(c|o|h|cpp) encontraría correctamente estos ficheros:
$ ls *.@(c|o|h|cpp)
clave.cpp clave.h
También hubiera sido equivalente usar @(*.c|*.o|*.h|*.cpp) ya que los patrones pueden estar anidados.
Si lo que hubiéramos querido es encontrar todos los ficheros excepto los .gif, los .jpg y los .html podríamos haber usado el patrón !(*.html|*gif|*jpg). Sin embargo, en este caso no podríamos haber usado *.!(html|gif|jpg)
Un último ejemplo, si queremos borrar todos los ficheros excepto los que empiezan por vt seguido por uno o más dígitos podemos usar el comando:
$ rm !(vt+([0-9]))
3. Los comandos internos de Bash
Bash busca los comandos a ejecutar en los directorios indicados en la variable de entorno $PATH, pero además existen una serie de comandos que no corresponden a ficheros del disco duro, sino que son internos a Bash y están siempre cargados en su memoria.
Ejemplos de estos comandos son cd, chdir, alias, set o export. Puede obtener una lista completa de estos comandos con su descripción ejecutando:
$ man builtin
Y puede obtener ayuda de estos comandos usando el comando help:
$ help alias
alias: alias [-p] [name[=value] … ]
`alias’ with no arguments or with the -p option prints the list of aliases in the form alias NAME=VALUE on standard output.
Otherwise, an alias is defined for each NAME whose VALUE is given.
A trailing space in VALUE causes the next word to be checked for alias substitution when the alias is expanded. Alias returns true unless a NAME is given for which no alias has been defined.
4. Redirecciones y pipes
4.1. Operadores de redirección
UNIX está basado en una idea muy simple pero muy útil: Tratar todos las entrada y salidas como streams (flujos) de bytes.
Cada programa va a tener asociadas siempre una entrada estándar (por defecto el teclado), una salida estándar (por defecto la consola), y una salida de errores estándar (por defecto también la consola).
Si queremos, podemos cambiar la entrada estándar para que el programa reciba datos de un fichero usando el operador de redirección <. Por ejemplo el comando cat, si no recibe argumentos, lee del teclado por la entrada estándar y lo pasa a la salida estándar:
$ cat
Esto es una linea acabada en intro
Esto es una linea acabada en intro
^D
Podemos indicar el final de un stream desde el teclado con la combinación de teclas Ctrl+D como se muestra en el ejemplo.
Podemos cambiar la entrada estándar de cat para que lea de un fichero con:
$ cat < clave.h
#ifndef CLAVE_H_
·····
En el caso concreto del comando cat, también puede recibir como argumento el nombre del fichero a pasar a la salida estándar, con lo que en el caso del comando cat nos podríamos haber ahorrado el operador <:
$ cat clave.h
#ifndef CLAVE_H_
·····
UNIX dispone de un gran número de comandos que leen de la entrada estándar, realizan una operación con el texto, y escriben en la salida estándar (o en la salida de errores si se produce un error): cat, grep, soft, cut, sed, tr,…
El operador de redirección de salida > permite cambiar la salida estándar de un comando, por ejemplo:
$ date > ahora
Envía el día y hora actuales al fichero ahora.
También podemos cambiar a la vez la entrada y salida estándar de un programa usando ambos operadores de redirección. Por ejemplo:
$ cat < ficheroa > ficherob
También podemos cambiar la salida de errores estándar con el operador de redirección 2>. Por ejemplo:
$ cat < ficheroa > ficherob 2>errores
Copia el ficheroa en el ficherob, y si se produce algún error lo escribe en el fichero errores.
Si no queremos sobrescribir un fichero de salida sino añadir el contenido al final podemos usar el operador de redirección >> para la salida estándar o 2>> para los errores estándar. Por ejemplo:
$ ls p* >>ficheros 2>>errores
Añadiría los ficheros que lista ls al fichero ficheros, y si se produjesen errores los añadiría al fichero errores. El operador de redirección 2>> es especialmente útil para almacenar los conocidos logs de errores.
Muchas veces no se quiere que un programa muestre mensajes en la consola del usuario, en este caso es muy común redirigir su salida estándar y salida de errores estándar al fichero /dev/null:
$ gcc *.cpp > /dev/null 2> /dev/null
4.2. Pipes
Es posible redirigir la salida estándar de un programa a la entrada estándar de otro usando el operador | (pipeline).
more es uno de los comandos típicos que lo usan. Este comando lo que hace es recoger la entrada estándar y irla mostrando poco a poco (página a página), luego si por ejemplo tenemos un directorio con muchos ficheros podemos hacer:
$ ls -la | more
y se irán mostrando página a página los ficheros.
Según vayamos avanzando podremos ir viendo ejemplos más complejos, por ejemplo:
$ cut -d: -f1 < /etc/passwd | sort
Nos muestra los nombres de todos los usuarios de la máquina ordenados alfabéticamente.
Téngase en cuenta que el operador | separa el comando en varios comandos antes de ejecutarlo, con lo que el operador de redirección tiene efecto sólo para el comando cut.
5. Ejecución secuencial y concurrente de comandos
Podemos ejecutar un comando que tarde mucho en ejecutarse y dejarlo ejecutando en background precediéndolo por &. Por ejemplo para compilar un conjunto de ficheros fuente de un programa C++ podemos hacer:
$ gcc *.cpp &
Aunque el proceso se sigue ejecutando en background, los mensajes que produce salen en la consola impidiéndonos trabajan cómodamente. Para evitarlo podemos enviar los mensajes a /dev/null:
$ gcc *.cpp > /dev/null &
Aunque si se produce un error, éste irá a la salida de errores estándar, con lo que seguiría saliendo en consola. Podríamos evitarlo redirigiendo también la salida de errores estándar, pero quizá sea mejor que se nos informase del error.
Otras veces lo que queremos es esperar a que se acabe de ejecutar un comando para ejecutar el siguiente, en este caso podemos usar el operador ; (punto y coma), por ejemplo, podríamos querer compilar el comando clave para luego ejecutarlo:
$ gcc clave.cpp -o clave ; clave
Este comando primero compila el programa, y cuando acaba de compilarlo lo ejecuta.
6. Caracteres especiales y entrecomillado
Los caracteres <, >, |, & *, ? , ~, [, ], {, } son ejemplos de caracteres especiales para Bash que ya hemos visto. La Tabla 1.2 muestra todos los caracteres especiales de Bash.
Más adelante veremos otros comandos tienen sus propios caracteres especiales, como puedan ser los comandos que usan expresiones regulares o los operadores de manejo de cadenas.
Carácter
Descripción
~
Directorio home
`
Sustitución de comando
#
Comentario
$
Variable
&
Proceso en background
;
Separador de comandos
*
Comodín 0 a n caracteres
?
Comodín de un sólo carácter
/
Separador de directorios
(
Empezar un subshell
)
Terminar un subshell
\
Carácter de escape
<
Redirigir la entrada
>
Redirigir la salida
|
Pipe
[
Empieza conjunto de caracteres comodín
]
Acaba conjunto de caracteres comodín
{
Empieza un bloque de comando
}
Acaba un bloque de comando
‘
Entrecomillado fuerte
”
Entrecomillado débil
!
No lógico de código de terminación
Tabla 1.2: Caracteres especiales de Bash
6.1. Entrecomillado
Aunque los caracteres especiales son muy útiles para Bash, a veces queremos usar un carácter especial literalmente, es decir sin su significado especial, en este caso necesitamos entrecomillarlo (quoting).
Por ejemplo si queremos escribir en consola el mensaje: 2*3>5 es una expresión cierta, podemos usar el comando echo así:
$ echo 2*3>5 es una expresion cierta
Al ejecutarlo podemos observar que no da ninguna salida, pero realmente ha creado el fichero 5 con el texto 2*3 es una expresion cierta. La razón está en que >5 ha sido entendido como redirigir al fichero 5, y además se ha intentado ejecutar el carácter especial *, pero al no encontrar ningún fichero que cumpliera el patrón no se ha expandido y se ha pasado el parámetro a echo tal cual.
Sin embargo si entrecomillamos usando ‘, el carácter de entrecomillado fuerte obtenemos el resultado esperado:
$ echo ‘2*3>5 es una expresion cierta’
2*3>5 es una expresion cierta
Un ejemplo más práctico del entrecomillado es el comando find que nos permite buscar ficheros por muchos criterios. Por ejemplo para buscar por nombre usa el argumento -name patron. Si por ejemplo queremos buscar todos los ficheros .c en nuestra máquina podemos intentar hacer:
$ find / -name *.c
Pero si tenemos la mala suerte de que en el directorio actual exista algún fichero .c, sustituirá el comodín y buscará el nombre de el/los ficheros de nuestro directorio actual en el disco duro. Para evitarlo es recomendable entrecomillarlo:
$ find / -name ‘*.c’
En la Tabla 1.2 aparece el entrecomillado débil “, el cual pasa sólo por algunos de los pasos del shell, o dicho con otras palabras, interpreta sólo algunos caracteres especiales. Más adelante veremos cuando es preferible usar este tipo de entrecomillado, de momento usaremos sólo el entrecomillado fuerte.
6.2. Caracteres de escape
Otra forma de cambiar el significado de un carácter de escape es precederlo por \, que es lo que se llama el carácter de escape.
Por ejemplo en el ejemplo anterior podríamos haber hecho:
$ echo 2\*3\>5 es una expresion cierta
2*3>5 es una expresion cierta
Donde hemos puesto el carácter de escape a los caracteres especiales para que Bash no los interprete.
También este carácter se usa para poder poner espacios a los nombres de ficheros, ya que el espacio es interpretado por Bash como un separador de argumentos de la línea de comandos, si queremos que no considere el espacio como un cambio de argumento podemos preceder el espacio por \. Por ejemplo el fichero Una historia.avi lo reproduzco en mi ordenador con mplayer así:
$ mplayer Una\ historia.avi
Si queremos que Bash no interprete el carácter de escape podemos entrecomillarlo ‘\’, o bien hacer que él se preceda a si mismo \\.
6.3. Entrecomillar los entrecomillados
Podemos usar el carácter de escape para que no se interpreten los entrecomillados simples o dobles, es decir:
$ echo \”2\*3\>5\” es una expresion cierta
“2*3>5” es una expresion cierta
$ echo \’2\*3\>5\’ es una expresion cierta
‘2*3>5’ es una expresion cierta
6.4. Texto de varias líneas
Otro problema es como escribir un comando que ocupa varias líneas. Bash nos permite utilizar el carácter de escape para ignorar los retornos de carro de la siguiente forma:
$ echo En un lugar de la Mancha de cuyo nombre no \
> quiero acordarme no a mucho tiempo vivía un \
> hidalgo caballero.
En un lugar de la Mancha de cuyo nombre no quiero acordar no a mucho tiempo vivía un hidalgo caballero.
Al pulsar intro Bash nos devuelve el segundo prompt, que por defecto es >, y nos permite seguir escribiendo.
Una segunda opción es entrecomillar y no cerrar las comillas, con lo que Bash nos pide que sigamos escribiendo sin necesidad de poner un carácter de escape a los retornos de carro:
$ echo ‘En un lugar de la Mancha de cuyo nombre no
> quiero acordarme no a mucho tiempo vivía un
> hidalgo caballero.’
En un lugar de la Mancha de cuyo nombre no
quiero acordar no a mucho tiempo vivía un
hidalgo caballero.
La diferencia está en que en el primer caso los retornos de carro son eliminados, y en el segundo caso no.
Un artículo de Fernando López Hernández de MacProgramadores
La versión completa la podéis descargar de aquí.
- Cómo solucionar pantalla negra en Windows 10 en un HP - Septiembre 13, 2017
- Cómo actualizar el firmware del Kingkong Q100 a Betaflight - Junio 19, 2017
- Alimentación PoE y SAI simple para AP - Junio 17, 2017
- Cómo montar una antena parabólica para un enlace P2P / PtP - Junio 14, 2017