Logo de FotoLibre

Fotografía Libre con Software Libre

Octubre 30, 2007

Curso de bash para fotógrafos linuxeros. (7) Comandos internos y externos. Órdenes compuestas.

por Redy @ 11:14 pm — Archivado en: Fotografí­a Libre


Comandos internos y externos

Hay una serie de comandos que la propia shell ejecuta sin ninguna ayuda, a esos comandos les llamaremos comandos internos.
A modo meramente informativo puedo poner una relación de los comandos internos de bash: ., :, [, alias, bg, bind, break, builtin, caller, cd, command, compgen, complete, continue, declare, dirs, disown, echo, enable, eval, exec, exit, export, false, fc, fg, getopts, hash, help, history, jobs, kill, let, local, logout, popd, printf, pushd, pwd, read, readonly, return, set, shift, shopt, source, suspend, test, times, trap, true, type, typeset, ulimit, umask, unalias, unset, wait

Cualquier otro comando que no se encuentre entre estos, bash no sabe ejecutarlo, y entenderá que es un programa externo, así que lo buscará en una serie de directorios que se la han configurado.

Por ejemplo si tecleamos:

$ ls

El bash ejecutará un programa que está en el directorio /bin/ y se lama ls.

Hay que decir también que existen ciertas redundancias, bash maneja internamente ciertos comandos que también son ejecutables externos, otras shells quizá no los manejen internamente, por eso son necesarios como ejecutables externos. Por ejemplo el comando 'pwd' nos informa de en que directorio nos encontramos, y es un comando interno de bash, pero también es un programa externo que está en /bin/. En casos así siempre se ejecuta primero el interno.

$ pwd
/home/redy

Si existe esta coincidencia, pero por la razón que sea necesitamos ejecutar el comando externo y no el interno debemos hacerlo indicando su path completo:

$ /bin/pwd

Aparentemente el resultado es el mismo, pero se ejecutó un programa externo en vez del comando interno. Prueba

$ /bin/pwd --help
Modo de empleo: /bin/pwd [OPCIÓN]
 
Muestra el nombre de fichero completo del directorio de trabajo actual.
   –help muestra esta ayuda y finaliza
   –version informa de la versión y finaliza
NOTE: your shell may have its own version of pwd, which usually supersedes
the version described here. Please refer to your shell’s documentation
for details about the options it supports.

Otra forma de evitar los comandos internos es usando el comando enable que permite activar o desactivar el manejo interno del comando que queramos. Si tecleamos por ejemplo

$ enable -n pwd

A partir de ese momento y hasta que salgamos de la shell o lo volvamos a activar pwd nunca será tratado como comando interno, se ejecutará en su lugar el programa externo. Para volver a activarlo como comando interno deberás ejecutar

$ enable pwd

Para ver todos los comandos que se manejan internamente, puedes usar 'enable' sin parámetros o 'enable -p'. Y para ver los que están desactivados puedes usar 'enable -n'. Por último 'enable -a' te los muestra todos mezclados.

$ enable
$ enable -p
$ enable -n
$ enable -a

Una orden muy útil para ver como tratará bash un comando es 'type'. Type nos informa de si un comando es manejado internamente o externamente.
$ type man
man is /usr/bin/man
$ type -t man
file
$ type pwd
pwd is a shell builtin
$ type -t pwd
builtin
$ enable -n pwd
$ type pwd
pwd is /bin/pwd
$ type -t pwd
file

Todo comando que el bash no sepa manejar internamente, lo busca en los directorios dónde residen los programas. Busca un programa con el mismo nombre que el comando que tecleamos, puede ser una utilidad del sistema operativo, o un programa de usuario, no hay diferencia, si lo encuentra lo ejecuta, y si no nos avisa del error diciendo 'command not found.'.
Existe una variable que tiene un significado especial, su nombre es PATH, y contiene la lista de directorios separados por ':' en los que la shell busca los programas.

Cuando hablamos de 'comando' sin más, sin definir si es externo o interno, nos referimos tanto a los comandos que el propio bash interpreta, como a los programas que tenemos instalados en nuestro sistema operativo.


Concepto de programa y proceso

Quizá este sea el momento de definir lo que es un programa. Un programa es un archivo o un conjunto de archivos que contienen instrucciones y datos, el ordenador de alguna forma puede ejecutar las instrucciones de ese programa. Por ejemplo ls es un programa, contenido en un archivo, que está en el directorio /bin/ y que realiza una tarea que es la de mostrar el contenido de un directorio.

Fundamentalmente hay dos tipos de programas: unos está escritos en código binario, en lenguaje, que el propio procesador puede entender. No es que se escriban así, generalmente se escriben en un lenguaje de programación de alto nivel, más inteligible por seres humanos y un programa (compilador) los traduce al lenguaje binario del procesador. En cambio hay otro tipo de programas que están escritos en un lenguaje que el procesador no entiende directamente, generalmente son archivos de texto que tendrán que ser interpretados por otro programa binario que los va traduciendo línea a línea y los va ejecutando. Tal es el caso de los scripts que escribimos en las anteriores entregas, son archivos de texto que el programa binario /bin/bash, tiene que interpretar. Pues bien en nuestro sitema habrá de los dos tipos. Por ejemplo en el mío:
$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), for GNU/Linux 2.6.9, dynamically linked (uses shared libs), for GNU/Linux 2.6.9, stripped
$ file /bin/egrep
/bin/egrep: Bourne shell script text executable
$ file /usr/bin/partmon
/usr/bin/partmon: perl script text executable
$ file /usr/bin/helpviewer
/usr/bin/helpviewer: a python script text executable
$ file /usr/bin/spamassassin
/usr/bin/spamassassin: awk script text

Ya veis que hay tanto programas binarios, como scripts en formato texto tanto de bash como de otros intérpretes como pueden ser el perl o el python, el awk..

En un entorno multitarea y multiusuario como Linux hay que distinguir programa y proceso. Se denomina proceso a un programa que se encuentra en ejecución. La multitarea permite que un mismo programa sea ejecutado simultáneamente varias veces, ya sea porque varios usuarios lo han lanzado, o porque un único usuario lo ha lanzado varias veces. Esto es: un mismo programa puede generar varios procesos. En linux cada proceso tiene un número de identificador (PID). Un proceso (proceso padre) puede desencadenar otros (procesos hijos), y un proceso no puede finalizar mientras haya procesos hijos que no hayan terminado. En linux un único proceso llamado init, que siempre tiene el PID 1, es el padre (bueno mejor dicho el ancestro) de todos los demás procesos. Para ver los procesos en ejecución se utiliza el comando ps, pero no adelantemos acontecimientos porque dedicaremos un capítulo a la gestión de procesos.

Lo que si quiero reseñar es que cada proceso tiene un área de memoria, a la que llamamos entorno del proceso dónde se guardan sus parámetros y variables. Cada proceso tiene su entorno y el hecho de modificar un parámetro o variable en un proceso no afectará para nada a los parámetros de otro proceso, aunque se llamen igual. Cuando un proceso finaliza su entorno desaparece con él. Ahora bien cuando se crea un proceso hijo se copia parte del entorno del padre, ese entorno que se hereda es una copia y como dije antes se destruye al acabar el proceso hijo, sin afectar para nada al entorno del padre, ni a otros entornos de otros procesos. Si hablamos de variables; podemos decidir que variables serán copiadas en los procesos hijos y cuales no marcándolas como exportables. El comando para hacer esto es export y su sintáxis es más o menos:
'export VAR1[=valor1] ... [VAR_N=[valor_n]]'.
Si se usa export con una lista de una o más variables estas se marca para ser exportadas a los procesos hijos. Se puede asignar un valor a la variable al mismo tiempo.

$ A=5
$ B=6
$ export B # Los procesos hijos tendrán una variable B que copiará su valor del proceso padre
$ bash # Abrimos un proceso bash hijo
$ echo $A # A no tiene valor en el proceso hijo
$ echo $B # Pero B si, ha copiado el valor del padre en el entorno del hijo
$ B=7 # Modificamos B en el hijo
$ echo $B # Vemos que se ha modificado
$ exit # Finalizamos el proceso hijo
$ echo $A # Las variables del proceso padre siguen conservando sus valores
$ echo $B # Incluso la B que había sido modificada en el hijo, pero claro, era una copia.

Si queremos desmarcar una variable que ha sido previamente marcada como exportable usaremos 'export -n variable', esto hará que no se copie a los procesos hijos que se puedan desencadenar desde ese momento. Y si queremos obtener una lista de todas las variables, que se copiarán al entorno de los procesos hijos cuando estos se inicien usaremos el comando export sin ningún argumento o con el argumento -p

$ export -p

