Desarrollo

Trabajando con ficheros – leer ficheros

Desarrollo PHP

Como vamos a ver a continuación en este artículo sobre cómo leer ficheros en PHP, nos vamos a encontrar con múltiples opciones para poder realizar su lectura, aunque normalmente utilicemos como base algunas de ellas. La lectura de ficheros de texto engloba a una multitud de funciones con las que poder obtener los datos, leer líneas, moverse por el fichero, controlar que se llega al final, volver al principio…, lo que enriquece el arte de leer ficheros. Vamos a ir viendo, poco a poco, muchas de estas opciones disponibles.

Fichero de ejemplo para su lectura

Para los siguientes ejemplos de cómo leer ficheros, en los que iremos leyendo el fichero con distintos métodos, vamos a utilizar el siguiente fichero con extensión txt de ejemplo y al que vamos a llamar fichero.txt:

Línea 1|Dato 1-1|Dato 1-2|Dato 1-3
Línea 2|Dato 2-1|Dato 2-2|Dato 2-3
Línea 3|Dato 3-1|Dato 3-2|Dato 3-3
Línea 4|Dato 4-1|Dato 4-2|Dato 4-3

fread para una lectura hasta donde le indiquemos

Con la función fread podemos leer ficheros de forma que le indiquemos cuántos bytes deben ser leídos. Esta función realiza una lectura en modo binario seguro, por lo que en Windows le debemos indicar, en el modo de apertura del fichero, la b al final que comentamos, pues Windows diferencia entre ficheros binarios y de texto.

Esta función recibe como parámetros el fichero abierto por uno de los métodos que vimos en el anterior artículo y la longitud de bytes que debe leer.

Un ejemplo de fread

<?php
$fichero = fopen('fichero.txt', 'rb');

$datosLeidos = fread($fichero, 70);

fclose($fichero);

echo $datosLeidos . '<br><br>';
echo 'Se han leído ' . strlen($datosLeidos) . ' caracteres';
?>

Este código muestra por pantalla lo siguiente:

Línea 1|Dato 1-1|Dato 1-2|Dato 1-3 Línea 2|Dato 2-1|Dato 2-2|Dato 2

Se han leído 70 caracteres

Todo ha ido bien, ¿o no? Si nos ponemos a contar el número de caracteres de forma manual nos sale que hay 68 caracteres, ¿qué está pasando?

strlen y mb_strlen

Vamos a colocar unas nuevas líneas en el ejemplo indicado tal que así:

<?php
$fichero = fopen('fichero.txt', 'rb');

$datosLeidos = fread($fichero, 70);

fclose($fichero);

echo $datosLeidos . '<br><br>';
echo 'Se han leído ' . strlen($datosLeidos) . ' caracteres<br>';
echo 'Se han leído ' . mb_strlen($datosLeidos) . ' caracteres<br>';
?>

Y vemos que, por pantalla, aparece ahora lo siguiente:

Línea 1|Dato 1-1|Dato 1-2|Dato 1-3 Línea 2|Dato 2-1|Dato 2-2|Dato 2

Se han leído 70 caracteres
Se han leído 68 caracteres

¿Qué ha ocurrido? La función strlen nos indica que se han leído 70 caracteres, sin embargo la función mb_strlen nos indica que se han leído 68 caracteres lo que, para nosotros, es más “real”. ¿Por qué sucede esto? La función strlen no es capaz de leer caracteres que se representan con más de un byte, y en el texto leído tenemos dos caracteres que tienen más de un byte, concretamente la i acentuada en la palabra Leído.

Como fread lee hasta el número de bytes que le hemos indicado, para esta función la i acentuada (y otros caracteres similares) realmente ocupan dos bytes, por lo que, a nuestros ojos, no ha leído lo que hemos querido que lea, que son 70 caracteres. Por ello, esta función puede que no nos sea útil para estos menesteres. Además, leyendo así tendríamos que saber exactamente cuántos bytes leer cada vez, lo que lo hace una incomodidad o una imposibilidad ante líneas con longitudes diferentes.

