Jun 10
GNU/Linux A todos los que utilizamos una shell de linux más o menos habitualmente nos ha pasado que hemos perdido información valiosa que creíamos que teníamos a buen recaudo en el historial.

Con el tiempo nos damos cuenta que el histórico de comandos que se graba en ~/.bash_history se actualiza cada vez que cerramos nuestra sesión (logout). Esto trae muchos problemas cuando tenemos abiertas varias sesiones simultáneamente ya que lo que grabamos en una al cerrar sesión se pierde cuando posteriormente cerramos otras sesiones. Las posibilidades de perder la información del histórico son muy grandes. Es raro que no se haya pensado en esto pues es algo muy común. Pues estamos equivocados nuevamente porque el problema se puede solucionar rápidamente con opciones de shell.

Basta con ejecutar

$ shopt -s histappend

Para que cuando se cierren una sesión de shell se añadan los comandos ejecutados en esta sesión al fichero de historial en lugar de sobrescribir su contenido.

Además también podemos hacer que los comandos se graben en el histórico justo después de ser ejecutados y no cuando se cierra la sesión. Basta con fijar esta variable de esta manera:

PROMPT_COMMAND='history -a'

Hay que tener cuidado aquí si PROMPT_COMMAND ya ha sido inicializada y tiene algún valor podemos perderlo y es mejor hacer esto:

PROMPT_COMMAND="history -a;$PROMPT_COMMAND"

Por su puesto podemos añadir estas dos lineas

shopt -s histappend
PROMPT_COMMAND="history -a;$PROMPT_COMMAND"

a nuestro fichero ~/.bashrc para que se activen automáticamente estas opciones al inicio de la sesión. Y si tenemos acceso al root de la máquina y queremos que todos los usuarios tengan las mismas opciones activas podemos escribir las dos líneas en el fichero /etc/bash.bashrc


Bueno creo que este blog se esta convirtiendo poco a poco en un pequeño libro de recetas sobre pequeños problemas que van surgiendo al utilizar linux. Espero que sean de utilidad.

Publicado por Abraham Covelo

Jun 10
GNU/Linux Hay bastante gente que anda incluyendo las fuentes de launchpad para poder descargarse algunos paquetes no oficiales para Ubuntu. También hay gente que tiene problemas con la validación de los paquetes porque no tienen instalado la clave gpg adecuada para launchpad. Los síntomas del problema son los siguientes:

Abren una consola de root y ejecutan:

# apt-get update

Y tras algunos mensajes relacionados con la descarga de las últimas listas de paquetes actualizados le aparece por consola algo tal que así

W: GPG error: http://ppa.launchpad.net intrepid Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 28A8205077558DD0


Y te sugiere que vuelvas a darle al apt-get update para solucionar el problema. La solución no esta ahí. En su lugar hay que ver el hash que aparece justo al final del mensaje de aviso en concreto en sus últimos 8 caracteres. En el ejemplo serían 77558DD0 pero en tu caso podrían ser otros (consulta la salida de tu apt-get update)

Para añadir las claves basta con ejecutar lo siguiente pero sustituyendo $key por esos 8 caracteres:

# gpg --keyserver subkeys.pgp.net --recv-key $key && gpg -a --export $key | sudo apt-key add -

El problema/molestia habrá desaparecido

Publicado por Abraham Covelo

Jun 9
GNU/Linux La shell bash no es capaz por si sola ni de reconocer ni de analizar expresiones regulares. Sin embargo tiene un proceso para descubrir/detectar nombres de fichero de una manera más o menos automatizada conocida con el nombre de expansión de nombre de fichero o también globbing.

Mediante el globbing y empleando caracteres especiales como *, ?, ^, -, comas paréntesis, llaves y corchetes; bash puede analizar expresiones que le permiten sustituir dicha expresión por el conjunto de ficheros que verifican dicha expresión.

Veamos cual es el significado de cada uno de estos caracteres y como funcionan con varios ejemplos:

* Sirve para sustituir cualquier conjunto de caracteres, incluso un conjunto vacío que este presente en el nombre de los ficheros a detectar. No detectará implicitamente ficheros que comiencen por punto

Ejemplos:

$ echo *
Todos los ficheros que no empiecen por punto

$ echo .*
Todos los ficheros que empiecen por punto

$ echo a*
Todos los ficheros que empiecen por 'a'

$ echo *a*
Todos los ficheros que contengan al menos una 'a'

