Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Comment on

( #3333=superdoc: print w/ replies, xml ) Need Help??

I've asked Pedagogues what was the procedure to get a translated tutorial into its natural section. I was told that it's OK to post it here, so the following discussion would get archived. That's a great idea.

I translated three tutorials into spanish in some spare time I had last week. This is one of them (original, by pfaut), one that I found very valuable when I read it. I've incorporated most of the suggestions that the original tutorial received in that thread.

I'm posting the other two within a few days.


Tutorial de pack/unpack

Una conversación reciente en el chatterbox me dio la idea para escribir esto. Un programador novato estaba intentando codificar algo con pack y unpack pero tenía problemas a la hora de controlar su funcionamento exacto. Yo nunca he tenido problema con estas funciones pero tengo conocimientos de hardware y estoy muy familiarizado con programación en ensamblador y C. Los que llevan poco tiempo programando seguramente no saben nada de estas cosas a tan bajo nivel y podrían no entender cómo los ordenadores almacenan los datos. Conocer un poco esta materia puede hacer de pack y unpack algo más fácil de entender.

Por qué se necesitan pack y unpack

Perl puede manejar cadenas, enteros y números en coma flotante. De vez en cuando un programador tendrá que intercambiar datos con programas escritos en otros lenguajes. Estos otros lenguajes tienen un conjunto mucho más amplio de tipos de datos. Tienen enteros de distintos tamaños, o pueden ser capaces de manejar solamente cadenas de longitud fija (¿se puede mencionar COBOL?). Otras veces, puede surgir la necesidad de intercambiar datos binarios con otras máquinas a través de una red. Estas máquinas pueden tener distintos tamaños de palabra o almacenar los valores de otras formas (una palabra es un conjunto de bits que el ordenador maneja todos juntos). Se hace necesaria alguna manera de convertir nuestros datos a un formato que estos otros programas y máquinas entiendan. También necesitamos poder interpretar las respuestas que nos llegan de vuelta.

Las funciones pack y unpack nos permiten leer y escribir buffers de datos de acuerdo con una plantilla. Esta plantilla nos permite indicar cosas específicas como el orden de los bytes o el tamaño de la palabra, o también usar los valores por defecto. Esto nos da mucha flexibilidad cuando nos comunicamos con programas externos.

Para entender cómo funciona todo esto, es conveniente saber cómo hacen los ordenadores para almacenar los distintos tipos de información.

Formatos enteros

La memoria de un ordenador se puede ver como si fuera una colección enorme de bytes. Un byte contiene ocho bits (esto es casi universal) y puede representar valores sin signo entre 0 y 255, o valores con signo entre -128 y 127. No se puede hacer mucho con este rango tan pequeño de valores, por lo que los registros de los ordenadores modernos (un registro es una pequeña parte de memoria, con la que el procesador trabaja directamente) son más grandes que un byte. La mayoría de procesadores modernos usan registros de 32 bits (es decir, 4 bytes) y hay algunos con registros de 64 bits. Un registro de 32 bits puede almacenar números sin signo entre 0 y 4294967295 o números con signo entre -2147483648 y 2147483647.

Al almacenar en memoria valores con un tamaño mayor de 8 bits, éstos se dividen en segmentos de 8 bits y se almacenan en posiciones consecutivas de la memoria. Algunos procesadores almacenan el segmento que contiene los bits de mayor peso en la posición de memoria más baja, y los demás segmentos van a las posiciones siguientes (por ejemplo, en el número 1234, la cifra 1 tiene mayor peso que las demás, porque significa 1000). Eso se llama "big-endian". Otros procesadores almacenan primero el segmento que contiene los bits de menos peso, y los demás segmentos van en posiciones superiores. Esto se llama "little-endian".

Lo veremos mejor gráficamente. Supongamos que un registro contiene el número 0x12345678 y lo vamos a almacenar en la memoria. Así es como aparecería:

DirecciónMáquina
Big-Endian
Máquina
Little-Endian
00x120x78
10x340x56
20x560x34
30x780x12

Si miramos perldoc -f pack o consultamos la función pack en Programming Perl, veremos una tabla que lista todos los caracteres que pueden ir en la plantilla, junto con una descripción del tipo de dato que significan. Esa tabla lista varios formatos de números enteros de distintos tamaños, con distintos órdenes, y con signo o sin él:

FormatoDescripción
c,CUn valor de tipo char con signo o sin él (entero de 8 bits).
s,SUn valor de tipo short con signo o sin él, siempre de 16 bits.
l,LUn valor de tipo long, siempre de 32 bits.
q,QUn valor de tipo quad (entero de 64 bits).
i,IUn valor entero con signo o sin él, en el formato nativo de la máquina actual.
n,NUn valor de 16 ó 32 bits con orden "de red" (big-endian).
v,VUn valor de 16 ó 32 bits con orden "VAX" (little-endian).

Los formatos s, l y q empaquetan valores de 16, 32 y 64 bits en el orden nativo de la máquina en la que se ejecute la función pack. El formato i empaqueta un valor con un tamaño de palabra igual al de la máquina actual. Los formatos n y v nos permiten especificar el tamaño y orden de almacenamiento de los bytes, y se usan para intercambiar información con otros ordenadores.

Formatos de caracteres

Las cadenas de texto se almacenan como colecciones de caracteres. Tradicionalmente, cada carácter se codificaba usando un solo byte, con un sistema de codificación como ASCII o EBCDIC. Nuevos sistemas de codificación como UTF-8 usan codificaciones de, o bien una longitud fija de varios bytes, o bien secuencias de bytes de varias longitudes para representar cada carácter.

La función pack de Perl acepta en la plantilla los siguientes caracteres para especificar cadenas de texto:

FormatoDescripción
a,AUna cadena rellena con caracteres nulos/espacios.
b,BUna cadena de bits (binaria) con órdenes de bits ascendiente o descendiente.
h,HUna cadena hexadecimal, con el nybble alto o bajo primero.
ZUna cadena terminada con un carácter nulo.
Un nybble son 4 bits. Un byte se representa en hexadecimal con dos dígitos de 4 bits cada uno, es decir, con dos nybbles.

Las cadenas se almacenan en posiciones de memoria sucesivas, con el primer carácter de la cadena en la posición más baja de la memoria.

La función pack de Perl

La función pack acepta una plantilla y una lista de valores. Devuelve un escalar que contiene los valores empaquetados de acuerdo con los formatos especificados en la plantilla. Esto nos permite escribir datos en un formato que luego podrá ser leído por otro programa escrito en C o en otro lenguaje, o pasar datos a un sistema remoto a través de una red.

La plantilla contiene una serie de letras de las tablas que hay más arriba. Cada letra va seguida, opcionalmente, de un número que indica cuántas veces se ha de repetir este valor (para números) o el tamaño (para cadenas). Un '*' en un formato entero le dice a pack que debe usar este formato para el resto de los valores de la lista. Un '*' en un formato de cadena le dice a pack que use el tamaño de la cadena.

Probemos un ejemplo. Supongamos que estamos recogiendo información de un formulario web y enviándoselo a un backend que está escrito en C, para que lo procese. El formulario le permite a un monje solicitar material de oficina. El backend necesita que la información esté en este formato:

struct SupplyRequest { time_t request_time; // hola de solicitud int employee_id; // empleado que la realiza char item[32]; // material solicitado short quantity; // cantidad short urgent; // ¿es urgente? };

Tras comprobar los archivos de cabecera de nuestro sistema, vemos que time_t es de tipo long. Para crear un registro adecuado para el backend, podemos usar lo siguiente:

    $rec = pack( "l i Z32 s2", time, $emp_id, $item, $quan, $urgent);

Esta plantilla dice "Un long, un int, una cadena de 32 caracteres terminada en un nulo, y dos shorts".

Si el monje número 217641 (anda! si soy yo!) (N del T.: evidentemente, ese no soy *yo*) hace una solicitud urgente de dos cajas de clips ("boxes of paperclips") el día 1 de Enero de 2003 a la 1:00 de la tarde EST, $rec tendría el siguiente contenido (la primera línea es decimal, la segunda va en hex, la tercera son caracteres si tiene sentido). Un carácter pipa indica los límites entre dos campos:

Desplz. Contenido (las direcciones de memoria aumentan hacia la d +erecha) 0 160 44 19 62| 41 82 3 0| 98 111 120 101 115 32 1 +11 102 A0 2C 13 3E| 29 52 03 00| 62 6f 78 65 73 20 +6f 66 | b o x e s + o f 16 32 112 97 112 101 114 99 108 105 112 115 0 0 0 + 0 0 20 70 61 70 65 72 63 6c 69 70 73 00 00 00 +00 00 p a p e r c l i p s 32 0 0 0 0 0 0 0 0| 2 0| 1 0 00 00 00 00 00 00 00 00| 02 00| 01 00

Veamos de dónde sale todo esto. El primer elemento de la plantilla es una l, que empaqueta un long. Un long son 32 bits, o 4 bytes. El valor que ha sido almacenado viene de la función time. El valor almacenado en realidad es 1041444000, o 0x3e132ca0 en hexadecimal. ¿Ves cómo concuerda con el principio del buffer? Mi sistema tiene un procesador Intel Pentium, que es little-endian.

El segundo elemento de la plantilla es una i. Esto quiere un entero del mismo tamaño que un int en la máquina actual. El Pentium es un procesador de 32 bits, por lo que de nuevo empaquetamos el valor en 4 bytes. El número de monje es 217641, 0x00035229.

El tercer elemento de la plantilla es Z32. Esto especifica un campo de 32 caracteres terminados con un nulo. Se puede apreciar la cadena "boxes of paperclips" almacenada a continuación en el buffer, seguida de ceros (caracteres nulos) hasta que se ha llegado al límite de 32 bytes.

El último elemento es s2. Esto significa dos shorts, que son enteros de 16 bits, lo cual consume dos valores de la lista que se la ha pasado a pack. 16 bits se almacenan en 2 bytes. El primer valor tiene la cantidad 2 y el segundo vale 1, indicando urgencia. Estos dos valores ocupan los últimos 4 bytes del buffer.

La función unpack de Perl

Sin que lo supiéramos cuando programamos lo anterior, alguien ha portado el backend de C a Perl. Pero como nosotros ya habíamos escrito la parte web de la aplicación, pensaron que podríamos seguir usando el mismo formato de datos. Por tanto, han tenido que usar unpack para leer los datos que nosotros les enviamos.

unpack es lo contrario de pack. pack coge una plantilla y una lista de valores, y devuelve un escalar. unpack coge una plantilla y un escalar, y devuelve una lista.

En teoría, si le damos a unpack la misma plantilla y el mismo escalar producido por pack, deberíamos obtener de vuelta la lista de valores que le pasamos a pack. Digo en teoría porque si el desempaquetado se hace en una máquina con un orden de bytes distinto (big-endian en oposición a little-endian) o con un tamaño distinto de palabra (16, 32, 64 bits), unpack podría interpretar los datos de forma distinta a como lo ha hecho pack. Los formatos que hemos utilizado especificaban el orden de bytes de la máquina actual e i puede significar tamaños diferentes en máquinas diferentes por lo que podríamos tener problemas. Pero en nuestro simple caso, vamos a asumir que el backend se ejecuta en la misma máquina que el interface web.

Para desempaquetar los datos, el backend ejecutaría una orden como esta:

($order_time, $monk, $itemname, $quantity, $urgent) = unpack( "l i Z32 s2", $rec );

Nótese que la plantilla es idéntica a la que usamos antes al empaquetar, y que pack nos devuelve la misma información en el mismo orden.

Formatos enteros
a.k.a. ¿por qué tantos tipos?

Puedes estar preguntándote por qué hay tantas formas distintas de escribir el mismo tipo de datos. i, l, N y V se pueden usar para escribir un entero de 32 bits a un buffer. ¿Por qué usar uno en concreto? Bueno, eso depende de con quién estemos intentando intercambiar información.

Si sólo vamos a intercambiar información con otros programas dentro del mismo ordenador, podemos usar i, l, s y q y sus equivalentes sin signo (en mayúsculas). Ya que tanto el programa que lee como el que escribe están en la misma máquina, podemos usar los formatos nativos sin problema.

Si estamos escribiendo un programa para leer archivos cuya distribución depende de la arquitectura, es mejor usar los formatos n, N, v y V. De esta forma, siempre sabremos que estamos interpretando la información correctamente sin importar en qué arquitectura se ejecuta nuestro programa. Por ejemplo, el formato de archivos de sonido "wav" está definido para Windows sobre un procesador Intel, que es little-endian. Si quisiéramos leer el encabezado de un archivo wav, deberíamos usar v y V para leer valores de 16 y 32 bits respectivamente.

Se dice que los formatos n y N tienen orden "de red" por una razón: ese es el orden especificado para las comunicaciones TCP/IP. Al hacer ciertos tipos de programación de red, es necesario usar estos formatos.

Formatos de cadenas

Elegir entre los distintos formatos de cadenas es un poco distinto. Normalmente usaremos a, A o Z dependiendo del lenguaje en que esté hecho el otro programa. Si el otro programa está en C o C++, probablemente queramos usar a o Z. A sería una buena opción para comunicarse con programas en COBOL o Fortran.

Los formatos a, A y Z

Al empaquetar, los formatos a y Z rellenarán el espacio sobrante con caracteres nulos (sólo cuando especifiquemos un tamaño). A rellena lo sobrante con espacios. Al desempaquetar, A elimina el espacio y los caracteres nulos que haya al final, Z quita todo lo que vaya después del primer carácter nulo y a devuelve el campo entero tal cual.

Ejemplos

pack ('a8',"hello") produce "hello\0\0\0" pack ('Z8',"hello") produce "hello\0\0\0" pack ('A8',"hello") produce "hello " unpack('a8',"hello\0\0\0") produce "hello\0\0\0" unpack('Z8',"hello\0\0\0") produce "hello" unpack('A8',"hello ") produce "hello" unpack('A8',"hello\0\0\0") produce "hello"

Los formatos b y B

Los formatos b y B convierten cadenas que consisten de caracteres 0 y 1 en bytes, y desempaquetan bytes en cadenas de ceros y unos. Al empaquetar, Perl trata los caracteres con valor par como si fueran un cero, y los caracteres de valor impar como si fueran un uno. La diferencia entre b y B es el orden en que se convierten los bits dentro de cada byte. Con b los bits se espeficican en orden ascendente (primero el de menos peso); con B es al revés. El tamaño representa el número de bits a empaquetar.

Ejemplos

ord(pack('b8','00100110')) produce 100 (4 + 32 + 64) ord(pack('B8','00100110')) produce 38 (32 + 4 + 2)

Los formatos h y H

Los formatos h y H empaquetan cadenas que contienen dígitos hexadecimales. h coge primero el nybble de menos peso, H toma primero el mayor. El tamaño indica el número de nybbles a empaquetar.

Ejemplos

Cada uno de estos ejemplos devuelve un escalar de dos bytes:

pack('h4','1234') produce 0x21,0x43 pack('H4','1234') produce 0x12,0x34

Información adicional

Perl 5.8 incluye su propio tutorial de pack y unpack. Es un poco más profundo que este pero algunas de las cosas de las que habla pueden ser específicas de Perl 5.8. Los usuarios de Perl 5.6 deben comprobar su propia documentación si las cosas no funcionan tal como está decrito en ese tutorial.

Existen más caracteres que se pueden usar en las plantillas, de los que no se habla aquí. También hay formas adicionales de leer y escribir campos ASCII de tamaño fijo, así como trucos con los que se puede jugar con pack y unpack. Prueba perldoc -f pack o consulta Programming Perl. Y sobre todo, no temas experimentar (excepto con programas de verdad). Usa la función DumpString de aquí abajo para examinar los buffers devueltos por pack hasta que entiendas cómo manipula los datos.

Referencias

Programming Perl, Tercera Edición, Larry Wall, Tom Christiansen, y Jon Orwant, © 2000, 1996, 1991 O'Reilly & Associates, Inc. ISBN 0-596-00027-8.

Gracias a bart por la referencia al tutorial de pack/unpack de Perl 5.8.

Gracias a Zaxo y jeffa por revisar este documento y compartir sus propios esfuerzos para crear un tutorial.

Gracias a sulfericacid y PodMaster por inspirar esto en el CB.

Ejemplo

El siguiente programa contiene los ejemplos de este documento:

#!/usr/bin/perl -w use strict; # muestra el contenido de una cadena como bytes decimales y hexadecima +les, y como caracteres sub DumpString { my @a = unpack('C*',$_[0]); my $o = 0; while (@a) { my @b = splice @a,0,16; my @d = map sprintf("%03d",$_), @b; my @x = map sprintf("%02x",$_), @b; my $c = substr($_[0],$o,16); $c =~ s/[[:^print:]]/ /g; printf "%6d %s\n",$o,join(' ',@d); print " "x8,join(' ',@x),"\n"; print " "x9,join(' ',split(//,$c)),"\n"; $o += 16; } } # efectuamos la solicitud my $t = time; my $emp_id = 217641; my $item = "boxes of paperclips"; my $quan = 2; my $urgent = 1; my $rec = pack( "l i a32 s2", $t, $emp_id, $item, $quan, $urgent); DumpString($rec); # procesamos la solicitud my ($order_time, $monk, $itemname, $quantity, $ignore) = unpack( "l i a32 s2", $rec ); print "Order time: ",scalar localtime($order_time),"\n"; print "Placed by monk #$monk for $quantity $itemname\n"; # formatos de cadena $rec = pack('a8',"hello"); # debería producir 'hello\0\0 +\0' DumpString($rec); $rec = pack('Z8',"hello"); # debería producir 'hello\0\0 +\0' DumpString($rec); $rec = pack('A8',"hello"); # debería producir 'hello ' DumpString($rec); ($rec) = unpack('a8',"hello\0\0\0"); # debería producir 'hello\0\0 +\0' DumpString($rec); ($rec) = unpack('Z8',"hello\0\0\0"); # debería producir 'hello' DumpString($rec); ($rec) = unpack('A8',"hello "); # debería producir 'hello' DumpString($rec); ($rec) = unpack('A8',"hello\0\0\0"); # debería producir 'hello' DumpString($rec); # formatos de bits $rec = pack('b8',"00100110"); # debería producir 0x64 (100) DumpString($rec); $rec = pack('B8',"00100110"); # debería producir 0x26 (38) DumpString($rec); # formatos en hexadecimal $rec = pack('h4',"1234"); # debería producir 0x21,0x43 DumpString($rec); $rec = pack('H4',"1234"); # debería producir 0x12,0x34 DumpString($rec);

Update: Translated comments in the last Perl example. Thanks to hossman for pointing it out. More fixes by shmem

--
David Serrano

Moved from Perl Monks Discussion to Tutorials by planetscape

( keep:0 edit:6 reap:0 )


In reply to Spanish translation of pack/unpack tutorial by Hue-Bond

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • Outside of code tags, you may need to use entities for some characters:
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others taking refuge in the Monastery: (11)
    As of 2014-12-17 20:03 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      Is guessing a good strategy for surviving in the IT business?





      Results (31 votes), past polls