Los comandos internos se ejecutan normalmente en el mismo proceso en el que estamos, pero para ejecutar comandos externos bash genera un proceso hijo en el que se ejecuta el comando. Es importante reseñar esto ya que si el comando necesita acceder al valor de una variable es necesario que ésta esté en el entorno del comando (proceso hijo). Por ejemplo si trabajamos con varios displays (varios monitores, o varias consolas gráficas virtuales, y queremos ajustar la gamma de uno de ellos podemos usar el comando xgamma que por defecto ajustará el display indicado en la variable $DISPLAY


$ export -n DISPLAY # por si acaso :-)
$ DISPLAY=:1
$ xgamma 1.2

No funcionará como queremos porque al ejecutarse xgamma en un proceso hijo, en su entorno la variable DISPLAY no está definida. Tendríamos que hacer:


$ DISPLAY=:1
$ export DISPLAY
$ xgamma 1.2

Así cuando se genera el proceso hijo, la variable DISPLAY, marcada para exportación se copiará con el valor que tenga del entorno del proceso padre all entorno del proceso hijo. En lugar de dos órdenes también se puede usar una sola, que es más cómodo, para exportar y asignar valor:

$ export DISPLAY=:1
$ xgamma 1.2

O simplemente usar la forma de asignar variables en la misma línea del comando que ya hemos visto:

$ DISPLAY=:1 xgamma 1.2

Decíamos en el capítulo anterior que esta es una asignación temporal del valor de la variable; en realidad lo que hacemos con ese comando es asignar valor a una variable en el entorno del proceso hijo que vamos a iniciar para ejecutar el comando xgamma.

Cuando una variable está marcada para exportación y se copia a un proceso hijo conserva todos sus atributos, marca de esportación incluída, así que si no hacemos algo que lo evite también se copiará a los proceso hijos del hijo y así sucesivamente.

Hay ciertas variables que se definen en los ficheros de configuración del bash, y que normalmente tienen el atributo de exportables. Un ejemplo es PATH que mencionábamos antes que es uasad por la shel para saber dónde buscar programas ejecutables, es decir comandos externos.

Órdenes compuestas

Hablábamos ya en la entrega 1 de las órdenes simples, y decíamos: "Si comparamos el lenguaje bash con el castellano, podríamos decir que cada orden es como un frase. Cada orden consta de varios elementos (palabras) y tiene significado propio, puede ser interpretada y ejecutado por la shell. Como en nuestro idioma, toda frase debe tener normalmente un verbo, en bash los verbos son los comandos, que generalmente van al principio de la orden. [...] Hay ordenes que no tienen más que el comando, y hay otras que llevan argumentos adicionales. [...] Cuando estudiábamos lengua en el cole, había oraciones simples y compuestas. Las simples tenían un solo verbo y las compuestas, se formaban combinando dos o más oraciones simples, y normalmente tenían más de un verbo. Además había varios tipos de oraciones compuestas, coordinadas, subordinadas... En el lenguaje de bash pasa lo mismo, hay órdenes simples con un solo comando, y hay órdenes compuestas que llevan varios comandos. En el castellano para unir varias frases simples en una farse compuesta se usan la conjunciones, en bash se usan también unos signos que denominaremos separadores.

El caso más simple de ordenes compuestas consiste en juntar varias ordenes simples, para que se ejecuten una tras otra separándolas por un punto y coma ';' tal que así

$ echo "Este es el listado del directorio"; ls; echo "Buenos días"

Hay otras formas de formar órdenes compuestas, que iremos viendo poco a poco, pero antes tengo que explicar algunos conceptos:

Ejecución de programas, código de terminación

Siempre que se ejecuta un comando, ya sea este interno o externo, al finalizar el proceso obtenemos un código de terminación, que es un número de 0 a 65535. Normalmente, y salvo que el programador haya decidido otra cosa, se sigue el convenio de que si el programa finalizó sin ningún error se obtiene un código 0 y si hubo algún problema un código distinto de 0.

Podemos consultar el código de terminación del último comando ejecutado viendo el valor del parámetro $? por ejemplo con el comando echo, tal que así:
$ echo $?

Prueba a hacer un ls de un fichero existente y de otro que no existe y verás como el código de terminación cambia:

$ cd ~/ejercicios_curso_bash
$ touch fichero_que_existe
$ ls fichero_que_existe
$ echo $?
0
$ ls fichero_que_no_existe
$ echo $?
1

Ordenes compuestas condicionadas

Una vez entendido esto volvemos a las órdenes compuestas. Más arriba veíamos como en una sola orden podíamos agrupar varias ordenes simples usando el separador ';'. Hay dos separadores que hacen lo mismo pero la segunda (y sucesivas ordenes si las hubiese), se ejecutará o no en función del código de terminación de la orden anterior.

Para ejecutar una orden si el código de terminación de la anterior fue 0 usaremos el separador '&&' dos signos de andpersand consecutivos de la siguiente forma.


$ ls fichero_que_existe && echo "El fichero existe"
fichero_que_existe
El fichero existe

$ ls fichero_que_no_existe && echo "Esto no se imprimirá"
ls: fichero_que_no_existe: No existe el fichero o el directorio

Para ejecutar una orden si el código de terminación de la anterior fue distinto de 0, usaremos el separador '||' dos barras verticales consecutivas tal que así:


$ ls fichero_que_existe || echo "Esto no se imprime"
fichero_que_existe
$ ls fichero_que_no_existe || echo "El comando ls terminó con error $?"
ls: fichero_que_no_existe: No existe el fichero o el directorio
El comando ls terminó con error 1

Los ejemplos vistos unen dos ordenes simples en una compuesta, pero podemos concatenar más de dos combinando los separadores como nos interese.

$ ls fichero_que_existe && ls fichero_que_no_existe || echo "hola"
fichero_que_existe
ls: fichero_que_no_existe: No existe el fichero o el directorio
hola

 
$ ls fichero_que_existe && echo "CT: $?"; echo "El fichero existe"
fichero_que_existe
CT: 0
El fichero existe

Como pueden combinarse muchas o?denes simples usando varios separadores hay que establecer unas prioridades para le evaluación de estos, es como en matemáticas que la multiplicación tiene prioridad sobre la suma y la resta por ejemplo, y por tanto si ponemos 3 + 2 * 5, se entiende que primero se multiplica el dos por el cinco y a continuación se suma a tres el resultado de ese producto. Aquí es igual, los separadores anteriores se ejecutan en un orden de prioridad: primero se ejecuta '&&' y '||' que tienen la misma prioridad, y se ejecutarán por órden de izquierda a derecha. Y luego está ';'. En caso de duda podemos hacer agrupaciones con llaves o con paréntesis.

Agrupación con llaves: Toda lista de ordenes que agrupemos entre llaves funcionará, en cuanto a prioridad se refiere como si fuese una sola orden simple. Hay que dejar espacios siempre entre las llaves y las ordenes (para que la shell no las confunda con una expansión de llaves) , y la lista tiene que acabar en un ';' antes de cerrar la última llave. Por ejemplo:

$ ls fichero_que_no_existe && { echo hola ; ls pepe || echo adios ; }
Dada la prioridad de cada separador, si no pusiesemos ninguna llave llave sería como ejecutar:
${ ls fichero_que_no_existe && echo hola ; } ; { ls pepe || echo adios ; }

Los comandos entre llaves se agrupan y se ejecutan como procesos de la shell actual y el código de terminación obtenido como resultado de la evaluación de las llaves es el del último comando ejecutado dentro de las llave.

Agrupación con paréntesis: funciona de manera similar. Como el paréntesis no puede confundirse con una expansíon como era el caso de las llaves, no es necesario respetar los espacios. La agrupación con paréntesis no se ejecuta en la misma shell sino que se abre en un proceso hijo otra subshell y se ejecuta dentro de esta, por lo que si las órdenes modifican el entorno en la subshell, el entorno de la shell original permanece invariable al finalizar. No hace falta acabar en ';' antes de cerrar el último paréntesis.

Las llaves se ejecutan en la misma shell con la siguiente orden compuesta podéis ver que entre nuestra lista de procesos, solo hay un bash.

$ { echo Comandos entre llaves ; ps f; }
Comandos entre llaves
PID TTY STAT TIME COMMAND
14911 pts/2 S 0:00 bash
15358 pts/2 R 0:00 ps f

Sin embargo en una orden entre paréntesis se abre otro proceso con otro bash en el que se ejecutan las órdenes.
$ (echo Comandos entre paréntesis ; ps f)
Comandos entre paréntesis
PID TTY STAT TIME COMMAND
14911 pts/2 S 0:00 bash
17637 pts/2 S 0:00 bash
17638 pts/2 R 0:00 \_ ps f

$ A=5; { A=6; echo 'Dentro $A vale' $A; } ; echo 'Fuera $A vale' $A
Dentro $A vale 6
Fuera $A vale 6

$ A=5; (A=6; echo 'Dentro $A vale' $A ) ; echo 'Fuera $A vale' $A
Dentro $A vale 6
Fuera $A vale 5

Algo de lógica

Para los que sepan programar en C u otro lenguaje similar habrán visto en los separadores "||" y "&&" una relación con los operadores de C de igual grafía que representan a los operadores lógicos and y a or respectivamente.

Que no se asuste nadie. No hace falta saber C, para entender lo que voy a intentar explicar. Eso sí, si no se presta atención puede ser algo lioso de entender, así que espero muchas dudas y consultas de este capítulo, porque ya sabéis que el que no pregunta nada, o lo sabe todo, o no sabe nada.

Decíamos que si todo iba sin errores el estado de terminación era 0 y si había error era algo distinto a 0. Podemos asumir que cero es 'Éxito' y no cero es 'Fallo'. Para abstraernos a algo más booleano (de Boole, el padre de la lógica), podemos entender que 'Éxito' es como un resultado 'Cierto' y 'Fallo' es como un resultado 'Falso', o sea que cero es Cierto, y no cero es Falso. (A veces cuesta pensar en lógica negativa, ya que estamos acostumbrados a que el cero sea falso, pero en bash el cero es cierto).

En lógica de Boole tenemos dos operadores muy usados.

A veces a estos dos operadores se les llama suma lógica (O) y producto lógico (Y) pero yo creo que se entiende más con la nomenclatura de las conjunciones Y y O

El operador Y (and en inglés) que dadas dos proposiciones nos devuelve 'Cierto' si ambas son Ciertas y 'Falso' Si alguna de ellas es falsa.

(Yo soy usuario de Fotolibre) Y (Me gusta Windows)

Es falso porque una de las dos proposiciones es falsa pero...

(Yo soy usuario de Fotolibre) Y (Sé algo de bash)

Es cierto porque ambas son ciertas.

El otro operador lógico es O (or en inglés) que nos da como resultado Cierto si alguna de las proposiciones es cierta, y falso solo si ambas son falsas.

(Yo soy usuario de Fotolibre) O (Me gusta Windows)

Es cierto porque al menos una de las dos es cierta, pero...

(Me gusta Windows) O (Odio Linux)

Es falso porque ambas son falsas.

En realidad el separador && es un operador lógico Y. Entonces ¿Porque sólo se ejecuta la segunda sentencia si la primera devuelve un error 0?

Tal como decíamos antes 0='Éxito'='Cierto'. Aplicando un principio de comodidad, si la primera orden nos da cierto, tenemos que evaluar si se cumple la segunda para saber si se ambas son ciertas. Pero si la primera no es cierta ya no necesitamos seguir evaluando, ya sabemos que al menos una no es cierta, para que vamos a ejecutar la otra.

La tabla de esta operación lógica sería:

   En lógica:                               En bash:
   -----------------------------            ----------
   Cierto Y Cierto     = Cierto             0 && 0 = 0
   Cierto Y Falso      = Falso              0 && 1 = 1
   Falso  Y lo_que_sea = Falso              1 && X = 1

De forma análoga el separador '||' es un operador lógico O. Así que si la primera orden nos da un resultado de 0, es decir cierto, no se ejecuta la segunda, porque ya sabemos que al menos una de las dos es cierta. Por el contrario si el resultado de la primera es no cero, o sea falso, debemos seguir ejecutando porque, para saber si alguna es cierta, debemos saber el resultado de la segunda. La tabla de la operación lógica O sería:

   En lógica:                               En bash:
   -----------------------------            ----------
   Cierto O lo_que_sea = Cierto             0 || X = 0
   Falso  O Cierto     = Cierto             0 || 1 = 0
   Falso  O Falso      = Falso              1 || 1 = 1

El resultado de la evaluación Y es 0 (Cierto) si ambas ordenes devuelven 0. En realidad se devuelve el resultado de la última orden ejecutada, si la primera es 'no 0' (Falso) se devuelve este valor, ya que la siguiente no se ejecuta. Y si la primera es 0 y la segunda no, se devuelve el valor de terminación de la segunda. Así que efectivamente funciona como una operación lógica Y.

En una evaluación O también se devuelve siempre el código de terminación de la última orden ejecutada, deducirá el lector fácilmente que también funciona como una operación lógica O.

Si te has liado con lo último, no te preocupes, recuerda lo dicho en el apartado anterior:

&& (and ó Y) ejecuta una orden si la anterior devolvió 0 (no error)
|| (or ó O) ejecuta una orden si la anterior no devolvió 0 (error)

Como regla nemotécnica para recordar que cual es Y y cual es O solo tenéis que recordar que el & en los nombres de sociedades significa Y, como en 'Manolo Fdez. & Hijos'. :-) Y para entender el funcionamiento puedes remplazar el && (Y) por un 'Y (si ha funcionado)' o el || (O) por un 'O (si no)' por ejemplo:

