En este tutorial vamos a hacer dbf, una herramienta de consola escrita en Visual FoxPro que mira y exporta tablas sin abrir VFP:
dbf info customers.dbf
dbf campos customers.dbf --format json
dbf exportar customers.dbf clientes.csvTiene tres comandos, una ayuda que sale sola, errores que explican qué pasa y códigos de salida que un script puede leer. Se reparte copiando una carpeta, en un equipo sin Visual FoxPro instalado. Y todo el código es un .prg de VFP de 76 líneas.
Trabajamos con customers.dbf, la tabla de clientes de los datos de ejemplo Northwind que trae VFP, así que lo que ves en las capturas lo puedes repetir tal cual. dbf solo lee: nunca escribe en la tabla.
Si la consola te queda lejos, antes de empezar tienes el curso CLIs para foxeros: qué es una orden, qué son los argumentos y las opciones y qué es un código de salida. Aquí doy todo eso por sabido.
Lo que necesitas
- Visual FoxPro 9 (o VFP Advanced).
- FoxCli instalado. Trae FoxForge, el taller que se engancha al IDE de VFP. Sin licencia funciona igual; la diferencia la vemos al final.
- Windows 10 u 11.
Abre VFP como siempre, sin «Ejecutar como administrador». Al compilar como administrador, VFP registra el componente en HKLM, y ese registro estorba a las compilaciones siguientes. Y no uses el botón Build del Administrador de Proyectos: compila con Ctrl+F7, que es el que deja la CLI completa.
Paso 1. Cargar FoxForge
En la ventana de comandos de VFP:
DO "C:\Program Files (x86)\FoxForge\FoxForge.app"Cambia la ruta si instalaste FoxForge en otra carpeta. Aparece un menú nuevo en la barra de VFP, FoxForge, y desde ahí se hace todo lo demás.
Paso 2. Crear el proyecto
FoxForge > Nuevo proyecto > Aplicación de consola (CLI). En el asistente:
- Nombre:
dbf. Es el nombre del programa, el que se escribe en la consola. - Carpeta: la tuya de proyectos. Yo he usado
C:\a1\tutorial-b. - Plantilla mínima, sin comentarios: marcada. La otra plantilla trae el código explicado línea a línea, que está bien para leerla una vez, pero hoy vamos a escribir el nuestro.

