viernes, 22 de septiembre de 2017

Viendo el Mundo como lo ve el Shell

En este capítulo vamos a ver algo de la “magia” que ocurre en la línea de comandos cuando presionas la tecla enter. Mientras, examinaremos varias características interesantes y complejas del shell, lo haremos con un único comando nuevo:
  • echo – Muestra una línea de texto
Expansión

Cada vez que escribes un comando y presionas la tecla enter, bash realiza varios procesos sobre el texto antes de llevar a cabo el comando. Hemos visto un par de casos de cómo una simple secuencia de caracteres, por ejemplo “*”, puede tener mucho significado para el shell. El proceso que hace que ésto ocurra se llama expansión. Con la expansión, introduces algo y se expande en otra cosa antes de que el shell actúe sobre ello. Para demostrar qué queremos decir con ésto, echemos un vistazo al comando echo. echo es una función del shell que realiza una tarea muy simple. Muestra sus argumentos de texto en la salida estándar:

 [me@linuxbox ~]$ echo this is a test
 this is a test

Es muy sencillo. Cualquier argumento que pasemos a echo se muestra. Probemos otro ejemplo:

 [me@linuxbox ~]$ echo *
 Desktop Documents ls-output.txt Music
 Pictures Public Templates
 Videos

Pero, ¿qué ha pasado? ¿por qué echo no ha escrito “*”? Como recordarás de nuestro trabajo con comodines, el carácter “*” significa coincidencia de caracteres en el nombre de archivo, pero lo que no hemos visto en nuestra conversación original es como hace eso el shell. La respuesta sencilla es que el shell expande el “*” en algo más (en este ejemplo, los nombres de los archivos que se encuentran en el directorio de trabajo actual) antes de que el comando echo se ejecute. Cuando presionamos la tecla enter, el shell automáticamente expande todos los caracteres en la linea de comandos antes de que el comando sea ejecutado, por lo que el comando echo no ve el “*”, sólo su resultado expandido. Sabiendo ésto, podemos ver que echo se ha comportado como se esperaba.

Expansión de nombres de archivo


El mecanismo según el cual trabajan los comodines se llama expansión de nombres de archivo. Si probamos algunas de las técnicas que hemos empleado en nuestros capítulos anteriores, veremos que son realmente expansiones. Tomemos un directorio home que aparezca de la siguiente forma:

 [me@linuxbox ~]$ ls
 Desktop ls-output.txt Pictures Templates
 Documents Music Public Videos

podríamos llevar a cabo las siguientes expansiones:

 [me@linuxbox ~]$ echo D*
 Desktop Documents

y:

 [me@linuxbox ~]$ echo *s
 Documents Pictures Templates Videos

o también:

 [me@linuxbox ~]$ echo [[:upper:]]*
 Desktop Documents Music Pictures Public
 Templates Videos