$ ls fichero_que_existe && echo "El fichero existe"

Lo leeríamos como '(ls fichero_que_existe) y si ha funcionado (echo "El fichero existe")' y

$ ls fichero_que_no_existe || echo "El fichero no existe"

Lo leeríamos como '(ls fichero_que_no_existe) o si no (echo "El fichero no existe)"

Me interesaría bastante recibir opiniones sobre este capítulo, porque pienso que es especialmente lioso ¿o no?

Octubre 29, 2007

II Open de Boulder MendiExpo’07

por Tat @ 7:54 pm — Archivado en: Fotografí­a Libre

Como el año pasado, este año me he vuelto a pasar por la competición de Boulder asociada a la feria de naturaleza y montaña MendiExpo que se celebra en Irun, en el recinto ferial de FICOBA.

(más...)

Octubre 26, 2007

Curso de bash para fotógrafos linuxeros. (6) Variables y parámetros

por Redy @ 8:13 am — Archivado en: Fotografí­a Libre

Introducción a las variables y parámetros

Cualquiera que conozca cualquier lenguaje de programación tendrá ya claro el concepto de variable, pero para los que no sabéis programar es conveniente hacer una pequeña introducción.

Todo el mundo sabe usar una calculadora. Todas las calculadoras tienen una o varias teclas de ‘memoria’, usando esas teclas podemos almacenar un valor con el que estamos trabajando, para poder recuperarlo después, también podemos hacer operaciones con ese valor, como sumarle o restarle cosas. Una variable, en programación, es muy similar a ese concepto, es un almacén donde guardo un dato, que puedo recuperar cuando lo necesito. Puedo modificar o borrar el dato cuando quiera, es por eso que a esa ‘memoria’ se da el nombre de variable. A diferencia de las calculadoras que normalmente solo tienen una ‘memoria’ con las variables tenemos cuantas necesitemos, es decir podemos almacenar no solo un valor sino muchos, cada uno en un espacio distinto. Para distinguir cada uno de estos espacios de almacenamiento es necesario darle un nombre a cada uno, los nombres los elige el usuario a su libre albedrío, pero siguiendo ciertas reglas.

A diferencia de nuestro ejemplo de la calculadora, que sólo trabaja con números, los ordenadores pueden procesar muchos tipos de datos, números, palabras, textos… las variables pueden almacenar cualquier tipo de datos.

En algunos lenguajes es necesario distinguir de que tipo es el dato que guardamos en una variable, en bash no, la misma variable puede contener en un momento dado un número y en otro un texto.

En realidad las variables son un caso concreto de un concepto más amplio que es el de parámetro. Un parámetro es cualquier entidad capaz de almacenar un valor susceptible de cambiar. Las variables son en realidad parámetros que nosotros podemos crear, destruir o modificar su valor a voluntad. Pero hay otros parámetros a los que no podemos asignar valores pero si consultarlos. Por ejemplo el código de error del último comando ejecutado no puede ser asignado ni modificado por nosotros sin embargo es un valor que puede consultarse.

Llamamos asignar un valor a una variable al hecho de almacenar un valor en el espacio designado por el nombre de ésta, cada variable solo puede contener un valor al mismo tiempo, y si asignamos un nuevo valor a una variable cualquier valor que tuviese previamente se destruye.

Variables, asignación y utilización

A una variable se le puede asignar un valor en bash mediante la orden:

$ NOMBRE=valor

Esta es una de las órdenes cuya primera palabra no es un comando, tal como estábamos acostumbrados a ver, la primera palabra es el nombre de una variable, luego lleva un signo igual y luego un valor, no se ponen espacios ni antes ni despues del signo igual. El nombre puede ser cualquier serie de caracteres alfabéticos (A-Z, a-z), numéricos (0-9) o el caracter de guión bajo ‘_’, pero el primer caracter no puede ser un número, lo siguiente son asignaciones válidas:

$ A=5
$ Mi_Nombre=Redy
$ JC45="Esto es un valor"
$ _T_65=Hola

Estas no valen:

$ 5jK=67 #(El nombre de la variable no puede empezar por un número)
$ A-47=Hola #(El nombre de la variable no puede tener signos, solo letras números y '_')
$ A = 7 #(No se pueden dejar espacios entre la variable y el '=' ni entre el '=' y el valor)
$ NombreCompleto=Redy Rodriguez #(Si el valor lleva espacios hay que usar comillas o escape)

Las letras mayusculas y minusculas en los nombres de variables son diferenciadoras, no es lo mismo A=7 que a=7, son dos variables distintas.

Otra cosa… podemos definir varias variables en la misma orden:

$ A=5 B=6 C=hola

Para obtener el valor de un parámetro, o de un variable, el bash utiliza un mecanismo de expansión, que funciona de manera similar a las expansiones vistas hasta ahora. Cuando bash encuentra en una palabra el signo ‘$’ todo lo que va a continuación se considera el nombre de un parámetro, y todos los caracteres desde el ‘$’ hasta el final de la palabra son sustituidos por el valor contenido en el parámetro (o variable) con dicho nombre.

$ A=Curso
$ B="Redy Rodríguez"
$ echo este $A lo escribe $B
este Curso lo escribe Redy Rodríguez
$ C=mente
$ echo este $A normal$C lo escribe $B
este Curso normalmente lo escribe Redy Rodríguez

Como ves el $ no tiene porque aparecer siempre al principio de la palabra, en este caso no hay ningun problema porque está al final de la palabra. Pero cuando queremos hacer una expansión de variable en medio de una palabra surge un problema: no sabemos donde acaba el nombre de la variable. Por ejemplo, esto no funcionaría como esperamos.

$ A=Normal
$ echo $Amente $B escribe este curso
Redy Rodríguez escribe este curso

Como después de $A no acaba la palabra, bash se cree que el nombre de la variable es ‘Amente’ y como esa variable no tiene ningún valor se sustituye toda la palabra por una cadena vacía. Para evitar este problema podemos recurrir a encerrar el nombre de la variable entre llaves. $A y ${A} significan exactamente lo mismo y se expanden igual pero con la segunda forma las llaves delimitan el nombre y así podemos ponerla en medio de otra palabra:

$ echo ${A}mente $B escribe este curso
Normalmente Redy Rodríguez escribe este curso

La sustitución de parámetros funcionará incluso dentro de expresiones encerradas entre comillas dobles, pero no entre comillas simples. Veamos ejemplos:

$ Nombre=Redy Apellido=Rodriguez
$ echo "Me llamo $Nombre $Apellido"
Me llamo Redy Rodriguez
$ echo 'Si pongo $Nombre $Apellido obtengo:' "$Nombre $Apellido"
Si pongo $Nombre $Apellido obtengo: Redy Rodriguez
$ Directorio=/home/
$ ls $Directorio

Aunque todo lo que hay dentro de las comillas es una sola palabra, ya ves que no es imprescindible usar las llaves cuando el nombre de la variable va seguido de un blanco o de otro signo que no pueda formar parte del nombre. Las llaves solo son imprescindibles cuando no se pueda interpretar dónde acaba el nombre de la variable, pero ante la duda, si las pones no pasa nada, solo el trabajo de teclear dos caracteres de mas.

La expansión de parámetros tendrá efecto en cualquier parte de una orden, incluso en el comando (esto puede sorprender a quienes conocéis otros lenguajes de programación), o en la propia asignación a la derecha del igual:

$ Comando="ls -l"
$ $Comando

$ Comando=ls
$ Argumento=-l
$ $Comando $Argumento

$ MeLlamo="$Nombre $Apellido"
$ Echo Me llamo $MeLLamo
Me llamo Redy Rodriguez

No obstante no se podrá usar a la izquierda del igual en una asignación. Lo siguiente no le asignará un valor a la variable A5 y dará error:

$ NombredeVariable=A5
$ $NombredeVariable='Valor de la variable'

La asignación de variables se puede hacer en la misma orden en la que se ejecuta un comando: de esta forma:


$ DISPLAY=:0 xgamma 1,2

Pero en ese caso la variable tiene un carácter temporal, y solo tendrá ese valor durante la ejecución de ese comando, volviendo a tomar al finalizar al valor antiguo. Intentaré demostrar esto:


$ A=0 B=3
$ A=1 B=5 eval 'echo $A $B'
1 5
$ echo $A $B
0 3