Pulsa Crear. FoxForge crea el proyecto y lo abre en el Administrador de Proyectos:
dbf\
├── dbf.pjx el proyecto de VFP
├── foxforge.json la marca de proyecto CLI
├── pack.ps1 empaqueta lo que se reparte
├── src\
│ ├── main.prg tus comandos: lo único que editas
│ ├── foxcli.h los códigos de salida con nombre
│ └── foxcli.prg la clase base, que es de FoxCli
└── dist\ aquí sale el .exe al compilarPaso 3. Lo que trae la plantilla
Abre src\main.prg:
#INCLUDE foxcli.h
*!* dbf: describe aquí lo que hace tu CLI
DEFINE CLASS DbfCommand AS FoxCliCommand OF foxcli.prg OLEPUBLIC
*!* Saluda a alguien
*!* @tcNombre -n A quién saludar
PROCEDURE Saludar(tcNombre AS String)
Console.WriteLine("Hola " + tcNombre + "!")
ENDPROC
ENDDEFINECon esto ya se ve cómo funciona todo:
- Cada método público de la clase es un comando.
Saludarse usa comodbf saludar. - Cada parámetro es una opción, con el nombre sin el prefijo:
tcNombrees--nombre. El tipo sale delAS. - El bloque
*!*de encima es la documentación. La primera línea es la descripción del comando, y cada línea@anota un parámetro:-nle da un alias corto y el resto es su descripción. De ahí sale la ayuda. Consolees la consola.Console.WriteLine()escribe por la salida yConsole.Error(), por el canal de error.
Paso 4. Escribir los comandos
Sustituye el contenido de main.prg por esto:
#INCLUDE foxcli.h
*!* dbf: mira y exporta tablas de VFP desde la consola, sin abrir Visual FoxPro
*!* @exit 10 La tabla no existe
DEFINE CLASS DbfCommand AS FoxCliCommand OF foxcli.prg OLEPUBLIC
*!* Cuántos registros y campos tiene una tabla
*!* @tcTabla <> La tabla (.dbf)
*!* @example dbf info customers.dbf
PROCEDURE Info(tcTabla AS String)
IF !THIS.Abrir(tcTabla)
RETURN EXIT_NO_TABLE
ENDIF
Console.WriteLine("Registros: " + TRANSFORM(RECCOUNT()))
Console.WriteLine("Campos: " + TRANSFORM(FCOUNT()))
Console.Write("Modificada: ")
Console.WriteLine(LUPDATE())
USE
ENDPROC
*!* La estructura de una tabla: nombre, tipo y ancho de cada campo
*!* @tcTabla <> La tabla (.dbf)
*!* @example dbf campos customers.dbf --format json
PROCEDURE Campos(tcTabla AS String)
LOCAL laCampos[1], lnCampos, lnI, lcJson
IF !THIS.Abrir(tcTabla)
RETURN EXIT_NO_TABLE
ENDIF
lnCampos = AFIELDS(laCampos)
USE
IF Console.IsJson()
lcJson = ""
FOR lnI = 1 TO lnCampos
lcJson = lcJson + IIF(lnI > 1, ",", "") + ;
'{"nombre":"' + LOWER(laCampos[lnI, 1]) + '","tipo":"' + laCampos[lnI, 2] + ;
'","ancho":' + TRANSFORM(laCampos[lnI, 3]) + '}'
ENDFOR
Console.WriteLine("[" + lcJson + "]")
ELSE
FOR lnI = 1 TO lnCampos
Console.WriteLine(PADR(LOWER(laCampos[lnI, 1]), 14) + laCampos[lnI, 2] + ;
STR(laCampos[lnI, 3], 5))
ENDFOR
ENDIF
ENDPROC
*!* Copia una tabla a un fichero CSV o XLS
*!* @tcTabla <> La tabla (.dbf)
*!* @tcDestino <> El fichero que se crea
*!* @tcFormato :csv|xls =csv Formato del fichero
*!* @example dbf exportar customers.dbf clientes.csv
*!* @example dbf exportar customers.dbf clientes.xls --formato xls
PROCEDURE Exportar(tcTabla AS String, tcDestino AS String, tcFormato AS String)
IF !THIS.Abrir(tcTabla)
RETURN EXIT_NO_TABLE
ENDIF
IF tcFormato == "xls"
COPY TO (tcDestino) TYPE XL5
ELSE
COPY TO (tcDestino) TYPE CSV
ENDIF
Console.Error(TRANSFORM(RECCOUNT()) + " registros copiados a " + tcDestino)
USE
ENDPROC
*-- Abre la tabla solo para leer. Si no existe, lo dice por el canal de error.
PROTECTED FUNCTION Abrir(tcTabla)
IF !FILE(FORCEEXT(tcTabla, "dbf"))
Console.Error("dbf: no encuentro la tabla " + tcTabla)
RETURN .F.
ENDIF
USE (FORCEEXT(tcTabla, "dbf")) AGAIN SHARED NOUPDATE ALIAS tabla
RETURN .T.
ENDFUNC
ENDDEFINEY añade al final de src\foxcli.h el código de salida propio:
#DEFINE EXIT_NO_TABLE 10 && la tabla no existeLo que hay que mirar en ese código:
<>hace un posicional.@tcTabla <>dice que la tabla se escribe sin nombre delante:dbf info customers.dbf, y nodbf info --tabla customers.dbf.:csv|xls =csves una lista cerrada con valor por defecto.--formatosolo aceptacsvoxls, y si no se escribe valecsv. De validarlo se encarga FoxCli, antes de llegar a tu código.- Lo que devuelve el método es el código de salida. Sin
RETURN, el comando acaba con 0.RETURN EXIT_NO_TABLEacaba con 10, y el@exit 10de arriba lo documenta. Los tuyos van del 10 al 125: los números bajos son de FoxCli. Console.IsJson()dice si quien llama ha pedido--format json. Esa opción no la declaras tú: la trae FoxCli en todos los comandos.- El resultado va por la salida y los mensajes por el error.
exportaravisa de cuántos registros ha copiado conConsole.Error(), así que si alguien redirige su salida a un fichero, el aviso no se mezcla. AbriresPROTECTED: un método auxiliar como cualquier otro, que no sale en la lista de comandos. Por eso su comentario va con el*--de siempre:*!*es para lo que sale en la ayuda de la CLI, y a un método auxiliar no se le llama desde la consola.Console.WriteLine(LUPDATE())escribe la fecha en formato ISO (2004-12-09) en cualquier equipo, sin depender deSET DATE. La salida de una CLI la leen otros programas, y tiene que ser igual en todas partes.
Paso 5. Compilar
FoxForge > Proyecto actual > Compilar CLI, o Ctrl+F7. Sin licencia de FoxCli, sale este aviso:

Ya tienes dist\dbf.exe. De paso, FoxForge ha generado la documentación en docs\dbf.md, con la descripción de cada comando sacada de los bloques *!*.
Paso 6. Probarla desde el IDE
Antes de abrir una consola, se puede probar sin salir de VFP. FoxForge > Proyecto actual > Argumentos…, o Shift+F8, y escribe la línea como la escribirías en una consola, sin el nombre del programa:

