Archivo

Archivo para la categoría ‘Linux’

Caché+Compresión+Palabras clave en ficheros CSS y JS

Viernes, 22 de Enero de 2010 admin Sin comentarios

Cuando nos aventuramos en un proyecto web con más o menos visitas, queremos que sea lo más rápido para el usuario y para ello enviar la menor cantidad de información (si nuestro hosting nos cobra además por transferencia también ganamos por esto), para ello podemos comprimir la información (y ya lo soportan la mayoría de los navegadores).
Aunque tenemos que tener en cuenta que si comprimimos información (la compresión es un proceso algo pesado), estamos gastando recursos de CPU que, si lo pensamos, estar comprimiendo la misma información a cada petición que nos hagan de un fichero Javascript es tontería; por tanto, una vez que lo comprimamos, lo almacenamos en disco, que este tipo de información no ocupa tanto.
Por otra parte, si eres de los que realizan una plantilla más o menos completa y la publica con pequeñas modificaciones en diferentes webs, o incluso estar preparados para que un cliente nos cambie la gama de colores y no echarnos las manos a la cabeza; o por ejemplo cuando tenemos que tener en cuenta el mismo color tanto en el código CSS como en el Javascript, podemos escribir ciertas palabras clave dentro del fichero CSS y Javascript que nuestro script PHP traducirá antes de enviar la información al usuario final.

Para ello, y para poco más es el siguiente script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<?
/* tcjs.php versión 0.05 Copyright 2010 Gaspar Fernández*/

/* Inclusión de keywords y compresión de archivos JS y CSS */

/* El código fuente se entrega tal cual, sin garantía para ser */
/* estudiado y utilizado en cualquier entorno libre y no libre. */
/* Eso si, agradecería que me comunicárais si lo usáis en un proyecto */
/* mandando un mail a blakeyed@totaki.com; sólo para saber que esto  */
/* se está usando en algún lugar del mundo :) */

/* Configuración del script */

/* $basepath normalmente es la ruta donde se encuentra el script. Es la ruta base
absoluta a partir de la cual se encuentran todos los archivos. Esta ruta será padre
de la ruta donde se encuentre el fichero CSS o JS que llamaremos. */

$basepath = '/home/gaspy/proyectos/www/poesiabinaria/tests/';
/* $httppath es la URL base de nuestra web o sección, normalmente será la dirección con la que
abriremos el directorio web correspondiente a $basepath. */

$httppath = 'http://localhost/poesiabinaria/tests/';
/* $cachedir es el directorio donde se encontrarán los cachés de los ficheros JS y CSS */
$cachedir = $basepath.'var/gzcache/';
/* $excluded_files es un archivo de texto donde se encuentran (uno por línea) todos los ficheros
que no pasarán por el procesador. Sólo serán comprimidos y guardados en caché. */

$excluded_files = $basepath.'var/excluded';
/* $definiciones_file es un archivo php que incluiremos cargando las definiciones necesarias para
completar el contenido de los CSS y JS. Puede haber un fichero de definiciones tanto raiz como
en el directorio donde se encuentra el CSS o JS a procesar.*/

$definiciones_file = 'lib/tcjc_defs.php';
/* FIN de Configuración del script */

function gf_getgz_cachefile($file)
{
/* Obtenemos un nombre de fichero único para los archivos de cache */
global $cachedir;
$getstr = $file.sizeof($file).filemtime($file);
$hash = base_convert(md5($getstr),16,36); /* Hash en base36 */

return $cachedir.$hash;
}

function add_ending_slash($path)
{
/* Añade una barra al final de la ruta del directorio */
$sep = (PHP_OS == 'Windows')? '\':'/';
$path .= (substr($path,-1) == $sep)? '':$sep;
return $path;
}