¿Porque uso eval ‘echo $A $B’ en lugar de un simple echo $A $B? Muy sencillo: Analicemos como funciona bash. Primero divide la línea en palabras y hace las expansiones, y luego ejecuta el comando. Si pusiera:


$ A=1 B=5 echo $A $B

Lo primero que se haría sería sustituir $A y $B por sus valores que inicialmente eran 0 y 3. Luego ejecutaría el comando, que consiste por una parte en asignar a la variable A y B unos nuevos valores y a continuación ejecutar el comando echo con dos parámetros que previamente se habían expandido a 0 y a 3. No era eso lo que queríamos ¿verdad?. Al encerrar entre comillas simples $A y $B no se expanden en un principio, pero cuando se ejecuta el comando eval si. (El comando eval lee la cadena que le pasamos como argumento y se la pasa al bash para que la ejecute como si se la hubiésemos tecleado.) ¿Se ha entendido esto? Hay que tener muy claro como funciona bash si no queremos tener extrañas sorpresas que parecen ir contra la lógica.

Dialogando con los scripts: El comando read

Un comando muy interesante y que funciona con variables es read. Este comando permite leer lo que tecleamos y lo almacena en una o varias variables. Veamos como funciona en un script:

#! /bin/bash
#
# Este script ilustra el uso del comando read
#
echo -n "¿Como te llamas? "
read NOMBRE
echo "Hola $NOMBRE; encantado de conocerte."

Lo guardamos como saluda2 en nuestra carpeta ~/bin y le damos permiso de ejecución.

$ saluda2
¿como te llamas? Redy
Hola Redy; encantado de conocerte.

El parámetro -n de echo sirve para que no salte a la línea siguiente tras escribir lo que le decimos. El read lee lo que le tecleamos hasta que pulsamos <enter> y lo almacena en la variable NOMBRE. Podríamos haber puesto el equivalente a estas dos líneas en una sola usando el argumento -p en el read. La palabra que sige a -p se mostrará en pantalla antes de leer del teclado. Modifiquemos nuestro script:

#! /bin/bash
#
# Este script ilustra el uso del comando read
#
read -p "¿Como te llamas? " NOMBRE
echo "Hola $NOMBRE; encantado de conocerte."

A veces nos interesa que read lea varios valores. Si después del read no ponemos ningun nombre de variable todo lo que tecleamos hasta pulsar <enter> se almacenará en la variable $REPLY. Si ponemos una sola variable se almacenará en esa variable, pero si ponemos varias irá tomando las palabras que le tecleemos (en el sentido que bash entiende las palabras) y almacenará la primera palabra en la primera variable, la segunda en la segunda variable, y así hasta la última. Si tecleamos más palabras que variables, la última variable contendrá todas las palabras que queden hasta el <enter> Y si tecleamos menos palabras que variables las últimas variables quedarán vacías.

#! /bin/bash
#
# Este script ilustra el uso del comando read
#
read -p "¿Cual es su nombre y apellido? " NOMBRE APELLIDO
echo "Encantado de conocerle sr. $APELLIDO."
echo "D. $NOMBRE, se nota que es usted una persona distinguida."

También podemos especificarle al read un tiempo máximo de espera. Si el usuario no le teclea nada después de ese tiempo finaliza (con error). Esto de los errores lo veremos más adelante. Para especificarle el tiempo pondremos -t y un valor en segundos como argumentos. (el || exit del final es para que no continúe en caso de error, ya lo veremos más adelante también).

#! /bin/bash
#
# Este script ilustra el uso del comando read
#
read -t 20 -p "¿Cual es su nombre y apellido? " NOMBRE APELLIDO || exit
echo "Encantado de conocerle sr. $APELLIDO."
echo "D. $NOMBRE, se nota que es usted una persona distinguida."

Ahora el script hace lo mismo, salvo que tiene ‘menos paciencia’, si tardas más de 20 segundos en contestar a su pregunta se cansa y termina.

Parámetros posicionales:

Otra manera de pasarle información a nuestros scripts es usando argumentos. Hasta ahora solo ejecutábamos el script sin más pero, a los scripts, al igual que al resto de programas se le puede pasar argumentos. Supongamos que hacemos un script para rescalar una imagen a un tamaño de 1000 pixels en su lado más largo, y además si no estaba ya en jpg, convertirlo con unos valores de compresión adecuados para que no pese mucho ni pierda mucha calidad. Vamos, lo ideal para subirlo a nuestra galería de fotolibre, por ejemplo. Todo eso lo haremos con imagemagick. Yo obtengo muy buenos resultados para hacer esto, con bastante buena calidad y no demasiado peso del archivo con el comando:

$ convert -filter Lanczos -resize 1000x1000 -sharpen 1x0.75 -blur 1x0.5 -quality 85 -strip -sampling-factor 2x2 -compress JPEG IMAGEN-ORIGINAL IMAGEN_RESCALADA.jpg

Suelen salir, dependiendo de la foto ficheros de no más de 200K en los que no se nota la pérdida de enfoque que produce el rescalado y creo que normalmente no genera artefactos. Si alguien tiene un sistema con el que se consigan mejores resultados que lo diga. Claro que el comando es un poco largo para recordarlo de memoria y para escribirlo entero cada vez que queremos redimensionar una foto ¿no? Así que ¿que os parece si hacemos un script que le llamemos ‘pasubir’ por ejemplo y que haga todo esto por nosotros con la imagen que le digamos?
Como cada vez vamos a hacer el proceso con una imagen distinta habrá que encontrar una manera de decirle al script la imagen que queremos tratar. Pues para eso están los argumentos. Si ejecutamos

$ pasubir imagen.tif

Estamos pasando un argumento al script. Podemos pasar varios. Luego en el script estos argumentos podemos leerlos consultando lo que llamamos parámetros posicionales. $1 será el primer argumento que hemos pasado, $2 el segundo $3 el tercero y así hasta $9. Si queremos ir más allá del 9 hay que poner ${10}, ${11} etc… En nuestro caso concreto con el 1 nos apañamos.

#! /bin/bash
#
# Este script convierte una imágen (si no lo está ya) a jpg y 
# la rescala a un máximo de 1000 pixels por su lado más largo. 
# la imágen de destino se llamará igual que la original pero añadiendo 
# al nombre el sufijo '_escalado.jpg'
# 
convert -filter Lanczos -resize 1000x1000 -sharpen 1x0.75 \
             -blur 1x0.5 -quality 85 -strip -sampling-factor 2x2 \
             -compress JPEG $1 $1_escalado.jpg

Ya expliqué lo de escapar los retornos de cario ¿no?. Si, lo dije en la entrega 3. Para los despistados lo repito: “Si pones un escape justo antes de dar al intro, anulas el efecto del intro, y se continúa introduciendo el comando en la siguiente línea, pero al final se tratará como si fuese una sola línea, esto puede ser útil para teclear líneas largas.” Para bash no es ningún problema que las líneas sean largas, pero cuando editamos un script las líneas largas se hacen incómodas de leer, así que porque no partirlas en varias, poniendo un caracter de escape ‘\’ justo al final de la línea. Mucho cuidad de no dejar espacios después del ‘\’ porque si lo hacemos escapamos el espacio y no el intro ¡eh!

Hala como ejercicio propongo mejorar el script para que no solo rescale al tamaño de 1000 pixels sino que seamos nosotros quienes le digamos el tamaño que queremos. ¿que os parece?

Cancho Roano

por Charly Morlock @ 12:01 am — Archivado en: Fotografí­a Libre

Con mas de 2500 años de antigüedad, es el conjunto tartesico mejor conservado de la Península.
A pesar de haberlo construido los tartesos allá por el siglo VI a. C., se conservan restos importantes de un edificio monumental, con los distintos espacios de almacenes, estancias y zona sacra, que esta rodeado por una muralla y un foso.
Los tartesos lo abandonaron dos siglos después de haberlo construido con adobe, un material endeble.
Antes de abandonarlo lo incendiaron en un acto ritual con todas las pertenencias dentro y despues lo sellaron con tierra. El fuego cocio el adobe adquiriendo la consistencia del ladrillo y la tierra lo protegió, lo que permitió su preservación a lo largo de los siglos.
Se encuentra en la carretera BA-632 entre Zalamea de la Serena y Quintana de la Serena en la provincia de Badajoz (España para los que estáis por el extranjero)
Bueno pues hoy he hecho un alto en el camino y he podido visitarlo y la verdad es que esta muy bien aunque no he podido pararme mucho en el centro de interpretación, el trabajo manda , pero he podido tirar unas fotillos para que os hagáis una idea de todo esto, espero os guste el sitio y las fotos.

Esta es la entrada al centro de interpretación , donde te explican que es lo que se ve en las ruinas.

(más…)

Octubre 25, 2007

Castillo de Alburquerque

por Charly Morlock @ 11:51 pm — Archivado en: Fotografí­a Libre

Extraído de la Wikipedia:
________________________________________________________________________________________________
Alburquerque se encuentra enclavada en la comarca denominada de Los Baldíos, siendo su principal núcleo. Está situada al noroeste de la Provincia de Badajoz, Extremadura, muy próxima a Portugal, desde donde se ven tierras de este país. Dista 45 km de la capital provincial (Badajoz).
Su población actual está en unos 5.625 habitantes, a los que se llama alburquerqueños.
La Villa de Alburquerque conserva un rico legado histórico, fundamentalmente de la Edad Media, aunque estudios arqueológicos datan su origen en época árabe, e incluso anterior, en los años de la dominación romana de Hispania.
Pero los monumentos más característicos que se conservan son los levantados en la Edad Media: así, su casco histórico está coronado por el inexpugnable Castillo de Luna (el condestable D. Álvaro de Luna fue uno de sus propietarios); existe una muralla que rodea en su mayor parte el barrio gótico-judío, llamado Villa Adentro, y en él se conservan entre sus callejuelas, numerosas casas con dinteles ojivales, algunos de ellos con elementos que delatan el origen judío de sus moradores antes de la expulsión de 1492.
________________________________________________________________________________________________

Bueno, pues hoy otro alto en el camino, me he parado unos minutos y tirado unas las espectacular castillo, no me dio tiempo subir y hacerlas por dentro , pero prometo que algún día con mas tiempo lo hago.
Espero os gusten, pongo dos versiones para que no os peléis y podáis quedaros con la que queráis