Se ejecuta dentro de dist\, así que aquí va la ruta completa de la tabla. Ejecutar (o Ctrl+F8 la próxima vez, que recuerda los argumentos) abre una consola con el resultado, y debajo el código de salida:

Si algo falla, FoxForge > Proyecto actual > Banco de pruebas ejecuta un comando desde tu fuente, con el depurador de VFP delante y sin compilar.
Paso 7. Repartirla y usarla desde la consola
En la carpeta del proyecto, pack.ps1 arma un zip con lo que hace falta para ejecutar la CLI y nada más. Desde una consola en esa carpeta:
powershell -ExecutionPolicy Bypass -File pack.ps19 ficheros -> C:\a1\tutorial-b\dbf\dbf-dist.zip (3.634.677 bytes)
Se despliega descomprimiendo: no hace falta registrar nada ni ser administrador.Esos 9 ficheros son tu CLI y el runtime de VFP 9, para que funcione en un equipo sin Visual FoxPro. Descomprímelos donde quieras (yo, en una carpeta de herramientas) y ya está instalada. No se registra nada, así que tampoco hay que desinstalar: se borra la carpeta.
Para no tocar la carpeta de VFP, copia los datos de Northwind a tu carpeta de pruebas. Ojo: la carpeta entera, porque customers.dbf pertenece a la base de datos northwind.dbc:

Entra en la copia y añade la carpeta de dbf al PATH de esa consola, para escribir dbf sin la ruta:
cd C:\pruebas-cli\northwind
set PATH=%PATH%;C:\herramientas\dbf(Cambia C:\herramientas\dbf por la carpeta donde lo descomprimiste. Ese set vale solo para esa ventana.)
La ayuda la has escrito sin darte cuenta, en los bloques *!*:

describe también lo trae FoxCli: vuelca en JSON todos los comandos con sus opciones, para que un agente de IA descubra la CLI sin leer nada más.
Si te falta un argumento, dbf no llega a ejecutar tu código: dice qué falta, enseña la ayuda del comando y sale con 2. Con la tabla, da el resultado:

campos saca la estructura en una tabla que se lee en pantalla:

Y con --format json, en una línea que otro programa puede leer:
dbf campos customers.dbf --format json[{"nombre":"customerid","tipo":"C","ancho":5},{"nombre":"companyname","tipo":"C","ancho":40},{"nombre":"contactname","tipo":"C","ancho":30},...,{"nombre":"fax","tipo":"C","ancho":24}]exportar deja el fichero y avisa por el canal de error. Con una tabla que no existe, sale con el 10 que declaramos:

El CSV sale con las tildes en su sitio (Avda. de la Constitución 2222, México D.F.), porque lo escribe VFP con la página de códigos de la tabla.
Y un formato que no está en la lista no llega a tu código: FoxCli dice cuáles valen, enseña la ayuda de exportar y sale con 2.

Una tabla lejos de su base de datos. Si copias solo customers.dbf a otra carpeta, sin northwind.dbc, VFP intentará preguntarte dónde está la base de datos. En una CLI no hay nadie a quien preguntar, así que dbf sale con código 1 y el error de VFP por el canal de error («Operación de interfaz de usuario no permitida en este momento», el 2031). No se queda colgada esperando una respuesta: FoxCli nunca deja un diálogo abierto.
Paso 8. La licencia
Sin licencia, la CLI funciona igual y sin fecha de caducidad. Lo único que cambia es la línea que has visto al final de cada salida:
Hecho con FoxCli (evaluación) - irwinrodriguez.dev/foxcliVa por el canal de error, así que no se mete en un CSV ni en un JSON aunque redirijas la salida. Con una licencia de FoxCli cargada en FoxForge > Licencia de FoxCli…, Ctrl+F7 sella la CLI con tu nombre: la línea desaparece, y --version dice a quién está licenciada.
dbf --versiondbf (FoxCli 1.3.0, con licencia de Irwin Rodríguez)Las CLIs que compilas las repartes gratis y a quien quieras: la licencia es del programador que compila, no de cada copia.
Y ahora qué
- La guía de FoxCli está en FoxForge > Ayuda > Guía de FoxCli: leer la entrada, las fechas, el formato de salida, la cláusula
HELPy todo lo que hoy no ha salido. - La documentación de tu CLI la tienes en
docs\dbf.md, y se regenera en cada compilación: sirve tal cual como README. - Un ejemplo grande: FoxPack, el gestor de librerías para proyectos VFP, es una CLI hecha con FoxCli, y su código está publicado.
- Ideas para seguir con
dbf: un comandocontarcon un filtro (--donde "country = 'Spain'"), o queexportaracepte--campospara elegir las columnas.