Obtener el tamaño del fichero con filesize

Una forma de que la función fread nos pueda ser más útil es sabiendo el tamaño del fichero. Para ello, utilizamos la función filesize a la cual le indicamos el nombre del fichero y nos devolverá el tamaño, en bytes, del fichero. Probemos ahora con este código:

<?php
$nombreFichero = 'fichero.txt';
$fichero = fopen($nombreFichero, 'rb');

$datosLeidos = fread($fichero, filesize($nombreFichero));

fclose($fichero);

echo $datosLeidos . '<br><br>';
echo 'Se han leído ' . strlen($datosLeidos) . ' caracteres<br>';
echo 'Se han leído ' . mb_strlen($datosLeidos) . ' caracteres<br>';
echo 'El tamaño del fichero es de ' . filesize($nombreFichero) . ' bytes';
?>

Que nos muestra por pantalla lo siguiente:

Línea 1|Dato 1-1|Dato 1-2|Dato 1-3 Línea 2|Dato 2-1|Dato 2-2|Dato 2-3 Línea 3|Dato 3-1|Dato 3-2|Dato 3-3 Línea 4|Dato 4-1|Dato 4-2|Dato 4-3

Se han leído 146 caracteres
Se han leído 142 caracteres
El tamaño del fichero es de 146 bytes

Como podemos ver, la longitud de $datosLeidos para strlen es de 146 caracteres, que coincide con el tamaño que filesize nos ha indicado que tiene el fichero. Sin embargo, mb_strlen nos da 142 caracteres, pues hay cuatro letras acentuadas. Dependiendo de lo que queramos, será mejor una u otra forma de contar los caracteres. La cuestión es que ahora hemos conseguido leer el fichero entero gracias a fread sabiendo el tamaño máximo del fichero con filesize.

file_get_contents para leer todo el fichero

Pese a todo lo comentado anteriormente, en realidad tenemos un método que nos devuelve el contenido del fichero entero en un string. Este método se llama file_get_contents y únicamente le tenemos que pasar el nombre del fichero (no hace falta abrirlo con fopen, pues ya se encarga esta función ella sola de todo el proceso).

Se indica que, para leer ficheros, obtener todos los datos y guardarlos en un string, se utilice file_get_contents y no se utilice fread, pues es más eficiente la primera.

fgets para leer una línea del fichero

La función fgets lee una línea de un fichero. Es decir, leerá todos los caracteres que haya en el fichero hasta que ocurra una de las dos siguientes situaciones: o encuentra una nueva línea o ha llegado al final del fichero. Veamos un comportamiento inicial:

<?php
$fichero = fopen('fichero.txt', 'r');

$linea1 = fgets($fichero);
$linea2 = fgets($fichero);
$linea3 = fgets($fichero);
$linea4 = fgets($fichero);

fclose($fichero);

echo $linea1 . '<br>';
echo $linea2 . '<br>';
echo $linea3 . '<br>';
echo $linea4 . '<br><br>';

echo 'La línea 1 tiene ' . strlen($linea1) . ' caracteres<br>';
echo 'La línea 1 tiene ' . mb_strlen($linea1) . ' caracteres<br><br>';
?>

Lo cual nos muestra por pantalla lo siguiente:

Línea 1|Dato 1-1|Dato 1-2|Dato 1-3
Línea 2|Dato 2-1|Dato 2-2|Dato 2-3
Línea 3|Dato 3-1|Dato 3-2|Dato 3-3
Línea 4|Dato 4-1|Dato 4-2|Dato 4-3

La línea 1 tiene 37 caracteres
La línea 1 tiene 36 caracteres