Parque Natural de Cornalvo

por Charly Morlock @ 11:39 pm — Archivado en: Fotografí­a Libre

Bueno pues hoy me ido con mi hermano (yo con mi D50 y el con su Leica M6) a dar una vuelta al Parque Natural de Cornalvo, una de las entradas al parque esta a unos dos km de la puerta de mi casa, a pesar de que no era la mejor hora para la fotografía ni mucho menos pues el sol ya estaba muy alto , la verdad es que se estaba muy a gusto. Tiramos algunas fotos y algunas salieron medio bien para mi gusto, pero al final de la mañana nos llevamos la gran sorpresa, si seguís leyendo la veréis ;) .

Estuvimos en la zona que se conoce como Rugidero, donde cuando hay agua la verdad es que es espectacular, pero por aquí todavía no llovió lo suficiente como para que el agua corriese como es debido, esta foto es de allí.

Esta es un pano pero como he dicho la hora no era la mejor.


Salimos de la zona y nos vamos a uno de los observatorios de aves que hay en el parque, concretamente este es el de la zona del “Huevo”
(más…)

Octubre 22, 2007

Curso de bash para fotógrafos linuxeros. (5) Hola mundo

por Redy @ 1:53 pm — Archivado en: Fotografí­a Libre

Me estuve planteando que para hacer más ameno el curso deberíamos empezar ya a hacer scripts. (Ya sé que la palabra script no existe en castellano pero decir guiones o cualquiera de sus sinónimos no me parece adecuado, así que, y con la venia, seguiré diciendo script aun consciente de que no es correcto).
Primero pensé que como había que trabajar con los permisos de ejecución se hacía imprescindible explicar antes lo de los permisos en linux, y de hecho escribí un artículo sobre los permisos pero al terminar lo vi tan ‘infumable’ que no me atrevo a publicarlo. Ahora he retomado el asunto de explicar los scripts, y cuando haya que hablar de permisos daré unas breves nociones pero sin meterme a fondo. Quizá más adelante volvamos sobre el tema, para ver el tema de los permisos a fondo, pero de momento no lo considero imprescindible.

Hola mundo

Para los que nunca habéis hecho un curso de programación, decir que eso de ‘Hola Mundo’ es el primer ejemplo de programa que se suele poner para aprender a programa en cualquier lenguaje. Un programa tan simple que lo único que hace es imprimir una frase en la pantalla, la frase más manida es por supuesto ‘Hello World’ o su traducción ‘Hola Mundo’.

En bash no es necesario hacer un programa para sacar una frase por pantalla, si con un simple comandito de una línea ya está.

$ echo "Hola Mundo"
Hola Mundo

Pero claro con eso no aprenderíamos lo que es un script. Un script de bash no es más que una secuencia de comandos contenidos en un fichero de texto, cada uno en una línea, que la shell interpreta y ejecuta uno tras otro (salvo que haya instrucciones que indiquen una secuencia distinta claro).

Nuestro primer script solo tendrá una linea. Todavía tenemos la carpeta ‘ejercicios_curso_bash’ que creamos en el capítulo anterior, vamos a utilizarala, primero nos situamos y borramos todos los archivos que hay en ella. Asegurate con el comando ‘pwd’ de cual es tu directorio de trabajo antes de hacer el ‘rm -f *’. Esa órden borra todos los ficheros de la carpeta de trabajo, y no quiero sorpresas.
$ cd ~/ejercicios_curso_bash
$ pwd
/home/redy/ejercicios_curso_bash
$ rm -f *

Abre tu editor de textos favorito, crea un fichero nuevo con el siguiente texto:

echo "Hola Mundo"

Guardalo en la susodicha carpeta con el nombre ’saluda’

En lo sucesivo todos los scripts que ponga irán en recuadros como el de arriba. El texto aparecerá en colorines, al estilo de como aparece en muchos editores que resaltan la sintaxis del lenguage. Que sepas que cuando veas un recuadro así, son las líneas que debes teclear (bueno también ’se vale’ copiar y pegar :D venga…) en tu editor de textos, para que queden grabadas en el script (no te preocupes si los colores son distintos o si en tu editor no sale el texto coloreado, eso depende del editor.)

Ahora vamos a decirle a bash que ejecute ese script:

$ bash saluda
Hola Mundo

Si tu script saludo no dice ‘Hola Mundo’ tenemos un problema. ¿Has escrito exactamente lo que yo decía? ¿Lo has guradado con el nombre ’saluda’ en la carpeta ‘~/ejercicios_curso_bash’? ¿Era esa carpeta tu directorio de trabajo cuando le mandaste ejecutarlo?.

Comentarios

Un comentario en un programa es un texto que no forma parte de las instrucciones, no será interpretado por el ordenador a la hora de ejecutar o de compilar el programa, simplemente se pone para que cuando un programador lea el programa, le sirva de aclaración de algun aspecto o detalle importante.
Dicen los grandes programadores que el buen código no los necesita, que si un programa está bien escrito se entiende tan bien que cualquier comentario sobra. Y ahora me viene a la mente una frase de Martin Fowler, un experto en desarrollo y metodología del software, que dice que: “Any fool can write code that a computer can understand. Good programmers write code that humans can understand” (Cualquier necio puede escribir código que un ordenador puede entender. Los buenos programadores escriben código que los seres humanos podemos entender). Como ni yo, ni posiblemente tu tampoco , somos de esa pequeña minoría que se pueden llamar buenos programadores, será mejor que pongamos en nuestro código algún comentario y quizá así nos sea más fácil entender luego el espagueti que hemos liado (Bueno vale, el hola mundo se entiende bien sin comentarios, pero tiempo al tiempo… Mejor irnos acostumbrando a ponerlos.).
En bash los comentarios se ponen mediante el signo ‘#’. Cuando bash encuentre en un comando una palabra que comience por el signo ‘#’ ignorará todo lo que haya desde ese signo hasta el final del comando (final de línea).

$ echo Hola Mundo # Esta órden saludara a todo el mundo
Hola Mundo

Aunque lo más usual es que en un script el # se ponga al principio de la línea y entonces toda la línea es un comentario, ya ves que no tiene porque ser así, y que justo a continuación de un comando y en la misma línea podemos poner un # para clarificar algo. Evidentemente el # no tendrá efecto entre comillas, ni en medio de una palabra:

$ echo "Hola Mundo # Esto también sale"
Hola Mundo # Esto también sale
$ echo Hola Mundo Es#to también sale
echo Hola Mundo Es#to también sale

Ahora que ya sabemos comentar vamos a poner bonito nuestro script: ábrelo con tu editor favorito y modifícalo para que quede tal que así:

# saluda versión 1.0
#        Programa para aprender como confeccionar un primer script en bash
#        Autor: _pon tu nombre aquí_
#        Se permite la reproducción bajo los términos de la licencia GPL y patatín patatán
#
echo "Hola Mundo" 
# Fin del programa

Guárdalo, y tendrás una nueva versión del script que hace exactamente lo mismo pero queda como más ‘pofesional‘ ¿no?
$ bash saluda
Hola Mundo

Hacer que sea un programa ejecutable

Si ahora miramos, con el comando file, qué es nuestro script ’saluda’, veremos que el sistema lo reconoce como un simple archivo de texto ASCII.

$ file saluda
saluda: Non-ISO extended-ASCII text

Solo nosotros sabemos que es un script de bash. Y por tanto la única manera de ejecutarlo es diciéndole al bash que lo abra como un script, tal como hicimos arriba.
Vamos a mejorar un poco todo esto: podemos convertir nuestro script en un programa más del sistema que el kernel sepa ejecutar, y así solo tendremos que invocarlo por su nombre como un comando más. Para hacer esto solo es necesario un par de retoques:
En la primera línea del programa, añadiremos un comentario especial que comienza siempre por ‘#!’ y un espacio en blanco, luego ponemos el nombre de ruta del intérprete que va a ejecutar ese script, en nuestro caso ‘/bin/bash’.
Cuando al kernel le ordenamos ejecutar un fichero de texto cuya primera línea está formada siguiendo esa regla, en realidad ejecutará el intérprete pasándole como argumento el nombre del script (exactamente igual que hacíamos nosotros con el comando ‘bash saluda’). En este caso, como el script se llama ’saluda’ y la primera línea es ‘#! /bin/bash’, ejecutará lo equivalente a teclear ‘/bin/bash saluda’. Editemos pues nuestro script para añadirle esa línea:

#! /bin/bash
# saluda versión 1.0
#        Programa para aprender como confeccionar un primer script en bash
#        Autor: _pon tu nombre aquí_
#        Se permite la reproducción bajo los términos de la licencia GPL y patatín patatán
#
echo "Hola Mundo" 
# Fin del programa

Ahora ya es un script de bash para el sistema:

$ file saluda
saluda: Bourne-Again shell script text executable

