Universidad de Costa Rica
Escuela de Ciencias de la Computación e Informática
CI-1201 Programación II - 2012b
Profesor Jeisson Hidalgo-Céspedes
Implemente un comando trim
que permita eliminar espacios en blanco superfluos de archivos o la entrada estándar. Su programa deberá comportarse como un comando normal de Unix. Si se invoca con el parámetro --help
, brindará ayuda al usuario:
$ trim --help Usage: trim [-ailrtW] [FILES] Writes concatenation of all FILES to stardard output without redundant leading, trailing or inside whitespace in each line. Options: -a removes all whitespace; equals -lri -i removes inside whitespace -l removes left whitespace -r removes right whitespace (default) -t removes left and right whitespace; equals -lr -W overwrites each original FILE instead of writing to standard output $
Su programa deberá hacer un análisis inicial de los parámetros provistos por el usuario, los cuales se pueden agrupar en cuatro tipos:
-a -i -l -r -t
. Si ninguno se especifica se debe asumir -r
.-W
que indica sobrescribir los archivos de entrada.--help
para proveer ayuda.El análisis de parámetros incluye las siguientes consideraciones. Si se especifica --help
, el programa debe imprimir ayuda en la salida estándar indiferentemente de cuáles otros parámetros se hayan provisto. Si se provee un parámetro no válido, se debe imprimir un mensaje en el error estándar. Si se provee el parámetro -W
sin especificar ningún nombre de archivo, será reportado en el error estándar. En cualquiera de estos casos el programa termina su ejecución inmediatamente. Ejemplos:
$ trim -l -W -r -c trim: -c unknown option $ trim -l -W -r trim: -W option and no files were specified $
Si no se especifican nombres de archivos se asume que el texto será provisto en la entrada estándar y por ende, el resultado será impreso en la salida estándar. Por el contrario, si se proveen nombres de archivo y no la opción -W
, el resultado de hacer trim en cada línea de ellos será impreso en la salida estándar; pero si la opción -W
fue especificada, el contenido de los archivos será sobrescrito. En cualquiera de estos casos, se dice que el comando tendrá una fuente de datos para trabajar, indiferentemente de si sean archivos o la entrada estándar; y también un destino de datos donde serán impresos los textos sin espaciado redundante. Sugerencia: note que las funciones fgets()
, fputs()
y fprintf()
pueden trabajar tanto en archivos como la entrada y salida estándar, simplemente cambiando el parámetro FILE*
.
El programa debe trabajar a nivel de línea. Es decir, el comando lee líneas de la fuente de datos hasta encontrar el carácter fin de archivo. Por cada línea obtenida, imprimirá en el destino de datos una línea resultado de hacerle el tipo de trim escogido por el usuario. Nótese que cada archivo resultante tendrá la misma cantidad de líneas que el respectivo archivo original.
Si la opción -W
es especificada, su programa debe abrir cada archivo por parámetro y el resultado escribirlo en un archivo temporal. Puede utilizar el mismo nombre del archivo original concatenándole la extensión .tmp
ó .trim
. Para efectos de esta tarea asuma que este archivo no existe, y si existiese, será truncado sin aviso. Una vez terminado el procesamiento, el archivo original es eliminado con la función remove()
, y el archivo temporal es renombrado al original con la función rename()
, ambas de la biblioteca <stdio.h>
.
Se considera como espaciado los caracteres: espacio en blanco (' '
), tabulador ('\t'
), cambio de línea ('\n'
), retorno de carro ('\r'
), tabulador vertical ('\v'
), y avance de página ('\f'
). La función isspace(ch)
de <ctype.h>
retorna un valor distinto de 0 si su argumento es uno de estos caracteres considerados espacios, y el valor 0 cuando es otro carácter.
El left trim elimina el espaciado completo que aparece al inicio de la cadena, antes del primer carácter que no es espacio. Si se provee una cadena que consta únicamente de espaciado, el resultado será la cadena vacía. Por ejemplo:
char test1[] = "\t\t Test 1\t\t"; fprintf(stdout, "[%s]\n", trimLeft(test1)); // Imprime "[Test 1\t\t]" char test2[] = "\t\t \t\t"; fprintf(stdout, "[%s]\n", trimLeft(test2)); // Imprime "[]"
El right trim elimina el espaciado completo que aparece al final de la cadena, después del último carácter que no es espacio. Si se provee una cadena que consta únicamente de espaciado, el resultado será la cadena vacía. Por ejemplo:
char test1[] = "\t\t Test 1\t\t"; fprintf(stdout, "[%s]\n", trimRight(test1)); // Imprime "[\t\t Test 1]" char test2[] = "\t\t \t\t"; fprintf(stdout, "[%s]\n", trimRight(test2)); // Imprime "[]"
El inner trim o trim inside reemplaza cada espacio redundante dentro de la cadena por un espacio simple. Su área de trabajo es a partir del primer carácter que no es espacio hasta el último carácter que no es espacio. Si se provee una cadena que consta únicamente de espaciado, o de una única palabra, el trim inside no tendrá efecto. Por ejemplo:
char test1[] = "\t\t Test 1\t\t"; fprintf(stdout, "[%s]\n", trimInside(test1)); // Imprime "[\t\t Test 1\t\t]" char test2[] = "\t\t \t\t"; fprintf(stdout, "[%s]\n", trimInside(test2)); // Imprime "[\t\t \t\t]"
Diseñe su solución para que cada una de estas funciones realice a lo sumo un único recorrido por la cadena que recibe. Nótese que utilizar funciones como strlen()
ya realizan un recorrido completo por la cadena. Esta restricción implica que si el usuario invoca su comando con sólo uno de los parámetros -l
, -r
ó -i
, su programa recorrerá una única vez cada línea con el propósito de hacer trim. Si su comando se invoca con dos tipos de trim (-i -r
, -t
, ...), su programa recorrerá a lo sumo dos veces cada línea para quitar espacio redundante. Si su comando se invoca para hacer los tres tipos de trim (-l -i -r
ó -a
), cada línea será recorrida a lo sumo tres veces para quitar espacio redundante.
La diversidad de líneas a las que se puede enfrentar su comando es infinita. Es imposible probar todas las potenciales cadenas contra todos los posibles parámetros de su comando. Sin embargo, se puede identificar unos casos representativos y probar contra ellos. Su programa debe pasar al menos un conjunto de pruebas provistas por el profesor. Para ello el estudiante debe apegarse a las siguientes declaraciones o prototipos:
// Removes redundant whitespace at beginning of line. Returns line char* trimLeft(char* line); // Removes redundant whitespace at ending of line. Returns line char* trimRight(char* line); // Reduces redundant whitespace from the first no space character to the last no space character // in line. Each sequence of whitespace is replaced with a single space. Returns line char* trimInside(char* line);
Para someter a prueba su solución, declare las funciones anteriores en un archivo trim.h
(opcionalmente puede incluir las implementaciones en un archivo trim.c
). Luego descargue el programa de pruebas test.c
y almacénelo en la misma carpeta donde se encuentra trim.h
. Finalmente compile test.c
y ejecute la aplicación resultante (pueda que tenga que incluir trim.c
en el proyecto de prueba). Si al ejecutar el programa de prueba no genera ninguna salida, la implementación de sus funciones trim habrán pasado la prueba. Si por el contrario, ve salida en el error estándar, es porque el programa de prueba habrá encontrado errores. Por ejemplo:
$ gcc -Wall -o test test.c trim.c && ./test 4. 0 1 1 [No left. Inside white space. Right] => {No left. Inside white space. Right} 10. 0 0 1 [] => { } $
La salida anterior indica que el programa falló la prueba 4 y 10. Los números 0 y 1 en las tres columnas siguientes indican los tipos de trim aplicados, en orden: left trim, inner trim y right trim. De esta forma, la prueba 4 falló al aplicar right trim e inner trim, y la prueba 10 falló al aplicar sólo right trim. Para ver el texto original enviado a las funciones, estúdiese el código fuente de test.c
. El texto entre corchetes ([]
) era el resultado esperado; mientras que el texto entre llaves ({}
) fue el generado por las funciones de trim invocadas.
--help
indiferentemente de los demás parámetros. Imprime error ante una opción no válida o ante un -W
sin archivos.-W
, el comando lee líneas de cada uno de ellos en orden e imprime resultados en la salida estándar.-W
, lee líneas de cada uno de ellos en orden e imprime resultados en archivos temporales que luego reemplazan a los originales. Si hay algún error con el manejo de archivos (no existen, no hay espacio en disco, etc.) se reportan en el error estándar. Cierra archivos tan pronto como se dejen de utilizar.fgets()
maneja adecuadamente cambios de línea al final de las cadenas devueltas. Si utiliza memoria dinámica no provoca fugas de memoria.trimLeft()
, trimRight()
y trimInside()
trabajan correctamente, no acceden a memoria no permitida, hacen su trabajo en un único recorrido por la cadena. Retornan un puntero hacia el inicio de la cadena original. Pasan al menos todos los casos de prueba provistos por el profesor.trim -lrW file.txt
equivale a trim -l -r -W file.txt
.fgets()
incrementando el tamaño del buffer hasta que encuentre el final de línea.Discusión: ¿Se puede hacer que su programa trabaje un carácter a la vez?. Es decir, obtenga un carácter de la fuente de datos y determine si ese carácter debe: escribirse en el destino de datos, ser reemplazado por otro carácter o ser ignorado. ¿Cuáles son las ventajas y desventajas de este esquema? ¿Se puede utilizar este esquema cuando la fuente de datos es la entrada estándar?.
Para presentar su solución, comprima los archivos fuente que utilizó y súbalos a la plataforma educativa en la asignación con nombre Tarea03
.