y mirando más allá de nuestro directorio home:

 [me@linuxbox ~]$ echo /usr/*/share
 /usr/kerberos/share /usr/local/share

Expansión de nombres de archivos ocultos
Como sabemos, los nombres de archivo que empiezan por un punto están ocultos. La expansión de nombres de archivo también respeta este comportamiento. Una expansión como:


 echo *

no revela los archivos ocultos.

Podría parecer a primera vista que podríamos incluir archivos ocultos en una expansión comenzando el patrón con un punto, así:

 echo .*

Casi funciona. De todas formas, si examinamos los resultados atentamente, veremos que los nombres “.” y “..” también aparecen en los resultados. Como estos nombres se refieren al directorio actual y su directorio padre, usar este patrón probablemente producirá un resultado incorrecto. Podemos verlo si probamos el comando:

 ls -d .* | less

Para ejecutar mejor una expansión de nombres de archivo en esta situación, tenemos que emplear un patrón más específico:

 echo .[!.]*

Este patrón se expande en todos los nombres de archivo que empiecen con un punto, no incluye un segundo punto, seguido de cualquier otro carácter. Ésto funcionará correctamente con la mayoría de archivos ocultos (piensa que todavía no incluirá los nombres de archivo con múltiples puntos al principio). El comando ls con la opción -A (“almost all” o “casi todo”) proporcionará un listado correcto de los archivos ocultos:

 ls -A

Expansión de la tilde de la ñ


Como recordarás de nuestra introducción al comando cd, el carácter virgulilla, o tilde de la ñ (“~” tiene un significado especial. Cuando se usa al principio de una palabra, se expande en el nombre del directorio home del usuario nombrado, o si no se nombra a ningún usuario, en el directorio home del usuario actual:

 [me@linuxbox ~]$ echo ~
 /home/me

Si el usuario “foo” tiene una cuenta, entonces:

 [me@linuxbox ~]$ echo ~foo
 /home/foo

Expansión aritmética


El shell permite realizar aritmética mediante la expansión. Ésto nos permite usar el prompt del shell como una calculadora:

 [me@linuxbox ~]$ echo $((2 + 2))
 4

La expansión aritmética usa la forma:

 $((expresión))

donde expresión es una expresión aritmética consistente en valores y operadores aritméticos.

La expansión aritmética sólo soporta enteros (números enteros sin decimales), pero puede realizar un buen número de operaciones diferentes. Aquí hay unos pocos de los operadores soportados:


Operadores aritméticos

+   Suma

-   Resta

*   Multiplicación


/   División (pero recuerda, como la
 expansión sólo soporta enteros, los dos serán enteros.)

%   Módulo, que simplemente significa, “resto”

**  Potencia

Los espacios no son significativos en las expresiones aritméticas y las expresiones puede ser anidadas. Por ejemplo, para multiplicar 5 al cuadrado por 3:

 [me@linuxbox ~]$ echo $(($((5**2)) * 3))75

Los paréntesis sencillos pueden ser usados para agrupar subexpresiones. Con esta técnica, podemos reescribir el ejemplo anterior y obtener el mismo resultado usando una única expansión en lugar de dos:

 [me@linuxbox ~]$ echo $(((5**2) * 3))
 75

Aquí tenemos un ejemplo usando los operadores división y resto. Fíjate el efecto de la división con enteros:

 [me@linuxbox ~]$ echo Five divided by
 two equals $((5/2))
 Five divided by two equals 2
 [me@linuxbox ~]$ echo with $((5%2)) left over. with 1 left over.

La expansión aritmética será tratada más adelante.

Expansión con llaves


Quizás la expansión más extraña es la llamada expansión con llaves. Con ella, puedes crear múltiples cadenas de texto desde un patrón que contenga llaves. Aquí tienes un ejemplo:

 [me@linuxbox ~]$ echo Front-{A,B,C}-Back
 Front-A-Back Front-B-Back Front-C-Back

Los patrones a expandir con llaves deben contener un prefijo llamado preámbulo y un sufijo llamado postcript. La expresión entre llaves debe contener una lista de cadenas separadas por comas o un rango de números enteros o caracteres individuales. El patrón no debe contener espacios en blanco. Aquí hay un ejemplo usando un rango de números enteros:

 [me@linuxbox ~]$ echo Number_{1..5}
 Number_1 Number_2 Number_3 Number_4 Number_5

Los números enteros también pueden tener ceros a la izquierda así:

 [me@linuxbox ~]$ echo {01..15}
 01 02 03 04 05 06 07 08 09 10 11 12 13
 14 15
 [me@linuxbox ~]$ echo {001..15}
 001 002 003 004 005 006 007 008 009 010
 011 012 013 014 015

Un rango de letras en orden inverso:

 [me@linuxbox ~]$ echo {Z..A}
 Z Y X W V U T S R Q P O N M L K 
J I H G F E D C B A

Las expansiones con llaves puede ser anidadas:

 [me@linuxbox ~]$ echo a{A{1,2},B{3,4}}b
 aA1b aA2b aB3b aB4b

¿Y para qué sirve ésto? La aplicación más común es hacer listas de archivos o directorios a crear. Por ejemplo, si fuésemos fotógrafos con una gran colección de imágenes que queremos organizar en años y meses, la primera cosa que deberíamos hacer es crear una serie de directorios nombrados en formato numérico “Año-Mes”. De esta forma, los directorios se ordenarán cronológicamente. Podríamos escribir la lista completa de directorios, pero sería un montón de trabajo y sería muy fácil equivocarnos. En lugar de eso, podríamos hacer esto:

 [me@linuxbox ~]$ mkdir Photos
 [me@linuxbox ~]$ cd Photos
 [me@linuxbox Photos]$ mkdir {2007..2009}-{01..12}
 [me@linuxbox Photos]$ ls
 2007-01 2007-07 2008-01 2008-07 2009-01 2009-07
 2007-02 2007-08 2008-02 2008-08 2009-02 2009-08
 2007-03 2007-09 2008-03 2008-09 2009-03 2009-09
 2007-04 2007-10 2008-04 2008-10 2009-04 2009-10
 2007-05 2007-11 2008-05 2008-11 2009-05 2009-11
 2007-06 2007-12 2008-06 2008-12 2009-06 2009-12

¡Muy astuto!

Expansión con parámetros


Sólo vamos a tratar brevemente la expansión con parámetros en este capítulo, pero lo trataremos más extensamente más tarde. Es una función que es más útil en scripts de shell que directamente en la línea de comandos. Muchas de sus capacidades tienen que ver con la capacidad del sistema de almacenar pequeños trozos de datos y dar a cada trozo un nombre. Muchos de esos trozos, mejor llamados variables, están disponibles para que los examines. Por ejemplo, la variable llamada “USER” contiene tu nombre de usuario. Para invocar la expansión con parámetros y revelar el contenido de USER deberías hacer ésto:

 [me@linuxbox ~]$ echo $USER

Para ver una lista de las variables disponibles, prueba esto:

 [me@linuxbox ~]$ printenv | less

Habrás notado que con otros tipos de expansión, si escribes mal un patrón, la expansión no se lleva a cabo y el comando echo simplemente mostrará el patrón que has escrito mal. Con la expansión con parámetros, si escribes mal el nombre de la variable, la expansión se realizará, pero dando como resultado una cadena vacía:

 [me@linuxbox ~]$ echo $SUER
 [me@linuxbox ~]$

Sustitución de comandos


La sustitución de comandos nos permite usar la salida de un comando como una expansión:

 [me@linuxbox ~]$ echo $(ls)
 Desktop Documents ls-output.txt Music 
 Pictures Public Templates Videos

Una de mis favoritas hace algo como ésto:

 [me@linuxbox ~]$ ls -l $(which cp)
 -rwxr-xr-x 1 root root 71516 2007-12-05 08:58 
 /bin/cp

Aquí hemos pasado el resultado de which cp como un argumento para el comando ls, de esta forma tenemos el listado del programa cp sin tener que saber su ruta completa. No estamos limitados a comandos simples sólo. Pipelines completas pueden ser usadas (sólo se muestra una salida parcial):

 [me@linuxbox ~]$ file $(ls -d /usr/bin/* | grep zip)
 /usr/bin/bunzip2: symbolic link to `bzip2'
 /usr/bin/bzip2: ELF 32-bit LSB executable, Intel 80386,
 version 1 (SYSV), dynamically linked (uses shared libs), for
 GNU/Linux 2.6.9, stripped
 /usr/bin/bzip2recover: ELF 32-bit LSB executable, Intel 80386,
 version 1 (SYSV), dynamically linked (uses shared libs), for
 GNU/Linux 2.6.9, stripped
 /usr/bin/funzip: ELF 32-bit LSB executable, Intel 80386,
 version 1 (SYSV), dynamically linked (uses shared libs), for
 GNU/Linux 2.6.9, stripped
 /usr/bin/gpg-zip: Bourne shell script text executable
 /usr/bin/gunzip: symbolic link to `../../bin/gunzip'
 /usr/bin/gzip: symbolic link to `../../bin/gzip'
 /usr/bin/mzip: symbolic link to `mtools'

En éste ejemplo, el resultado del pipeline se convierte en la lista de argumentos del comando file.

Hay una sintaxis alternativa para la sustitución de comandos en programas de shell antiguos que también es soportada por bash. Utiliza tildes invertidas en lugar del signo del dólar y los paréntesis:

 [me@linuxbox ~]$ ls -l `which cp`
 -rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp

Entrecomillado


Ahora que hemos visto de cuantas formas el shell puede realizar expansiones, es hora de aprender cómo podemos controlarlas. Tomemos un ejemplo:

 [me@linuxbox ~]$ echo this is a test
 this is a test

o:

 [me@linuxbox ~]$ echo The total is $100.00
 The total is 00.00

En el primer ejemplo, la división por palabras del shell ha eliminado el espacio en blanco de la lista de argumentos del comando echo. En el segundo ejemplo, la expansión con parámetros sustituyó una cadena vacía con el valor de “$1” porque era una variable indefinida. El shell proporciona un mecanismo llamado quoting (entrecomillado) para suprimir selectivamente expansiones no deseadas.

Comillas dobles


El primer tipo de citas que vamos a ver son las comillas dobles. Si colocas un texto dentro de comillas dobles, todos los caracteres especiales utilizados por el shell perderán su significado especial y serán tratados como caracteres ordinarios. Las excepciones son “$”, “\” (barra invertida), y “`” (tilde invertida). Esto significa que la división por palabras, expansión de nombres de archivo, expansión de la tilde de la ñ y la expansión con llaves están suprimidas, pero la expansión con parámetros, la expansión aritmética y la sustitución de comandos sí que funcionarán. Usando comillas dobles, podemos manejar nombres de archivo que contengan espacios en blanco. Digamos que somos la desafortunada víctima de un archivo llamado two words.txt. Si tratáramos de usarlo en la línea de comandos, la separación de palabras haría que fuera tratado como dos argumentos separados en lugar del único argumento que queremos:

 [me@linuxbox ~]$ ls -l two words.txt
 ls: cannot access two: No such file or directory
 ls: cannot access words.txt: No such file or directory

Usando comillas dobles, paramos la separación por palabras y obtenemos el resultado deseado; más aún, incluso podemos reparar el daño causado:

 [me@linuxbox ~]$ ls -l "two words.txt"
 -rw-rw-r-- 1 me me 18 2008-02-20 13:03 two words.txt
 [me@linuxbox ~]$ mv "two words.txt" two_words.txt

¡Ahí lo tienes! Ahora no tenemos que seguir escribiendo esas malditas comillas dobles.
Recuerda, la expansión con parámetros, la expansión aritmética y la sustitución de comandos siguen funcionando dentro de las comillas dobles:

 [me@linuxbox ~]$ echo "$USER $((2+2)) $(cal)"
 me 4 February 2008
 Su Mo Tu We Th Fr Sa
                 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

Deberíamos tomarnos un momento para mirar el efecto de las comillas dobles en la sustitución de comandos. Primero miremos un poco más atentamente a cómo funciona la sustitución de palabras. En nuestro ejemplo anterior, vimos como la sustitución de palabras parece eliminar los espacios sobrantes en nuestro texto:

 [me@linuxbox ~]$ echo this is a test
 this is a test

Por defecto, la sustitución de palabras busca la presencia de espacios, tabulaciones y lineas nuevas (caracteres de inicio de línea) y las trata como delimitadores entre palabras. Ésto significa que los espacios sin comillas, tabuladores y nuevas líneas no se consideran parte del texto. Sólo sirven como separadores. Como separan las palabras en diferentes argumentos, nuestra línea de comandos de ejemplo contiene un comando seguido de diferentes argumentos. Si añadimos comillas dobles:

 [me@linuxbox ~]$ echo "this is a test"
 this is a test

la separación de palabras se suprime y los espacios en blanco no se tratan como separadores, en lugar de eso pasan a ser parte del argumento. Una vez que añadimos las comillas dobles, nuestra línea de comandos contiene un comando seguido de un único argumento.

El hecho de que las líneas nuevas sean consideradas como separadores por el mecanismo de separación de palabras provoca un interesante, aunque sutil, efecto en la sustitución de comandos. Considera lo siguiente:

 [me@linuxbox ~]$ echo $(cal)
 February 2008 Su Mo Tu We Th Fr Sa 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
 [me@linuxbox ~]$ echo "$(cal)"
 February 2008
 Su Mo Tu We Th Fr Sa
                 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

En el primer caso, la sustitución de comandos sin comillas resulta en una línea de comandos que contiene 38 argumentos. En el segundo, una linea de comandos con un argumento que incluye los espacios en blanco y las nuevas líneas.

Comillas simples


Si necesitamos suprimir todas las expansiones, usamos comillas simples. A continuación vemos una comparación entre sin comillas, comillas dobles y comillas simples:

 [me@linuxbox ~]$ echo text ~/*.txt {a,b} 
$(echo foo) $((2+2)) $USER
 text /home/me/ls-output.txt a b foo 4 me
 [me@linuxbox ~]$ echo "text ~/*.txt {a,b} 
$(echo foo) $((2+2)) $USER"
 text ~/*.txt {a,b} foo 4 me
 [me@linuxbox ~]$ echo 'text ~/*.txt {a,b} 
$(echo foo) $((2+2)) $USER'
 text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER

Como podemos ver, con cada nivel sucesivo de entrecomillado, se van suprimiendo más expansiones.

Caracteres de escape


Algunas veces sólo queremos entrecomillar un único carácter. Para hacerlo, podemos preceder un carácter con una barra invertida, que en este contexto se llama carácter de escape. A menudo se hace dentro de las comillas dobles para prevenir selectivamente una expansión:

 [me@linuxbox ~]$ echo "The balance for user 
$USER is: \$5.00"
 The balance for user me is: $5.00

También es común usar caracteres de escape para eliminar el significado especial de un carácter en un nombre de archivo. Por ejemplo, es posible usar caracteres en nombres de archivo que normalmente tienen un significado especial para el shell. Ésto incluye “$”, “!”, “&”, “ “, y otros. Para incluir un carácter especial en un nombre de archivo puedes hacer ésto:

 [me@linuxbox ~]$ mv bad\&filename good_filename

Para permitir que la barra invertida aparezca, la “escapamos” escribiendo “\\”. Fíjate que dentro de las comillas simples, la barra invertida pierde su significado especial y se trata como un carácter ordinario.

Secuencias de escape con la barra invertida


Además de su rol como carácter de escape, la barra invertida también se usa como parte de una notación para representar ciertos caracteres especiales llamados códigos de control. Los primeros 32 caracteres en el esquema de código ASCII se usan para transmitir comandos a dispositivos de la familia de los teletipos. Algunos de estos códigos son familiares (tabulador, salto de línea y salto de párrafo), mientras que otros no lo son (nulo, fin de la transmisión, y entendido).


\a  Tono (“Alerta” - hace que el ordenador pite)

\b  Retroceder un espacio

\n  Nueva línea. En sistemas 
como Unix, produce un salto de línea

\r  Retorno de carro

\t  Tabulación

La tabla anterior lista algunos de las secuencias de caracteres de escape más comunes. La idea detrás de esta representación usando la barra invertida se originó en la programación en lenguaje C y ha sido adoptada por otros muchos, incluido el shell.

Añadiendo la opción “-e” a echo activaremos la interpretación de las secuencias de escape. También puedes colocarlos dentro de $' '. Aquí, usando el comando sleep, un programa simple que sólo espera a que le digamos un número específico de segundos y luego se cierra, podemos crear un primitivo cronómetro de cuenta atrás:

 sleep 10; echo -e "Time's up\a"

También podríamos hacer ésto:

 sleep 10; echo "Time's up" $'\a'

Resumiendo

A medida que avancemos en el uso del shell, encontraremos que las expansiones y entrecomillados se usarán con mayor frecuencia, así que cobra sentido tener un buen entendimiento de la forma en que funcionan. De hecho, se podría decir que son el aspecto más importante a aprender sobre el shell. Sin un entendimiento adecuado de la expansión, el shell siempre será una fuente de misterio y confusión, y perderemos mucho de su potencial.

Para saber más


La man page de bash tiene secciones principales tanto de expansión como de entrecomillado que cubren estos asuntos de una manera más formal.

El Manual de Referencia de Bash también contiene capítulos de expansión y entrecomillado:

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.