Solo falta otro detalle hay que darle permiso de ejecución. El sistema de permisos en Linux se basa en un esquema de usuarios/grupos que lo convierte en la base principal de la seguridad en Linux, a estos usuarios y grupos se les asignan distintos derechos sobre los archivos y directorios. Cada archivo pertenece a un usuario y a un grupo. Un archivo puede tener permiso de lectura, de escritura y de ejecución, si no tiene el permiso de lectura no podremos leerlo, si no tiene el de escritura no podremos modificarlo y si no tiene el de ejecución no podrá ser ejecutado, que es lo que nos interesa. Veamos como funciona, si haces:
$ ls -l
Te saldrá algo como esto:
-rw-r--r-- 1 redy redy 264 oct 22 10:07 saluda
El tercer y cuarto campos (con los valores ‘redy’ y ‘redy’ en mi caso) son el usuario propietario del fichero y el grupo al que pertenece el fichero. Un fichero pertenece normalmente al usuario que lo crea y al grupo de ese usuario. Solo el root puede cambiar el propietario de un fichero. Pero el propietario puede cambiarle los permisos. Las letras y guiones que aparecen en el primer campo son los permisos. Bueno, la primera letra es el tipo de elemento dentro del sistema de archivos, una ‘d’ indicará que es un directorio y un ‘-’ que es un fichero (hay otras como ‘b’, o ‘c’ para dispositivos, ‘l’ para enlaces simbólicos…). Los 9 caracteres a continuación y en grupos de tres en tres son los permisos; por este orden: permisos para el propietario, permisos para el grupo y permisos para el resto. Cada grupo de permisos indica lo que puede hacer el individuo en cuestión con ese fichero. Y las tres letras que pueden aparecer en cada grupo de permisos por este órden son r (Read: lectura), w (Write: escritura) y x (eXecute: ejecución). Si aparece la letra es que el permiso está concedido y si aparece un ‘-’ en su lugar es que no. ¿Vaya lío? Veámoslo en el ejemplo de arriba: Tenemos -rw-r–r– si quitamos el primer ‘-’ que indica que es un fichero y lo dividimos de tres en tres nos queda rw- r– r– lo cual indicará que los permisos para el usuario redy (que es el propietario del fichero) son rw- (lectura y escritura). Los permisos para cualquier otro usuario que pertenezca al grupo redy serían los tres siguientes r– (lectura). Y para el resto de usuarios del sistema también serían r– (lectura). Es decir que a pesar de que es reconocido por el sistema como un script de bash ejecutable, nadie puede ejecutar el fichero porque nadie tiene permiso para ello. Para modificar los permisos se usa el comando chmod. Normalmente a chmod se le pasan dos argumentos el primero indica los permisos que queremos modificar, y el segundo el nombre del fichero. Para indicar los permisos que queremos modificar pondremos el signo ‘+’ para darlo o ‘-’ para quitarlo seguido de la letra del permiso (’r', ‘w’, o ‘x’). Opcionalmente podremos poner antes del signo las letras ‘u’ (usuario), ‘g’ (grupo) u ‘o’ (otros) para indicar a quien queremos conceder o denegar ese permiso, si no ponemos esa letra o ponemos la ‘a’ (de all) se asignará o quitará el permiso para todos.
Entonces, para darle permiso de ejecución a nuestro fichero, tenemos que hacer:

$ chmod +x saluda
ls -l
-rwxr-xr-x 1 redy redy 264 oct 22 10:07 saluda

Ahora que tiene permiso de ejecución y en su primera línea se le indica al kernel como debe manejarlo, ya es un programa, y para ejecutarlo solo hace falta escribir como un comando su nombre de ruta

$ ~/ejercicios_curso_bash/saluda
Hola Mundo

Como ~/ejercicios_curso_bash es nuestro directorio de trabajo, y si volvemos a lo explicado hace un par de entregas eso puede representarse con una ruta relativa con un simple punto (’..’ era el directorio padre y ‘.’ el propio directorio, ¿recuerdas?), podemos teclear simplemente:

$ ./saluda
Hola Mundo

Y ya para darle el último toque. Hay una serie de directorios dónde suelen ponerse los programas. Al sistema se le indica dónde debe buscar los programas en caso de que nosotros no indiquemos la ruta completa, mediante una variable (pronto veremos esto de las variables, paciencia). Solo comentar que si pones el comando:
$ echo $PATH
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/usr/games:/home/redy/bin

Obtendrás la lista (separados por ‘:’) de todos los directorios dónde se buscan programas para ejecutarlos. Si ponemos nuestro programa en uno de esos directorios ya no será necesario especificar su nombre de ruta completo para ejecutarlo, sólo el nombre, como si fuera uno de tantos programas o utilidades de nuestro sistema. Claro que en esa lista no tendremos permiso de escritutra en la mayoría de los directorios… Veamos (No intentes entender el siguiente comando de momento, que sepas simplemente que nos sirve para ver en que directorios de los dichos podemos escribir):
$ ls -ld $(echo $PATH |tr ":" " ")
drwxr-xr-x 2 root root 4096 nov 20 2006 /bin/
drwxr-xr-x 2 redy redy 4096 oct 18 08:17 /home/redy/bin/
drwxr-xr-x 3 root root 61440 sep 21 13:53 /usr/bin/
drwxr-xr-x 2 root root 4096 abr 25 12:48 /usr/games/
drwxr-xr-x 2 root root 4096 sep 13 08:19 /usr/local/bin/
drwxr-xr-x 3 root root 4096 sep 27 07:54 /usr/X11R6/bin/

En la lista de los que a mi me salen, resulta que todos pertenecen al root y solo tienen permiso de escritura para el propietario, excepto el /home/redy/bin que pertenece a redy y también tiene permiso de escritura solo para el propietario. Vaya ese soy yo, luego ahí puedo escribir, lo cual, dicho de un directorio significa que puedo meter cosas dentro de él. Apuesto a que tu tienes uno igual pero, obviamente, sustituyendo ‘redy’ por tu nombre de usuario. Puedes trasladar el script ’saluda’ a ese directorio con el comando mv.

$ mv saluda /home/redy/bin
$ saluda
Hola Mundo

¡Funciona!
Recuerda que si quieres editarlo de nuevo, ahora ya no está en ~/ejercicios_curso_bash sino en ~/bin

Octubre 2, 2007

Curso de bash para fotógrafos linuxeros. (4) Las llaves y los comodines

por Redy @ 11:04 am — Archivado en: Fotografí­a Libre

La expansión de llaves

Lo primero que hace bash al analizar una orden es separarla en palabras, luego comprueba si alguna de las palabras debe ser expandida. Hay ciertas expresiones que son expandidas cuando bash las analiza para producir un resultado distinto a lo que hemos escrito. Lo primero que analiza bash es si alguna palabra contiene expresiones entre llaves ‘{}’. Te gustará este tipo de expansión porque sirve para escribir menos. Básicamente la expansión de llaves convierte una palabra en tantas como expresiones pongamos entre las llaves separadas por comas.


echo {1,2,3,4,5,6}
1 2 3 4 5 6

Visto así no parece muy práctico ¿no? :) bueno déjame continuar… Antes y después de las llaves puedes poner un prefijo y/o un sufijo, pero siempre formando una única palabra, en ese caso se combinará prefijo y sufijo, con cada uno de los elementos.

echo cant{o,as,a,amos,áis,an}
canto cantas canta cantamos cantáis cantan
echo {lenta,rápida,inmediata}mente
lentamente rápidamente inmediatamente
echo co{mun,tidiana}mente
comunmente cotidianamente

Cuando tengas soltura en bash, te verás escribiendo cosas como: ‘convert fichero.{jpg,png}’ en lugar de ‘convert fichero.jpg fichero.png’

Algunos elementos de la expansión pueden estar vacíos:

echo esta{,ba,tatica}
esta estaba estatatica

Si en una palabra aparecen varias llaves se expanden sucesivamente. Por ejemplo echo prueba{1,2}{a,b} primero se expandiría la primera llave quedando dos palabras 'prueba1{a,b} prueba2{a,b}' Como cada una de las palabras generadas sigue teniendo una llave se volverían a expandir, dándo como resultado cuatro palabras: 'prueba1a prueba1b prueba2a y prueba2b'

También se pueden anidar unas llaves dentro de otras:

$ echo hola{1{a,b,c},2,3{d,e}}
hola1a hola1b hola1c hola2 hola3d hola3e

A partir de la versión 3 de bash (La que tendréis la mayoría), también se pueden poner intervalos numéricos en la expansión de llaves. {3..9} es equivalente a {3,4,5,6,7,8,9}. {0..999} generará mil palabras 0 1 2 … 998 999. En cambio si queremos generar 000 001 002 … 998 999 tendremos que poner {0..9}{0..9}{0..9}

La expansión solo funciona a nivel de palabras. Y nunca dentro de comillas (ni simples ni dobles). Si queremos incluir espacios en la expansión hay que evitar que dividan la palabra con el caracter de escape o con comillas pero sin que estas afecten a las llaves.

$ echo hola {Juan,Carlos,Jose Manuel}
hola {Juan,Carlos,Jose Manuel}
$ echo "hola {Juan,Carlos,Jose Manuel}"
hola {Juan,Carlos,Jose Manuel}
$ echo hola {Juan,Carlos,"Jose Manuel"}
hola Juan Carlos Jose Manuel
$ echo hola\ {Juan,Carlos,Jose\ Manuel}
hola Juan hola Carlos hola Jose Manuel
$ echo "hola "{Juan,Carlos,"Jose Manuel"}
hola Juan hola Carlos hola Jose Manuel

En el primer caso no se expande nada porque la llave de abrir y la de cerrar están en palabras distintas. En el segundo caso están en la misma palabra pero tampoco se expande porque dentro de unas comillas nunca se raliza la expansión de llaves. En el tercer caso se hace la expansión pero solo de las llaves ya que hay un espacio que separa la palabra ‘hola’.
En los dos últimos utilizamos el escape y las comillas para hacer la expansión correctamente.

La expansión de nombres de ruta. Patrones, comodines…

