Diferencia entre revisiones de «Programando para Linux»

De Jose Castillo Aliaga
Ir a la navegación Ir a la búsqueda
Sin resumen de edición
Sin resumen de edición
Línea 154: Línea 154:
}
}
</nowiki>
</nowiki>
Veamos cómo funciona esta función:
==== Entrada y Salida estándar ====
La biblioteca stdio.h proporciona la entrada y salida estándar. ('''stdin''' y '''stdout'''). Estas se usan con funciones como scanf o printf. La tradición de Unix dice que hay que usar la entrada y salida estándar para poder enlazar varios programas con ''' | (pipes o tuberías)''' o para [[Scripts_Bash#Redirecciones|redireccionar]] la salida con > <.  También está stderr para la salida de errores, que suele ser por pantalla.
Podemos modificar la salida estándar o la entrada estándar para escribir o leer a ficheros y no a la pantalla. Por ejemplo, si queremos escribir algo por la salida de error:
fprintf (stderr, ("Error ...."));
{nota|Hay que mencionar que stdout se comporta como un buffer. Es decir, no escribe en consola lo que ese escribe hasta que el buffer está lleno, el programa acaba o se cierra stdout. Se puede vaciar el buffer con la orden:
fflush (stdout);
stderr no tiene buffer.
Observa el curioso comportamiento de este programa:
#include <stdio.h>
int main(){
  while (1){
  printf(".");
  fprintf(stderr, ":");
  usleep (10000);
  }
  }
}

Revisión del 17:13 16 jun 2014

Este artículo trata de hacer un programa siguiendo los estándares de los programas para GNU/Linux. La mayoría de programas de terminal de Linux cumplen en mayor o menor medida unas pautas para facilitar su uso y aprendizaje. Estas están relacionadas con la filosofía UNIX. Que, resumiendo, se trata de hacer programas que hagan una cosa y la hagan bien, que puedan trabajar juntos y que manejen texto, ya que es la interfaz universal. De esta manera, difícilmente, un programa que solo tenga interfaz gráfica, que haga de todo un poco pero nada demasiado bien o que tenga una formato propietario para guardar sus datos, podrá cumplir los ideales de esta filosofía.

Dentro de esta manera de pensar, otros autores han concretado más las reglas para hacer un buen programa para Unix. A continuación voy a resumir las que me parecen más relevantes y intentaré cumplir en este artículo:

  • Modularidad: Todo programa mínimamente complejo ha de estar formado por módulos que se comunican con unas interfaces bien definidas para poder mejorar los módulos por separado.
  • Claridad: Se debe hacer código legible por el programador y otros. Como se trata de hacer software libre esta regla es bastante importante.
  • Composición: Nuestro programa ha de poder comunicarse fácilmente con los demás para poder hacer comandos más complejos. Por ejemplo, los comandos cat, cut, tr, grep, sed... se pueden poner sin problemas entre | para ir filtrando un texto.
  • Simplicidad: Un programa potente no es necesariamente un programa con miles de opciones y parámetros. Algunos comandos pueden descorazonar al usuario al entra en su manual. Si el programa ha de ser complejo, se debería facilitar un uso básico fácil y una curva de aprendizaje asequible.
  • Robustez: El programa ha de ser capaz de hacer bien su trabajo en cualquier condición.
  • Pocas sorpresas: El programa tendrá una interfaz similar a todos los demás. Por ejemplo, el signo + ha de significar siempre sumar o añadir algo.
  • Silencio: A menos que se le indique, el programa solo sacará los datos pedidos. De esta manera, se pueden enlazar los programas.
  • Fallos ruidosos: Si algo falla, el usuario ha de ser consciente de todos los detalles.
  • Economía: En términos de ciclos de reloj y espacio en memoria.
  • Utilizar ficheros de texto plano: Puede que no sea lo más eficiente. Pero los podrá entender cualquiera y los podrá utilizar cualquier programa. Si es necesario, se pueden comprimir después.
  • Preferencia por la terminal: Todo lo que se puede hacer de forma gráfica se ha de poder hacer por terminal.

A lo largo del artículo nos centraremos en cómo hacer todo esto de manera técnica.

El programa

Hay programas para casi todo en la terminal y no voy a poder hacer algo totalmente nuevo. La función del programa es hacer un traductor de texto plano a braille. Para ello utlitzaremos los caracteres que posee Unicode para braille.

Las funciones del comando braille son las siguientes:

  • Actuar como un filtro similar al tr. Tendrá entrada y salida estándar.
  • Traducir un fichero de texto diréctamente y guardar en uno.
  • Traducir al braille español (de momento) pero permitir elegir el idioma si se implementan más.

De esta manera, ese comando:

$ echo "Hola Mundo" | braille
⡓⠕⠇⠁⠀⡍⠥⠝⠙⠕

Retorna el texto que se introduce por la stdin y lo muestra por pantalla.

Este comando puede ser una buena base porque trata de manera bastante compleja el tratamiento de la entrada/salida y también tiene interpretación de argumentos y opciones.

Implementación

El programa va a ser programador en C.

Interacción con el entorno

C y C++ proporcionan unas interfaces con el sistema Linux para introducir o extraer datos en los programas. Las más importantes son los argumentos, que se pasan al programa en el momento de su ejecución y la entrada y salida estándar para comunicarse con el proceso cuando está ya en marcha.

Los argumentos se pasan con:

  • argc: Variable int que es inicializada en el número de argumentos que se pasan al programa.
  • argv: Array de char* (Punteros a cadenas de caracteres) que contiene cada uno de los argumentos que se han pasado.

Estos argumentos pueden ser opciones o otros argumentos. Las opciones, por convención en Linux, van precedidas por el signo - y modifican aspectos del comportamiento del programa. Estas pueden ser:

  • Cortas: Van precedidas de un solo - y tienen un sola letra. -s -l -A -X...
  • Largas: Van precedidas de dos -- y tiene la palabra completa. Estas son más fáciles de recordar y de entender cuando se ve el comando que ha hecho otro.

Los programas típicos de Unix solían tener opciones cortas. A partir de GNU, también se popularizaron las opciones largas.

Muchas opciones cortas tienen un equivalente en opciones largas. Esto permite alternar entre velocidad o legibilidad.

Leer todas las opciones puede ser un trabajo tedioso. Algunas necesitan un argumento, pueden ir en cualquier orden, pueden ser largas o cortas, pueden tener diferentes espacios... Se puede hacer a mano: un for que recorra argv y que contemple todas estas posibilidades. Sin embargo, hay una librería que simplifica todo esto y evita errores: getopt_long

getopt_long

Se trata de una función que proporciona C y C++ para leer tanto opciones largas como cortas. Para usarlo hay que incluir la biblioteca:

#include <getopt.h>

En el caso de nuestra aplicación, aceptará estas opciones:

Forma corta Forma Larga Utilidad
-f --fileinput Fichero de entrada en caso de que no se use la entrada estándar.
-o --outputfile Fichero de salida en caso de que use la salida estándar.
-h --help Ayuda

Además el programa acepta no tener argumentos.

getopt_long necesita dos estructuras de datos. Una para las opciones cortas y otra para las largas.

La primera es un string con todos los caracteres cortos seguidos. Aquellos que necesitan un argumento extra (la ruta del fichero), tienen : detrás de la letra:

const char* const short_options = "ho:f:";

La segunda estructura es un array de struct con 4 campos: El primero es el string del nombre largo, el segundo es 0 si no necesita argumentos y 1 si los necesita, el tercero es NULL y el cuarto es el equivalente corto.

const struct option long_options[] = {
 { "help", 0,NULL, 'h'},
 { "output", 1, NULL, 'o'},
 { "fileinput", 1, NULL, 'f'},
 { NULL, 0, NULL, 0}
};

El último elemento está todo a 0.

Se invoca getopt_long pasando el argc, el argv y las dos estructuras:

next_option = getopt_long (argc,argv,short_options,long_options,NULL);
  • Cada vez que se invoca esta función, retorna una sola opción. Por tanto, se suele usar dentro de un bucle para ir recogiendo todos.
  • Si se recoge una opción que no está contemplada, imprime un mensaje de error y recoge el caracter ?.
  • Si se requiere un argumento para la opción, este se recoge con la variable optarg.

De esta manera, el código para recoger las opciones y argumentos queda así:

 /* El nombre del fichero de salida y de entrada*/
 const char* output_filename = NULL;
 const char* input_filename = NULL;
 program_name = argv[0];

 do {
  next_option = getopt_long (argc,argv,short_options,long_options,NULL);
  switch (next_option)
  {
  case 'h': /* -h or --help */
     print_usage (stdout, 0);
  case 'o':
     output_filename = optarg;
     break;
  case 'f':
     input_filename = optarg;
     break;
  case '?':
     print_usage (stderr, 1);
  case -1:
     break;
  default:
     abort (); 
  }
 }
 while (next_option != -1);

Esta es la función print_usage() usada tanto para escribir por la stdout el manual como por la stderr en caso de fallo:

void print_usage (FILE* stream, int exit_code)
{
 fprintf(stream, "Usage: %s options [ -f inputfile ...]\n", program_name);
 fprintf(stream,
        " -h --help             Muestra informacion de uso.\n"
        " -o --output filename  Guarda la salida en un fichero.\n"
        " -f --fileinput filename       Permite seleccionar un fichero de entrada. \n");
 exit (exit_code);
}

Veamos cómo funciona esta función:

Entrada y Salida estándar

La biblioteca stdio.h proporciona la entrada y salida estándar. (stdin y stdout). Estas se usan con funciones como scanf o printf. La tradición de Unix dice que hay que usar la entrada y salida estándar para poder enlazar varios programas con | (pipes o tuberías) o para redireccionar la salida con > <. También está stderr para la salida de errores, que suele ser por pantalla.

Podemos modificar la salida estándar o la entrada estándar para escribir o leer a ficheros y no a la pantalla. Por ejemplo, si queremos escribir algo por la salida de error:

fprintf (stderr, ("Error ...."));

{nota|Hay que mencionar que stdout se comporta como un buffer. Es decir, no escribe en consola lo que ese escribe hasta que el buffer está lleno, el programa acaba o se cierra stdout. Se puede vaciar el buffer con la orden:

fflush (stdout);

stderr no tiene buffer. Observa el curioso comportamiento de este programa:

#include <stdio.h>
int main(){
 while (1){
  printf(".");
  fprintf(stderr, ":");
  usleep (10000);
 }
 }
}