Parece que ha ido bien. Cada llamada a fgets ha ido leyendo una línea distinta como podemos ver después cuando las hemos ido imprimiendo una a una. Pero, un momento, ¿de verdad una línea tiene 37 o 36 caracteres como nos indican luego strlen y mb_strlen? Si contamos manualmente los caracteres veremos que no, que tienen menos. ¿Qué ha ocurrido esta vez? La función fgets ha leído también el salto de línea, lo que suma dos nuevos caracteres a la línea leída los cuales, probablemente, no nos van a interesar cuando procesemos esa información. Probemos a quitarlos con la función trim, que elimina espacios y caracteres no imprimibles delante y detrás.

<?php
$fichero = fopen('fichero.txt', 'r');

$linea1 = fgets($fichero);
$linea2 = fgets($fichero);
$linea3 = fgets($fichero);
$linea4 = fgets($fichero);

fclose($fichero);

echo $linea1 . '<br>';
echo $linea2 . '<br>';
echo $linea3 . '<br>';
echo $linea4 . '<br><br>';

echo 'La línea 1 tiene ' . strlen($linea1) . ' caracteres<br>';
echo 'La línea 1 tiene ' . mb_strlen($linea1) . ' caracteres<br><br>';

echo 'La línea 1 tiene ' . strlen(trim($linea1)) . ' caracteres<br>';
echo 'La línea 1 tiene ' . mb_strlen(trim($linea1)) . ' caracteres';
?>

Por pantalla veremos lo siguiente:

Línea 1|Dato 1-1|Dato 1-2|Dato 1-3
Línea 2|Dato 2-1|Dato 2-2|Dato 2-3
Línea 3|Dato 3-1|Dato 3-2|Dato 3-3
Línea 4|Dato 4-1|Dato 4-2|Dato 4-3

La línea 1 tiene 37 caracteres
La línea 1 tiene 36 caracteres

La línea 1 tiene 35 caracteres
La línea 1 tiene 34 caracteres

Parece que ahora sí, al haber eliminado el salto de línea que nos molestaba, ya tenemos las líneas guardadas en nuestras variables sin cosas raras que no queremos. Sin embargo, es incómodo tener que colocar una instrucción nueva para cada lectura de línea, por no decir que debemos saber entonces cuántas líneas tiene el fichero. Vamos, pues, a incluirlo dentro de un bucle.

Utilizando un bucle WHILE para leer línea a línea

Una solución fácil para leer ficheros es utilizar un bucle WHILE para ir leyendo línea por línea y, por ejemplo, guardarnos las líneas en un array para tenerlas todas agrupadas. Aquí tenemos el código:

<?php
$fichero = fopen('fichero.txt', 'r');

$lineas = array();
while ($linea = fgets($fichero)) {
	$lineas[] = trim($linea);
}

fclose($fichero);

echo '<pre>';
var_dump($lineas);
echo '</pre>';
?>

Creo que esta condición en el WHILE no la hemos visto todavía. En la propia condición del bucle estamos asignando a $linea el string que obtiene fgets. La función devuelve la línea y, como vimos en la transformación de tipos, cualquier string no vacío se considera true, por tanto la condición del WHILE es válida y se ejecuta. Cuando llegue al final del fichero, al no haber leído nada, el string será la cadena vacía y el WHILE se detendrá al ser una condición falsa.

Utilizamos la etiqueta <pre> para imprimir texto preformateado. Esto permite que var_dump muestre el texto mejor estructurado y será más cómodo leerlo. Así pues, por pantalla vemos lo siguiente:

array(4) {
  [0]=>
  string(35) "Línea 1|Dato 1-1|Dato 1-2|Dato 1-3"
  [1]=>
  string(35) "Línea 2|Dato 2-1|Dato 2-2|Dato 2-3"
  [2]=>
  string(35) "Línea 3|Dato 3-1|Dato 3-2|Dato 3-3"
  [3]=>
  string(35) "Línea 4|Dato 4-1|Dato 4-2|Dato 4-3"
}

Como podemos observar, nuestro array $lineas contiene ahora cuatro elementos, uno por cada línea del fichero. Así, si el fichero creciera o decreciera, no importaría en cuanto al código, pues leeríamos todas las líneas disponibles hasta llegar al final del fichero.