? Sirve para sustituir 1 caracter de cualquier tipo (excepto el punto al comienzo del nombre del fichero como en el caso del asterisco).

$ echo ???
Todos los ficheros que tengan 3 caracteres por nombre

$ echo ?a*
Todos los ficheros cuyo segundo caracter sea una 'a'

[] Para rangos de caracteres separados por guión o un conjunto de caracteres
Ejemplos:

$ echo [a-z]*
Todos los ficheros que empiecen por una letra minúscula del alfabeto inglés

$ echo *[0-9]
Todos los ficheros que terminen con un número

$ echo *[abc]*
Todos los ficheros que contengan una a, una b o una c minúscula

{} lista separada con comas de expansiones válidas. Permite combinar aditivamente varios expresiones de globbing

Ejemplo:
$ echo {*[abc]*,*[0-9],hola,???}

^ Negación de un rango de caracteres

Ejemplo:
$ echo [^0-9]*
Ficheros que no comienzan por numero

Globbing admite un modo extendido que habilita mas wildcards y posibilidades. Para tenerlo disponible debemos ejecutar:

$ shopt -s extglob

+, !, @ estarían ahora disponibles como wildcards

Un interesante y sutil punto a tener en cuenta es que el globbing lo realiza directamente el interprete bash y no los programas que lancemos a través de él ni el sistema operativo. Esto quiere decir que el globbing lo realizará el bash antes de ejecutar el programa al que sólo le llegarán los nombres de ficheros que hayan sido detectados correctamente mediante globbing. En la inmensa mayoría de los casos esto carece de importancia pero en otros si que la tiene. Por ejemplo, cuando ejecutamos programas a través de exec() o su familia de funciones amigas. Si no ejecutamos el programa a través de bash no tendremos disponible la expansión de nombre de ficheros.

Existe otro caso bien conocido en el que esta característica se vuelve importante. Imaginemos una carpeta que contiene cientos de miles de ficheros digamos más de cien mil (en realidad con tener más de 65535 nos llega). Por algún motivo (que bien podría ser este) queremos borrar estos ficheros. Entramos en este directorio y escribimos

$ rm -rf *

Pero rm se niega a ejecutarlo dando este mensaje de error

-bash: /bin/rm: Argument list too long

¿Que es lo que pasa?

Pues que bash ha pasado al programa rm una lista con más de 65535 argumentos que es más de los permitidos para que rm (o cualquier otro programa de bash) pueda ejecutar a causa del globbing. Para solucionar esto podemos ejecutar por ejemplo:

find -type f -exec rm -f {} \;

Cuidado esta instrucción no es equivalente a la anterior aunque si es válida en este escenario. La diferencia radica en que la instrucción con find también borrará ficheros que comiencen por punto que estén en el directorio actual. Sin embargo rm -rf * no los borrará (aunque si los ficheros que comiencen con punto incluidos en subdirectorios del directorio actual).


Publicado por Abraham Covelo

Jun 5
GNU/Linux A la hora de programar en C hay 3 tipos de funciones a las que podemos recurrir. Las funciones ordinarias internas a tu programa (desarrollo propio al programa), las funciones de librería que son funciones ordinarias que residen en una librería externa a tu programa (desarrollo propio o ajeno al programa). Como por ejemplo las funciones de la librería estandar C (libc). Una llamada a estas funciones es igual a cualquier llamada a una función. Los argumentos son situados en los registros del procesador o en la pila. La ejecución es transferida al comienzo del código de la función, que tipicamente está cargada en una librería compartida. Y, por último, las llamadas al sistema (system call).

Una llamada al sistema está implementada en el núcleo de Linux. Cuando un programa llama a una función del sistema, los argumentos son empaquetados y manejados por el núcleo, el cual toma el control de la ejecución hasta que la llamada se completa. Una llamada al sistema no es una llamada a una función ordinaria, y se requiere un procedimiento especial para transferir el control al núcleo. Sin embargo la librería GNU C encapsula la llamadas al sistema con funciones de manera que pueden ser llamadas de manera sencilla y se confunden con llamadas a funciones ordinarias. Las funciones de entrada/salida como open y read son ejemplos de llamadas al sistema en Linux.

El conjunto de llamadas al sistema Linux forman el interfaz más básico entre los programas y el núcleo de Linux.

Existen llamadas al sistema que sólo pueden ser ejecutados por el superusuario en caso contrario fallarán. Además una función de librería puede invocar una o más funciones de librería o llamadas del sistema como parte de su implementación.