function genera_y_cachea($file, $cachefile)
{
/* Genera el cache, sustituyendo las definiciones */
global $basepath, $definiciones_file, $httppath;

$definiciones = array ('%%http_path%%' => $httppath);
/* Si tenemos un archivo auxiliar para incluir definiciones */
if (file_exists($basepath.$definiciones_file))
require_once($basepath.$definiciones_file);

$local_defs = add_ending_slash(dirname($file)).$definiciones_file;

if (file_exists($local_defs))
require_once($local_defs);

$triggers = array_keys($definiciones);
$reemplaz = array_values($definiciones);
$contenidos = file_get_contents($file);
$contenidos = str_replace($triggers, $reemplaz, $contenidos);
file_put_contents($cachefile, $contenidos);
$gz = gzopen($cachefile.".gz","wb5");
gzwrite($gz, $contenidos);
gzclose($gz);
}

function comprime($file, $cachefile)
{
/* Símplemente comprime el archivo */
$contenidos = file_get_contents($file);

file_put_contents($cachefile, $contenidos); /* Hacemos un backup también */
$gz = fopen($cachefile.".gz","wb");
fwrite($gz, gzencode($contenidos));
fclose($gz);
}

function interpretar($file)
{
/* Mira el archivo de excluidos y si no debo interpretarlo, devuelve false */
global $excluded_files;
if (file_exists($excluded_files))
{
$excl = file ($excluded_files);
return (!in_array($file, $excl)); /* Si la encontramos, devolvemos FALSE */
}
return true;
}

$type = (isset($_GET['type']))?$_GET['type']:false;
$file = (isset($_GET['file']))?$_GET['file']:false;