feof o saber cuándo hemos llegado al final del fichero

Nuestro programa anterior también podría haber hecho uso de la función feof. Esta función nos indica si se ha llegado al final del fichero, por lo que también podríamos haber escrito el siguiente código, en el cual evaluamos la condición de que el WHILE continúe si no se ha llegado al final del fichero, y hubiera sido totalmente válido:

<?php
$fichero = fopen('fichero.txt', 'r');

$lineas = array();
while (!feof($fichero)) {
	$linea = fgets($fichero);

	$lineas[] = trim($linea);
}

fclose($fichero);

echo '<pre>';
var_dump($lineas);
echo '</pre>';
?>

Obtener un solo carácter

Tenemos la opción, también, de utilizar la función fgetc, la cual obtiene un solo carácter. Veamos qué ocurre con el siguiente código:

<?php
$fichero = fopen('fichero.txt', 'r');

$caracteres = array();
while ($caracter = fgetc($fichero)) {
	$caracteres[] = trim($caracter);
}

fclose($fichero);

echo '<pre>';
var_dump($caracteres);
echo '</pre>';
?>

Un trozo de lo que nos muestra por pantalla (pues todo es bastante extenso) es lo siguiente:

array(146) {
  [0]=>
  string(1) "L"
  [1]=>
  string(1) "�"
  [2]=>
  string(1) "�"
  [3]=>
  string(1) "n"
  [4]=>
  string(1) "e"
  [5]=>
  string(1) "a"
  [6]=>
  string(0) ""
  [7]=>
  string(1) "1"
  [8]=>
  string(1) "|"
  [9]=>
  string(1) "D"
  [10]=>
  string(1) "a"
  [11]=>
  string(1) "t"
  [12]=>
  string(1) "o"
  [13]=>
  string(0) ""
  [14]=>
  string(1) "1"
  [15]=>
  string(1) "-"
  [16]=>
  string(1) "1"
  [17]=>
  string(1) "|"
  [18]=>
  string(1) "D"
  [19]=>
  string(1) "a"
  [20]=>
  string(1) "t"
  [21]=>
  string(1) "o"
  [22]=>
  string(0) ""
  [23]=>
  string(1) "1"
  [24]=>
  string(1) "-"
  [25]=>
  string(1) "2"
  [26]=>
  string(1) "|"
  [27]=>
  string(1) "D"
  [28]=>
  string(1) "a"
  [29]=>
  string(1) "t"
  [30]=>
  string(1) "o"
  [31]=>
  string(0) ""
  [32]=>
  string(1) "1"
  [33]=>
  string(1) "-"
  [34]=>
  string(1) "3"
  [35]=>
  string(0) ""
  [36]=>
  string(0) ""
  [37]=>
  string(1) "L"
  [38]=>
  string(1) "�"
  [39]=>
  string(1) "�"
  [40]=>
  string(1) "n"
  [41]=>
  string(1) "e"
  [42]=>
  string(1) "a"
  [43]=>
  string(0) ""
  [44]=>
  string(1) "2"
...

Analicemos lo que ocurre. Es evidente que fgetc hace lo que se esperaba, es decir, leer ficheros carácter a carácter, pero vemos ahora que lo que realmente hace es leer byte a byte. ¿Qué nos puede llamar la atención? El acento en la í nos lo ha leído y guardado como dos elementos y nos muestra un símbolo extraño que el navegador no sabe cómo imprimir.

Del mismo modo, si nos fijamos cuando acaba la primera línea y empieza la segunda, tenemos ahí dos elementos totalmente vacíos que corresponderían al salto de línea entre las líneas. Por tanto, hay que tener mucho cuidado cuando leamos de esta forma o podemos obtener resultados no esperados. Para este caso concreto no nos serviría.

Movernos por el fichero

Leer las líneas del fichero está muy bien y es muy útil pero, ¿y si queremos movernos por el fichero? Para ello tenemos varias funciones como, por ejemplo, fseek, ftell o rewind.

Con fseek podemos movernos por el fichero a voluntad, siempre y cuando sepamos el número de bytes que queremos. Por ejemplo, si sabemos que la primera fila tiene 37 bytes como hemos visto, podríamos hacer un fseek($fichero, 37) y nos moveríamos al principio de la segunda línea.

Igualmente, podríamos hacer un fseek($fichero, 0) para movernos nuevamente al principio del fichero, pero este método tiene una función ya definida para ello de nombre rewind a la que sólo con pasarle el fichero ya nos lo mueve al principio del mismo.

Por último, y aunque no sirva para moverse, podemos utilizar ftell para saber en qué posición del fichero nos encontramos en cada momento. Todas estas funciones las veremos en un próximo artículo dedicado a funciones útiles sobre ficheros.

Cómo leer cada dato individual

Hemos leído líneas, caracteres, aprendido cómo movernos por el fichero…, pero tenemos una línea con varios datos y queremos obtenerlos. ¿Cómo lo hacemos? Aunque no sea propiamente lectura de ficheros, sí que debemos tratar este tema. Si os fijáis, hemos dividido los datos de cada línea con un carácter | a propósito, pues ahora será fácil dividir la línea y obtenerlos por separado utilizando la función explode de la siguiente forma:

<?php
$fichero = fopen('fichero.txt', 'r');

$lineas = array();
while($linea = fgets($fichero)) {
	$lineas[] = explode('|', trim($linea));
}

fclose($fichero);

echo '<pre>';
var_dump($lineas);
echo '</pre>';
?>

Lo que nos da la siguiente visualización:

array(4) {
  [0]=>
  array(4) {
    [0]=>
    string(8) "Línea 1"
    [1]=>
    string(8) "Dato 1-1"
    [2]=>
    string(8) "Dato 1-2"
    [3]=>
    string(8) "Dato 1-3"
  }
  [1]=>
  array(4) {
    [0]=>
    string(8) "Línea 2"
    [1]=>
    string(8) "Dato 2-1"
    [2]=>
    string(8) "Dato 2-2"
    [3]=>
    string(8) "Dato 2-3"
  }
  [2]=>
  array(4) {
    [0]=>
    string(8) "Línea 3"
    [1]=>
    string(8) "Dato 3-1"
    [2]=>
    string(8) "Dato 3-2"
    [3]=>
    string(8) "Dato 3-3"
  }
  [3]=>
  array(4) {
    [0]=>
    string(8) "Línea 4"
    [1]=>
    string(8) "Dato 4-1"
    [2]=>
    string(8) "Dato 4-2"
    [3]=>
    string(8) "Dato 4-3"
  }
}

Como podemos ver, ahora tenemos todos los datos organizados en un array en el que cada línea es un nuevo array con sus datos separados en cada uno de sus elementos. Hemos conseguido lo que queríamos gracias a explode, a la cual le hemos pasado el carácter separador | y nos ha devuelto un array con tantos elementos como tenía esa línea separados por el carácter separador indicado. Así sí es útil leer ficheros.

Podríamos tener, por tanto, un fichero en el que cada línea fuera el nombre, descripción y precio de un artículo, por ejemplo, y leerlo para obtener los artículos. Al hacerle el explode tendríamos un array con cada artículo (que sería una línea) y sus datos en tres elementos del array. Vamos, lo que sería una pequeña y simple base de datos.

Resumen

Después de haber visto cómo leer ficheros con unas cuantas funciones y todos los problemas y cosas a tener en cuenta de las que debemos ser conscientes, ya estamos capacitados para obtener los datos que nos importan de los ficheros y poder manejarlos.

En el siguiente artículo pasaremos a ver cómo escribir datos en los ficheros para así poder persistir cualquier dato que hayamos obtenido con nuestros programas.

Deja una respuesta