Después de la expansión de llaves y siempre antes de ejecutar una orden el bash comprueba si alguna palabra contiene algún carácter de estos: ‘*’, ‘?’ y ‘[' y si es así la considera como un patrón. Un patrón es una palabra que se expande a todos los nombres de ruta que concuerden con él antes de ejecutar la orden. A los caracteres que tienen un significado especial dentro de un patrón les llamamos caracteres comodín

Al igual que pasaba con la expansión de llaves, dentro de un par de comillas, ya sean simples o dobles jamás se hace esta sustitución, así que si queremos usar alguno los caracteres comodín literalmente, sin que se produzca la expansión podemos bien encerrarlo entre comillas o bien usar el caracter de escape.

Cuando una palabra contiene un comodín bash busca en nuestro sistema de ficheros todos los nombres de ruta que concuerdan con esa palabra patrón. Si encuentra concordancias la palabra es sustituida por todos los nombres que concuerden. Esto quiere decir que una palabra puede convertirse en varias antes de ejecutar la orden. Si ningún nombre concuerda se deja la palabra tal cual estaba y el caracter o caracteres comodín pasan a considerarse caracteres completamente normales.

Pero te estarás preguntando ¿que es eso de concordar? ¿Como funcionan exactamente los comodines?. El caracter '?' en un patrón significa cualquier caracter. Así que si ponemos en una órden la palabra '/usr/bin/?c', por ejemplo, se buscarían todos los nombres de ruta que comiencen con '/usr/bin/' luego tengan un caracter cualquiera y finalmente una 'c', que seguramente habrá varios, y se sustituirá la palabra que contenía el comodín por todos los nombres de ruta que se han encontrado. Personalmente me sale:


echo /usr/bin/?c
/usr/bin/bc /usr/bin/cc /usr/bin/dc /usr/bin/gc /usr/bin/mc /usr/bin/nc /usr/bin/wc

Se puede usar varias veces un comodín (o varios) en la misma palabra, por ejemplo:


echo /usr/bin/?a?
/usr/bin/cal /usr/bin/dat /usr/bin/fax /usr/bin/mag /usr/bin/man /usr/bin/pan /usr/bin/rar /usr/bin/raw /usr/bin/tac

Evidentemente en tu ordenador puedes obtener resultados distintos, porque no todos tendréis una instalación exactamente igual que la mía.

El comodín '*' en un patrón significa ninguno, o más caracteres cualquiera. Es decir concordará con cualquier secuencia de caracteres de cualquier longitud, incluso con ningún caracter. Ejemplos:

$ echo /usr/bin/col*
/usr/bin/col /usr/bin/colcrt /usr/bin/colormap /usr/bin/colrm /usr/bin/column
$ ls *.jpg
$ du -c f-*.jpg

Los que estáis familiarizados con el interprete de comandos de MS/DOS o Windows, debeis tener mucho ojo con la expansión de nombres, porque puede resultar muy parecido a como el DOS trata sus patrones (formados por los caracteres llamados comodines * y ?), pero es sustancialmente distinto, para comparar os pondré un ejemplo, si en MS/DOS o Windows ponemos en la línea de comando:

EDIT *.TXT

El command.com (o el cmd.exe) no hace nada, simplemente llama al programa edit y le pasa como argumento '*.TXT' es EDIT quien tras analizar el argumento entiende que es un patrón que puede designar múltiples ficheros y lo expande. Hay comandos que nunca expanden los nombres de fichero y otros que sí...

En bash, (y en las shells de UNIX en general), es la shell la que hace la expansión, y si escribo por ejemplo:

$ gimp *.jpg

La shell encuentra el *, sabe que designa un patrón y busca todos los ficheros que concuerden con ese patrón, supongamos que hay tres y que son: foto1.jpg, foto2.jpg y foto3.jpg. La orden se convierte, pues, antes de ser ejecutada en:

$ gimp foto1.jpg foto2.jpg foto3.jpg

Y el gimp recibirá tres argumentos y no uno como el EDIT en el ejemplo anterior. Esto que parece trivial hay que tenerlo muy claro, y suele desembocar (entre otros) en un error que muchos novatos en Linux que veníamos de DOS habremos cometido alguna vez, el error de usar el cp o el mv sin destino. Como anécdota voy a explicarlo ya que además de servirnos para ilustrar como funciona la expansión de nombres, te puede evitar algún disgusto:

Para los que hicimos nuestros 'pinitos' en DOS antes de pasarnos a Linux, conocemos perfectamente como funcionan los comandos COPY y MOVE del DOS. Copy (en DOS) copia uno o varios ficheros a un destino. Necesita normalmente dos parámetros, origen y destino, el origen puede ser un directorio, un fichero o un patrón que concuerde con varios ficheros. El destino puede ser otro fichero, o un directorio; pero puede omitirse. Si se omite se tomará como destino el directorio actual.

Supongamos que estamos en MS/DOS (o Windows) y el directorio actual es '\Mis Documentos' en la unidad C y en la unidad A tenemos varios fotos (.jpg) que queremos copiar a nuestro directorio actual, es suficiente con que tecleemos:
Copy a:\*.jpg

Veamos el mismo caso en Linux y concretamente con bash:

El comando para copiar es cp, cp siempre espera al menos dos argumentos: un origen y un destino. Si recibe más de dos argumentos, entiende que todos son orígenes excepto el último que es el destino, si hay más de un fichero origen, el destino debe ser un directorio. El comando cp no entiende de comodines ni de patrones es la shell quien tiene que darle todo expandido.

Ignorantes de estas premisas y acostumbrados a MS/DOS, si en Linux queremos hacer lo mismo del ejemplo anterior, es decir, nos encontramos en nuestro directorio home y queremos copiar a él todos los ficheros .jpg que hay en un diskette, es necesario poner origen y destino. Por desgracia, creyendo que bash es como el command.com, traducimos el comando copy a:\*.jpg
por lo que nos parece lo más equivalente que es:

$ cp /mnt/floppy/*.jpg

¿Que puede ocurrir? Si tenemos la suerte de que en el disquete hay más de dos ficheros .jpg, el bash expande los nombres de fichero en orden como:
$ cp /mnt/floppy/foto1.jpg /mnt/floppy/foto2.jpg /mnt/floppy/foto3.jpg

El comando cp (al menos en las versiones modernas del cp que suele venir con Linux) ve que hay más de dos parámetros, y como el último no es un directorio, nos da un error diciendo que no se puede copiar varios ficheros sobre uno solo, y que el destino tiene que ser un directorio. (En otros UNIX o en versiones muy antiguas de cp, puede que ni siquiera nos de el error y copie los dos primeros ficheros sobre el último).

Pero si tenemos la desgracia de que el disquete solo tiene dos ficheros .jpg (fatal coincidencia), el comando cp después de que bash expanda los nombres de fichero recibirá dos parámetros como si hubiésemos tecelado:
$ cp /mnt/floppy/foto1.jpg /mnt/floppy/foto2.jpg

Y como eso es perfectamente válido copia la foto1 sobre la foto2, machacando el contenido de la segunda. Así que mucho ojo, que esto no es MS/DOS. El directorio de trabajo actual se designa (tal como vimos cuando hablábamos de los nombres de ruta) por un punto, así que lo que debieramos haber hecho es:

$ cp /mnt/floppy/*.jpg .

Así nos evitaríamos algún que otro disgusto. Otra diferencia sustancial es que por para DOS/Windows el patrón '*.*' designaba a todos los ficheros del directorio de trabajo. En bash '*.*' solamente designa a aquellos fichero que contienen un punto en su nombre para designar a todos debemos emplear simplemente '*'

Hay otro comodín que no tiene equivalente en DOS que es una serie de caracteres entre corchetes '[...]‘ (paréntesis cuadrados), y que concuerda con uno cualquiera de dichos caracteres, por ejemplo [afj]* concordará con cualquier fichero que comience por las letras a, f ó j. En vez de los caracteres uno a uno también se puede especificar un rango indicando el carácter inicial y el final separados por un guión como [a-z] o [0-9] que concordarán con cualquier letra minúscula o cualquier número respectivamente. Si queremos que el propio guión ‘-’ sea uno de los caracteres de la serie debemos ponerlo al principio o al final, ya que si va entre dos caracteres delimitará un rango. Si queremos que el propio ‘]’ que se usa para marcar el final del conjunto forme parte de la expresión hay que ponerlo al principio de todo.

Ejemplos de patrones con este comodin:

  • [a-fA-F]* cualquier fichero que comience por una letra desde la a hasta la F bien sea en mayúsculas o minúsculas.
  • *[0-9]* cualquier fichero que contenga un número.
  • *[0-9].txt cualquier fichero cuyo nombre termine en un número seguido de un punto y las letras ‘txt’
  • ??[1-9][0-9]* Cualquier fichero que comience por dos caracteres cualesquiera, seguidos de dos números (y que el primero no sea un cero), y cualquier serie de caracteres después.
  • *[0-36-9]* Cualquier fichero que tenga un número del 0 al 3 o del 6 al 9 en su nombre.
  • *.[jJ][pP][gG] Cualquier fichero que termine en .jpg ya sea en mayúsculas, minúsculas o cualquier combinación de mayúsculas y/o minúsculas.

Si el rango, o la serie de caracteres empieza por ^ o por ! se entenderá que es cualquier
caracter fuera de ese rango o serie: Si queremos poner cualquiera de esos dos caracteres en una serie, los pondremos en una posición que no sea la primera

  • [^0-9]* Cualquier fichero excepto los que empiezan por un número.
  • [^a-zA-Z]* Cualquier fichero que no comience por una letra.

Hay que hacer notar que cuando empleamos rangos, se tiene en cuenta la ordenación alfabética de nuestra configuración regional. Esto es realmente importante porque en casi todos los lenguajes la ordenación alfabética no tiene en cuenta mayúsculas y minúsculas. Quiere esto decir que si ponemos, [a-z] por ejemplo y dependiendo de la configuración regional que tengamos puede coincidir con las mayúsculas y las minúsculas, o incluír la ‘ñ’ o las vocales acentuadas. Ya que en la configuración regional van alfabéticamente en ese rango. Para que la comparación de rangos sea estricta, y no tenga en cuenta la ordenación regional sino el órden de caracteres del código ASCCI se debe establecer la variable de entorno LC_COLLATE=C. Como las variables, el entorno y esas zarandajas las veremos más adelante, sólo os indico como hacerlo, no os preocupéis si no lo entendéis de momento. Para consultar de acuerdo a que reglas se está ordenando, pondremos poner:

$ echo $LC_COLLATE
es_ES

En mi caso me indica que estoy usando el método de ordenación alfabética de es_ES que es español de España. Podría ser es_AR si estás en argentina, o es_MX para Mexico, o en_US para English – USA, o fr_FR para Francia o fr_BE para francés de Bélgica, fr_CA para francés de Candá, o en_CA para ingles de Canadá. Nuestros idiomas autonómicos se codifican normalmente como ‘gl’ para gallego ‘ca’ para catalan o ‘eu’ para euskera, sin variantes de país aunque es posible que en alguna distribución aparezcan como gl_ES, ca_ES, o eu_ES, lo cual no me parece muy correcto. Estos son unos códigos definidos por el estándar POSIX, que dice que: ‘Los códigos de idioma y país se deben definir juntos. El idioma sigue la norma ISO 639-1. Los códigos de países están definidos en la norma ISO 3166‘. El código de lenguaje ‘C’ así sin más indica que se seguirá el orden numérico del código ASCII (Bueno en realidad del código de caracteres que estemos usando, normalmente en una distribución linux moderna será el UTF-8). Para cambiar el código de lenguaje utilizado para la ordenación se usará el siguiente comando, poniendo después del igual el código que nos interese que se utilice para las reglas de ordenación. Este cambio tendrá efecto durante toda esa sesión de bash, o hasta que lo cambiemos de nuevo:

$ export LC_COLLATE=C

Vamos a experimentar un poco todo esto. Primero crearemos un directorio para hacer pruebas, luego lo poblaremos con unos cuantos ficheros (vacíos, que para el caso es lo mismo), y sobre ellos usaremos distintos patrones para que entiendas como funciona el asunto.


$ cd ~
$ mkdir ejercicios_curso_bash
$ cd ejercicios_curso_bash
$ touch fichero{1,8,9,0,_,a,c,C,F,Ñ,ñ}.txt
$ touch {123,345,685}{b,j,A,BcD,JJ}.{dat,dit,zip,jpg,Jpg,JPEG}
$ touch {A,B,C,a,b,f,á,é,í,ñ,Ñ}{ABC,abc,def}
$ touch {A,á,Z,z,Ñ.Ñ}' '{ABC,abc,def}
 
$ echo 1???.dat
123A.dat 123b.dat 123j.dat
$ echo 1*.dat
123A.dat 123BcD.dat 123b.dat 123j.dat 123JJ.dat
$ echo 1*.d?t
123A.dat 123A.dit 123BcD.dat 123BcD.dit 123b.dat 123b.dit 123j.dat 123j.dit 123JJ.dat 123JJ.dit
$ echo 1*
123A.dat 123A.dit 123A.JPEG 123A.jpg 123A.Jpg 123A.zip 123BcD.dat 123BcD.dit 123BcD.JPEG 123BcD.jpg 123BcD.Jpg 123BcD.zip 123b.dat 123b.dit 123b.JPEG 123b.jpg 123b.Jpg 123b.zip 123j.dat 123j.dit 123JJ.dat 123JJ.dit 123JJ.JPEG 123JJ.jpg 123J
J.Jpg 123j.JPEG 123j.jpg 123j.Jpg 123JJ.zip 123j.zip

$ echo f*
fabc fABC fdef fichero0.txt fichero1.txt fichero8.txt fichero9.txt ficheroa.txt ficheroc.txt ficheroC.txt ficheroF.txt fichero±.txt ficheroÐ.txt fichero_.txt
$ echo f*.*
fichero0.txt fichero1.txt fichero8.txt fichero9.txt ficheroa.txt ficheroc.txt ficheroC.txt ficheroF.txt fichero±.txt ficheroÐ.txt fichero_.txt
$ echo *.jpg
123A.jpg 123BcD.jpg 123b.jpg 123JJ.jpg 123j.jpg 345A.jpg 345BcD.jpg 345b.jpg 345JJ.jpg 345j.jpg 685A.jpg 685BcD.jpg 685b.jpg 685JJ.jpg 685j.jpg
$ echo 3*.[jJ]pg
345A.jpg 345A.Jpg 345BcD.jpg 345BcD.Jpg 345b.jpg 345b.Jpg 345JJ.jpg 345JJ.Jpg 345j.jpg 345j.Jpg
$ echo 3*.[jJ][pP]*[gG]
345A.JPEG 345A.jpg 345A.Jpg 345BcD.JPEG 345BcD.jpg 345BcD.Jpg 345b.JPEG 345b.jpg 345b.Jpg 345JJ.JPEG 345JJ.jpg 345JJ.Jpg 345j.JPEG 345j.jpg 345j.Jpg
$ export LC_COLLATE=C
$ echo fichero[a-z].txt
ficheroa.txt ficheroc.txt
$ echo fichero[A-Z].txt
ficheroC.txt ficheroF.txt
$ export LC_COLLATE=es_ES
$ echo fichero[a-z].txt
ficheroa.txt ficheroc.txt ficheroC.txt ficheroF.txt ficheroñ.txt ficheroÑ.txt
$ echo fichero[a-f].txt
ficheroa.txt ficheroc.txt ficheroC.txt
$ echo fichero[A-Z].txt
ficheroc.txt ficheroC.txt ficheroF.txt ficheroñ.txt ficheroÑ.txt

Con el método de ordenación, establecido como ‘C’ en el rango [a-z] sólo coincidirán las letras minúsculas del código ascii (descartando eñes, vocales acentuadas, etc), en cambio con el método de ordenación es_ES, parece que obtendremos coincidencias en todas las letras mayúsculas y minúsculas del alfabeto español. Si ponemos el rango en mayúsculas [A-Z] con el LC_COLLATE=C coincidirá con todas las letras mayúsculas del código ASCII, como era de esperar. En cambio con es_ES no tamos que no sale la ‘a’ minúscula. Esto tiene una sencilla explicación: Como hay que darle un órden a todos los caracteres, se decidió que primero irían las letras minúsculas, luego las mayusculas, luego (en el caso de las vocales) las acentuadas, primero en minúscula y luego en mayúscula, Más o menos así: aAábBcC…nNñÑ…uUúü…zZ Visto así está claro que en el rango [a-z] nos dejamos fuera la ‘Z’ mayúscula y en el rango A-Z nos dejamos fuera la ‘a’ minúscula. O en el rango [a-f], como podemos apreciar en el ejemplo, la ‘F’ mayúscula queda fuera. Parece algo trivial pero hay que tenerlo en cuenta si no queremos sorpresas trabajando con rangos

Además de listas de caracteres y rangos, bash reconoce los siguientes nombres especiales de clase, que irán siempre entre otros corchetes (además de los corchetes de rango) y símbolos de dos puntos.

  • [:alpha:] Coincide con cualquier letra.
  • [:digit:] Coincide con cualquier dígito numérico decimal.
  • [:xdigit:] Coincide con cualquier digito hexadecimal, es decir los numeros 0-9 y las letras A-F que pueden ser mayúsculas o minúsculas.
  • [:alnum:] Coincide con cualquier letra o número decimal.
  • [:punct:] Cualquier signo de puntuación.
  • [:lower:] Cualquier letra minúscula.
  • [:upper:] Cualquier letra mayúscula.
  • [:ascii:] Cualquier caracter del conjunto ascii de 7 Bits.
  • [:blank:] Cualquier caracter de espaciado, es decir los espacios y los tabuladores.
  • [:cntrl:] Cualquier caracter de control.
  • [:print:] Cualquier caracter imprimible incluyendo los espacios.
  • [:graph:] Cualquier caracter imprimible sin incluir los espacios.

Como los rangos, estas clases dependen del LC_COLLATE que tengamos seleccionado. Por ejemplo con es_ES, la letra Ñ o las vocales acentuadas o la u con diéresis pertenecerán a la clase :alpha:, en cambio con fr_FR la Ñ no estará en la clase :alpha: pero si estará la C con cedilla ‘Ç’ o las vocales con acento grave o circunflejo.

Por ejemplo:


$ echo fichero[[:digit:]aC]*

Mostrará todos los ficheros que comiencen con la palabra fichero, y contengan bien un
número o las letras ‘a’ minúscula o ‘C’ mayúscula a continuación.

$ ls -1 *[[:blank:]]*

Mostrará todos los ficheros o directorios que tengan algun espacio en su nombre. Este lo pongo con el comando ls -1 para que salga cada uno en una línea porque si los pongo con un simple echo saldrían todos segudos y no se distinguiría cuales son los espacios dentro del nombre de los que separan unos nombres de otros.

Patrones extendidos

Además de los patrones normales existen los denominados patrones extendidos, pero normalmente no funcionarán porque suelen estar desactivados por defecto en bash. Los comodines normales, que acabamos de ver en detalle, aunque potentes, se quedaban algo escasos para algunas cosas, así que en la versión 2 de bash se implementó esta nueva expansión de nombres de ficheros. Pero para mantener compatibilidad hacia atrás, esta expansión solo funciona si se activa previamente, de no hacerlo así los nuevos caracteres podrían hacer que un script que funcionaba correctamente en bash 1.x tuviese un extraño comportamiento en el bash 2.x. Aclarado esto, para que funcionen los patrones extendidos de expansión de nombres de ficheros hay que activarlos previamente (en cada sesión) con la orden:

$ shopt -s extglob

Después de esa orden:

  • ?(patrón) concordará con una o ninguna ocurrencia del patrón dado. Por ejemplo texto?([0-9]).txt concordará con texto.txt, texto1.txt, texto3.txt, pero no con texto12.txt
  • *(patrón) concordará con ninguna o más ocurrencias del patrón dado. Por ejemplo texto*([0-9]).txt concordará con texto.txt, texto1.txt, texto3.txt, texto12.txt y texto12345.txt pero no con texto1a.txt
  • +(patrón) concordará con una o más ocurrencias del patrón dado. Por ejemplo texto+([0-9]).txt concuerda con texto1.txt, texto3.txt, texto12.txt y texto12345.txt pero no con texto.txt
  • @(patrón) concuerda solo con una ocurrencia del patrón. Por ejemplo texto@([0-9]).txt concuerda con texto1.txt, pero no con texto12.txt ni con texto.txt
  • !(patrón) Concuerda con cualquier cosa que no concuerde con el patrón. Por ejemplo texto!([0-9]).txt concordará con textoa.txt, textob.txt, texto19.txt o texto.txt, pero no con texto4.txt

En la evaluación de patrones extendidos, entre los paréntesis podemos poner un patrón simple o una lista de ellos separados por el caracter ‘|’, en caso de poner una lista coincidirá con cualquier cosa que coincida con cualquiera de los patrones de la lista.

Para volver a desactivar los patrones extendidos hay que usar

$ shopt -u extglob

¿Serías capaz de escribir un patrón que concordase con todos los ficheros que comenzaran con ‘img-’ seguidos de una serie de números, y después de un punto las extensiones jpg o jpeg teniendo en cuenta que tanto el img inicial como la extensión al final pueden aparecer en minúsculas, mayúsculas o cualquier combinación de mayúsculas y/o minúsculas? Puedes usar patrones extendidos.


Powered by WordPress. Theme by H P Nadig

Licencia de Creative Commons
Esta obra está bajo una licencia de Creative Commons.