// Nos aseguramos de que las dos esté especificadas
if (($type) &amp;&amp; ($file))
{

/* Establecemos las cabeceras http necesarias */
switch ($type)
{
case 'css': header("Content-type: text/css; charset=utf-8"); break;
case 'js' : header("Content-type: text/javascript; charset=utf-8"); break;
}
header("Cache-Control: must-revalidate");

if (file_exists($basepath.$file))
{
$origftm = filemtime($basepath.$file);
$cachefile = gf_getgz_cachefile($basepath.$file); /* Genera un nombre de archivo de caché único */

@$destftm = filemtime($cachefile); /* No daremos error si no existe el archivo */

if ($origftm>$destftm)
{
/* Si el fichero original es más nuevo que su caché, lo procesamos todo de nuevo */
if (interpretar($file))
genera_y_cachea($basepath.$file, $cachefile);
else
comprime ($basepath.$file, $cachefile);
}

if ( (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) &amp;&amp; (strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')))
{
header("Content-Encoding: gzip");
header("Content-Length: ".filesize($cachefile.".gz"));
readfile ($cachefile.".gz"); /* Si el navegador permite contenidos gzipeados... */
}
else
readfile ($cachefile);
}
/* else ERROR no existe el fichero CSS o JS */
}
/* else ERROR, no se ha especificado tipo de fichero o el fichero */
?>

También puedes descargarlo directamente: aquí: tcjs.php.gz (2.2Kb)
Lo que pretendo es que con este script sólo tengamos que copiar el archivo, definir un par de cosas y funcionar, y que sea totalmente transparente, tanto que nos olvidemos de que lo estamos usando.

Para instalarlo, podemos copiarlo en el directorio raiz de nuestro servidor, el ejemplo que muestro es para Apache (y es necesario tener mod-rewrite), y crear un archivo .htaccess que contenga lo siguiente:

1
2
3
4
RewriteEngine On
RewriteBase /
RewriteRule ^(.*\.css) tcjs.php?type=css&amp;file=$1
RewriteRule ^(.*\.js) tcjs.php?type=js&amp;file=$1

También tenemos que prestar atención a las primeras líneas de código del script tcjs.php:

  • $base_directory será el directorio raiz de todos los css, js, definiciones y caches (no es estrictamente necesario que sea de estos últimos). Es decir, si nuestra web está en /home/www/public_http/ no debemos llamar a nada que esté en un nivel inferior (podrán estar dentro de subdirectorios pero a partir de este punto); es más, los CSS y JS estarán por ejemplo en /home/www/public_http/js; en este caso $base_directory=’/home/www/public_http/
  • $httppath será la dirección web de menor nivel que vayamos a llamar; por ejemplo: http://www.midominio.com/
  • $cachedir será el lugar donde creemos los ficheros de caché, este directorio tiene que tener permisos para que el servidor web pueda escribir en él.
  • $excluded_files será el nombre de un archivo cuyo contenido serán todos los ficheros que no queramos que sean procesados, sólo serán comprimidos. Por ejemplo, si el fichero es /home/www/public_http/js/nieve.js y nuestro $base_directory es /home/www/public_http/, el texto que deberíamos incluir en el archivo será js/nieve.js
  • $definiciones_file es la ruta relativa a un archivo PHP que se cargará cuando vayamos a generar un CSS o JS. Puede haber dos tipos de archivo de definiciones, el global, situado en $base_directory.$definiciones_file y el local situado en (el mismo directorio que el archivo CSS a procesar).$definiciones_file. Por ejemplo:
    • $base_directory=’/home/www/public_http/’
    • El fichero JS que cargamos estará en /home/www/public_http/js/mijavascript.js
    • $definiciones_file=’defs/definiciones.php
    • tcjs.php cargará dos ficheros de definiciones situados en /home/www/public_http/defs/definiciones.php y en /home/www/public_http/js/defs/definiciones.php

El fichero de definiciones (que ya hemos visto donde está contendrá un array llamado $definiciones, pero no lo crearemos de nuevo, sino que lo continuaremos, por ejemplo:

1
2
3
4
...
$definiciones['color_base'] = '0ff00f';
$definiciones['ruta_static'] = 'http://estaticos.miservidor.com/';
$definiciones['visibilidad_mensajes'] = ($debug)?'visible':'hidden';

Podemos utilizarlo por ejemplo para definir un color de objeto desde PHP y que el CSS lo incluya (puede que debamos usarlo desde PHP también, por ejemplo para generar una imagen con ese color de fondo); para una ruta de ficheros estáticos en un servidor (normalmente en mi servidor local será otra ruta), para mostrar directamente una capa que está oculta mientras estamos programando algunas opciones en Javascript, y para muchas cosas más.

Script para subir archivos rápidamente (y actualizarlos)

Jueves, 13 de Agosto de 2009 admin 2 comentarios

Normalmente, cuando estoy desarrollando aplicaciones para Facebook, primero, las hago Offline, ejecutándolas en mi servidor local y, una vez que funcionan, las subo al servidor desde las que se ejecutarán, aunque muchas veces hay ciertos problemas una vez se está ejecutando la aplicación de forma definitiva, que requieren hacer algunas modificaciones más, y con esto, subir varias veces los archivos de los scripts.

Por otra parte, a veces, en ciertas aplicaciones web, es necesario introducir cierta información nueva (que hago fuera de línea) y cuando todas las novedades han sido introducidas procedo a subirlo todo junto.

También es importante, sobre todo para proyectos más o menos grandes, que sólo se suban al servidor los archivos nuevos o modificados antes de la última actualización, es muy importante, ya que la ejecución puede eternizarse si tenemos que subir 10Mb de datos cada vez que queremos actualizar, por lo tanto, almacenamos en un archivo el momento en que se ejecutó el script por última vez, y cada vez que buscamos archivos, lo hacemos con los que han sido actualizados a partir del momento que almacenamos en el fichero anterior.

Para eso creé este script (aún queda mucho trabajo por hacer), pero por ahora hace bastante bien el apaño. Yo llamo a este archivo (autoftp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#!/bin/bash

servidor=$1
ruta_serv=$2
ruta_local=$3
endline="
"

dir_actual=`pwd`
cd $3

if [ -e $ruta_local"/last_update" ]; then
    f_locales=`find . -newer $ruta_local/last_update`
else
    f_locales=`find .`
fi

function cierratodo()
{
    kill $ftp_pid
    kdialog --msgbox $1
}

function estadoman()
{
    while read estado
    do
        estadonum=`echo $estado | cut -d' ' -f1`
        echo $estado
        if [[ $estado != "Not connected" ]]; then
            if [[ $estadonum == "226" ]]; then
                subidos=$(($subidos+1))
                kdialog --passivepopup "Subido archivo "$subidos" de "$total_archivos 2
            elif [[ $estadonum == "221" ]]; then
                cierratodo "Los archivos se subieron con éxito"
                date +%s > $ruta_local"/last_update"
            # Fin del programa
               
            fi
        else
            echo "Cerrandooo"
            cierratodo "La conexión se cerró inesperadamente"
        fi
    done
}

to_do="pass"$endline"cd $ruta_serv"$endline;
last_dir="";
total_archivos=0
for i in $f_locales
do
        arch_relat=${i:2}
        if [ ! -z $arch_relat ]; then
            nombre_dir=`dirname $arch_relat`
            if [[ $nombre_dir == "." ]]; then
                nombre_dir=""
            fi
                if [ -d $arch_relat ]; then
                    to_do=$to_do"cd $ruta_serv/$nombre_dir"$endline
                    to_do=$to_do"mkdir $ruta_serv/$arch_relat"$endline
                else
#                   echo "*"$nombre_dir"="$last_dir"*";
                    if [[ $last_dir != $nombre_dir ]]; then
                        last_dir=$nombre_dir
                        to_do=$to_do"cd $ruta_serv/$nombre_dir"$endline
                    fi

                    to_do=$to_do"put $ruta_local/$arch_relat "`basename $arch_relat`$endline
                    total_archivos=$(($total_archivos+1))
#                     to_do=$to_do"put $3/$arch_relat\n"
                   echo "FICHERO --- $arch_relat";
                fi
        fi
done

OLDIFS=$IFS
IFS="
"

to_do=$to_do"quit"$endline

if [[ $total_archivos > 0 ]]; then
    for j in $to_do ; do echo $j;done;

    rm /tmp/ftp_pipe > /dev/null 2>&1
    mkfifo /tmp/ftp_pipe > /dev/null 2>&1

ftp -v totaki.com > /tmp/ftp_pipe <<EOF &
`for j in $to_do ; do echo $j;done;`
EOF

    ftp_pid=$!

else
    kdialog --msgbox "No hay archivos para subir"
fi
cd $dir_actual
IFS=$OLDIFS

rm /tmp/ftp_pipe > /dev/null 2>&1

Para su uso, son necesarios tres parámetros:

  • Dirección del servidor
  • Ruta donde se colocan los archivos en el directorio remoto
  • Ruta de donde se buscarán los archivos en el directorio local

Requerimientos:

  • kdialog (Se puede modificar el script para utilizar otro fácilmente)
  • ftp (Este programa se encargará de subir los archivos)

Para configurar el script es necesario editar el archivo .netrc localizado en nuestro home y donde irá el nombre de usuario y contraseña del FTP (este fichero es leído por ftp automáticamente y así no necesitamos incluir passwords ni en la línea de comando ni en el propio script, que quedan muy feos). El fichero .netrc tiene la siguiente estructura:

machine [servidor] login [usuario] password [contraseña]

Por ejemplo podemos hacer un .netrc que contenga:

machine www.servidor.com login pepito password josefina

Una vez completado esto, podemos asociar una tecla al script, y con ello simplemente pulsando esa tecla se subirán los archivos al servidor.
Por último, yo lo tengo configurado en Fluxbox con Mod4+F5 o lo que es lo mismo la tecla de Windows y F5; basta con crear un script lanzador que incluya lo siguiente (yo lo llamo ftp_upload):

1
2
3
4
#!/bin/bash
# Modelo
#/home/gaspy/.scripts/autoftp [servidor] [ruta interna] [ruta_local]
#/home/gaspy/.scripts/autoftp ftp.servidor.com http_docs/pruebas /home/yomismo/proyectos/pruebas

añadir al fichero keys dentro de /home/[usuario]/.fluxbox/ la siguiente línea:

Mod4 F5 :execcommand ~/.scripts/ftp_upload

Tuberías con nombre para comunicación entre procesos

Domingo, 19 de Julio de 2009 blakeyed Sin comentarios

Las tuberías con nombre son un método de comunicación FIFO entre procesos (también se les llama fifos). FIFO son las siglas de First In First Out, es decir, el primero que llega es que primero que se marcha, como en una cola, el primero que llega es el que antes termina.

Observad lo que hacen las macros depura_int y depura_string, nos darán el número de línea y el archivo donde están, así como el nombre de la variable y el valor que tiene en ese momento.

Normalmente las tuberías son anónimas, aunque el hecho de tenerlas con un nombre en nuestro sistema de ficheros puede sernos de gran utilidad. Como ejemplo, podemos verlo en funcionamiento en la siguiente imagen:

Cómo hacer una pipe con nombre en 1 minuto

Cómo hacer una pipe con nombre en 1 minuto

Primero vemos el terminal de abajo, en el que se ejecuta lo siguiente:

1
2
$ mkfifo prueba
$ cat prueba

Seguidamente en el otro terminal se escribe lo siguiente:

1
$ echo "Esto es una prueba" > prueba

Automáticamente el texto “Esto es una prueba” aparece en el primer terminal. ¡Hemos logrado enviar un mensaje!

De la misma forma podemos programar aplicaciones que utilicen estas características de forma muy fácil en casi cualquier lenguaje de programación, para los ejemplos utilizaré C. Debemos hacer dos programas, uno que escriba en la tubería (pipe) y otro que lea de ella. Empezamos por pipewrite.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
  FILE *mipipe;
  char buffer[128];
  int i=0;

  if (mkfifo("pipetest", S_IWUSR |  /* El usuario puede escribir */
                 S_IRUSR |  /* El usuario puede leer */
                 S_IRGRP |  /* El grupo puede leer */
                     S_IROTH    /* Otros pueden leer */
         )!=0)
    printf ("Hubo un problema al crear la pipe\n");

  mipipe=fopen("pipetest", "w"); /* Lo abrimos como un fichero normal y corriente */

  /* Con esta línea leemos hasta que se cierre la tubería por el otro */

  while (i<10)
    {
      sprintf(buffer, "CADENA ENVIADA Número: %i\n", i+1);
      fputs(buffer, mipipe);
      i++;
    }

  fclose(mipipe);       /* Cerramos la tubería por aquí también */

Y ahora pipecilla.c (o el programa que leerá la pipe):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
  FILE *mipipe;
  char buffer[128];
  if (mkfifo("pipetest", S_IWUSR |  /* El usuario puede escribir */
                 S_IRUSR |  /* El usuario puede leer */
                 S_IRGRP |  /* El grupo puede leer */
                     S_IROTH    /* Otros pueden leer */
         )!=0)
    printf ("Hubo un problema al crear la pipe\n");

  mipipe=fopen("pipetest", "r"); /* Lo abrimos como un fichero normal y corriente */

  /* Con esta línea leemos hasta que se cierre la tubería por el otro */
  while (!feof(mipipe))      
    {
      if (fgets(buffer, 128, mipipe))
      printf ("RECIBIDO: %s", buffer);
    }

  fclose(mipipe);       /* Cerramos la tubería por aquí también */
}

Ahora compilamos y ejecutamos en terminales diferentes de nuevo:
Probando pipes
En el terminal de pipewrite.c, al ejecutar el programa da un mensaje de error, es porque la pipe ya existe (se creó en el programa anterior).

Esta forma de comunicación es unidireccional, puede ser últil por ejemplo para publicar el estado de un programa (un reproductor de audio podría publicar la canción que está sonando como en xmms-infopipe), o para modificar el comportamiento de un software mientras éste está en ejecución. Pero si queremos que nuestras aplicaciones dialoguen (en full-duplex), debemos tener dos pipes simultáneas, en ese caso, la aplicación 1, por ejemplo deberá leer en la tubería 1 y escribir en la tubería 2; y la aplicación 2, deberá escribir en la tubería 1 y leer en la tubería 2.

Colores y posicionamiento en terminales Linux (como conio.h en DOS)

Viernes, 29 de Mayo de 2009 blakeyed Sin comentarios

A veces, es un poco difícil que alguien que sólo conoce conio.h se pase a Linux, más que nada, porque se puede utilizar ncurses, pero hay que cambiar un poco de mentalidad para poder trabajar con la nueva biblioteca.

Por eso, hace unos meses creé unas cuantas funciones que se podían utilizar para ir reemplazándolas poco a poco. Funcionan de forma muy parecida a conio.h (en los colores). Se basan en códigos ANSI, y las podemos utilizar para cualquier programa rápido en que necesitemos utilizar colores en terminal. ncurses es muy potente y si vamos a complicarnos un poco más la vida, mejor utilizar esta segunda.

Cuando estuve implementando estas funciones, tuve un problema, a la hora de leer información del terminal sin mostrarla por pantalla, me basé en: wsize.c de Stephen J. Friedl. También podéis encontrar información sobre los códigos ANSI aquí.

He incluído la opción subrayado (UNDERLINE) y un par de funciones que nos calculan las dimensiones de la pantalla (ya que los terminales pueden ser redimensionados y ser enormes).

Descargar stermp - Simple Terminal Play (stermp.c, stermp.h)

Os pongo aquí el código del ejemplo (conio.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include "stermp.h"

int main()
{
    int pp, fn;
    clrscr();
    for (pp=BLACK; pp<=WHITE; pp++)
        {
            gotoxy(5, 20+pp);
            for (fn=BLACK; fn<DARKGRAY; fn++)
                {
                    textcolor(pp);
                    textbackground(fn);
                    printf("COLOR\t");
                }
        }
    restore_color();
    textcolor(RED+UNDERLINE);
    gotoxy(51, 5);
    printf("Posición X: %d\n", wherex());
    printf("Posición Y: %d\n", wherey());
    printf("Pantalla %dx%d", screenwidth(), screenheight());

}

Decir la hora en palabras (v1)

Jueves, 28 de Mayo de 2009 blakeyed 3 comentarios

Si tenéis un equipo Linux que actúe como servidor y equipo de música (por poner algo), y no tenga un monitor conectado en todo momento; os puede ser útil este script:

1
2
3
4
5
6
7
8
#!/bin/bash

hora=`date +%H`
min=`date +%M`

decir="Son las "$hora" y "$min

echo $decir | espeak -ves

Si asociamos este script a una combinación de teclas, por los altavoces nos dirá la hora que es. Es muy útil en ocasiones :) A artir de aquí se puede extender tanto como se quiera: por ejemplo para que diga la hora en un lenguaje más coloquial: Son las tres menos cuarto, por ejemplo. Es cuestión de imaginación.

Requiere del sintetizador espeak aunque no debe ser difícil utilizar festival u otros.

Espero que os sea de gran ayuda

Categories: Bash, Linux Tags: , , , ,

Desea continuar? [(S)i, (N)o, (M)e lo pienso]

Lunes, 25 de Mayo de 2009 blakeyed Sin comentarios

Es común en los programas de consola hacer preguntas al usuario y éste responda, pero sólo una de las opciones válidas, si no, volver a preguntarle hasta que nos diga un valor correcto.

Aquí os enseño un ejemplo sencillo de todo esto (utilizaré la función mygetch):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>

int mygetch(int echo)
{
    struct termios oldt, newt;
    int ch;

    /* Obtenemos atributos del terminal */
    tcgetattr( STDIN_FILENO, &oldt );
    newt = oldt;
    /* Eliminamos el modo canónico: caracteres especiales */
    newt.c_lflag &= ~ICANON;
    /* Eliminamos el echo a voluntad */
        if (!echo)
            newt.c_lflag &= ~ECHO;
    /* Definimos los nuevos atributos al terminal */
    tcsetattr( STDIN_FILENO, TCSANOW, &newt );
    ch = getchar();
    /* Ponemos los atributos como estaban al principio */
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
    return ch;
}

int get_option(char *texto, char *validas)
{
    int tecla;
    char *ok;
    do
        {
            /* Mostramos texto en pantalla */
            printf("%s: ", texto);
            /* Pedimos una tecla y la pasamos a mayúsculas, para no */
            /* hacer demasiadas comprobaciones luego */
            tecla=toupper(mygetch(1));
            /* strpbrk nos dirá si tecla es una de las válidas */
            ok=strpbrk((char*)&tecla, validas);
            /* strpbrk devuelve NULL si no ha encontrado tecla en validas */
            if (ok==NULL)
                printf("\n¡Opción incorrecta! Por favor seleccione una de estas: %s\n", validas);
        } while (ok==NULL);
    return tecla;
}

int main()
{
    int tecla;

    do
        {
            tecla=get_option("Desea continuar? [(S)i, (N)o, (M)e lo pienso]", "SNM");
            switch (tecla)
                {
                case 'S': printf("\nHas elegido continuar...\n");
                      break;
                case 'N': printf("\nSalir\n");
                      break;
                case 'M': printf("\nTe doy 5 segundos para pensártelo\n");
                      sleep(5);
                      break;
                }
        } while (tecla!='N');
    return 0;
}

Como vemos, sólo tenemos que llamar a la función get_option, pasarle las opciones válidas, y la función sólo nos va a devolver una de las opciones que le digamos.

Categories: C/C++, Linux Tags: , , ,

Obtener UNA tecla

Domingo, 24 de Mayo de 2009 blakeyed Sin comentarios

Hace mucho tiempo, cuando empezaba con la programación, tenía la librería conio.h de Borland (en la que aún se siguen basando en muchos sitios), que nos permitía entre otras cosas borrar la pantalla, posicionarnos dentro de la pantalla, escribir con colores, y pedir una tecla al usuario.
Bien, vayámonos al último caso, pedir una tecla al usuario, se hacía con la función getch, y con sólo pulsar la tecla, salía de la función, es decir, no hacía falta pulsar enter.
Pero cuando nos vamos a linux, nos damos cuenta de que tenemos que utilizar getchar() y esa función estará pidiendo letras del teclado hasta que pulsemos enter.

Podemos optar por varias soluciones:

  1. Utilizar ncurses (que le da mil vueltas a conio.h)
  2. Si sólo queremos algo puntual, la que os propongo a continuación

La solución la encontré hace un tiempo y la tenía por aquí archivada, y la primera publicación del código que he encontrado ha sido esta por kermi3, la función original la publicó VvV.

La solución que propongo tiene un pequeño cambio en el que podemos elegir si queremos que se muestre dicha tecla pulsada o no (echo), y está comentada:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <termios.h>
#include <unistd.h>

int mygetch(int echo)
{
  struct termios oldt, newt;
  int ch;

  /* Obtenemos atributos del terminal */
  tcgetattr( STDIN_FILENO, &oldt );
  newt = oldt;
  /* Eliminamos el modo canónico: caracteres especiales */
  newt.c_lflag &= ~ICANON;
  /* Eliminamos el echo a voluntad */
    if (!echo)
      newt.c_lflag &= ~ECHO;
  /* Definimos los nuevos atributos al terminal */
  tcsetattr( STDIN_FILENO, TCSANOW, &newt );
  ch = getchar();
  /* Ponemos los atributos como estaban al principio */
  tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
  return ch;
}

int main()
{
  int tecla;
  printf("Pulsa una tecla (sin echo): ");
  tecla=mygetch(0);
  printf("\nHas pulsado la tecla %c (%c)\n", tecla, tecla);

  printf("Pulsa una tecla (con echo): ");
  tecla=mygetch(1);
  printf("\nHas pulsado la tecla %c (%c)", tecla, tecla);
}

La incluiré en la próxima revisión de strutils.

Categories: C/C++, Linux Tags: , , , , ,

Buffer de teclado en linux

Sábado, 23 de Mayo de 2009 blakeyed Sin comentarios

A veces, mientras se está desarrollando un pequeño programa en C en el que hay entradas del usuario por teclado, hay veces que parece que se pulsan teclas solas, esto es debido a una entrada de teclado anterior que no ha llegado a volcarse entera en nuestras variables.
En principio tenemos fflush(), es una función para el volcado de buffer de escritura, escritura de ficheros o escritura en la pantalla; pero bueno, funciona cuando hacemos fflush(stdin) y compilamos en Windows, pero no en Linux… tenemos un par de soluciones (aunque seguro que se nos ocurren muchas más):

  1. __fpurge() - Aunque no es una función estandar, y en alguna ocasión me ha dado algún problema (no ha hecho su trabajo como debía).
  2. La otra solución, parece un poco más rudimentaria, pero no me ha dejado tirado:
1
2
int ch;
while ((ch = getchar()) != '\n' &amp;&amp; ch != EOF);
Categories: C/C++, Linux Tags: , , , ,
Easy AdSense by Unreal