Linux tiene más de 300 diferentes llamadas al sistema. Un listado de las llamadas al sistema de tu versión del kernel de linux se puede encontrar en /usr/include/asm/unistd.h actualmente este fichero incluye a otros dos uno para arquitecturas de 32 bits y otro para arquitecturas de 64 bits (unistd_32.h y unistd_64.h respectivamente)

Para poder introducirnos en las llamadas al sistema es necesario conocer strace. Strace (system trace) es un comando de la shell donde puedes depurar las llamadas al sistema de cualquier programa. strace te informa sobre las llamadas al sistema que realiza un programa además de las señales que recibe. Es por tanto un herramienta muy valiosa de depuración.

Para utilizar strace basta con escribir strace seguido del nombre del programa que quieres analizar, por ejemplo

$ strace hostname

strace basicamente ejecuta el comando especificado hasta que este finaliza. Strace intercepta y registra las llamadas al sistema que el programa ha ejecutado y las señales recibidas por el proceso. strace envia a stderror o a un fichero especificado con la opción -o el nombre de la llamada al sistema, sus argumentos y el valor de retorno de la llamada. Por ejemplo:

execve("/bin/hostname", ["hostname"], [/* 43 vars */]) = 0

La llamada al sistema es execve. Los parámetros que se pasan al programa son los que se encuentran entre parámetros "/bin/hostname", ["hostname"], [/* 43 vars */] y el valor de retorno aparece despues del igual (0)

Para señales se imprime el simbolo de la señal y una cadena explicativa rodeado de '---' y '+++'. Por ejemplo:

--- SIGFPE (Floating point exception) @ 0 (0) ---
+++ killed by SIGFPE +++

En el caso de SIGKILL podemos ver:

<unfinished ...>

Recordemos que esta señal no se puede enmascarar.

A continuación voy a analizar un par de llamadas al sistema bastante comunes que pueden servir como ejemplo. El primero es access que sirve para comprobar los permisos sobre un determinado fichero. Access puede comprobar cualquier combinación de permisos lectura, escritura y ejecución.

La llamada a access tiene 2 argumentos el primero es la ruta al fichero cuyos permisos queremos comprobar el segundo es un campo de bits tipo OR que puede contener R_OK, W_OK, X_OK que corresponden a verificar los permisos de lectura, escritura y ejecución. El valor de retorno es 0 si tiene todos los permisos especificados por el segundo parámetro. Si el fichero existe pero no tiene todos los permisos especificados devuelve -1 y errno cambia a EACCES (o a EROFS si se ha solicitado los permisos de escritura para un fichero de sólo lectura).

Si el segundo argumento es F_OK sólo se comprueba la existencia del fichero. Si el fichero existe devuelve 0 si no existe devuelve -1 y fija errno a NOENT. Si el directorio donde está el fichero es inaccesible errno se fija a EACCESS. Veamoslo con un ejemplo:

#include <error.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char** argv)
{
if(argc!=2)
{
printf("Usage: %s file-path\n",argv[0]);
return 1;
}
char* path = argv[1];
int sc;
sc = access(path,F_OK);
if(sc == 0) printf("%s existe\n", path);
else printf("%s no existe o es inaccesible\n", path);
return 0;
}

Otro ejemplo de llamada a sistema es fsync. Fsync permite sincronizar el contenido de un fichero con su copia en disco. Linux utiliza sincronizaciones asincronas entre la copia del fichero en memoria y la copia en disco. Para asegurarse de que ambas copias contienen la misma información. El sistema se encarga de hacer esto automaticamente pero es posible hacerlo manualmente a través de esta llamada. Este es un ejemplo.


#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
const char* journal_filename = "journal.log";
void write_journal_entry (char* entry)
{
int fd = open (journal_filename, O_WRONLY | O_CREAT | O_APPEND, 0660);
write (fd, entry, strlen (entry));
write (fd, "\n", 1);
fsync (fd);
close (fd);
}

int main(int argc, int argv)
{
write_journal_entry("Esto va directo a disco");
return 0;
}

Si compilas el fichero anterior y al ejecutable lo llamamos sync en el directorio actual podemos ejecutar

$ traceroute sync

Y así ver como traceroute nos muestra la llamada al sistema, sync().

Publicado por Abraham Covelo

(Página 2 de 2, en total 8 entradas)