Lo hice y lo entendí

El blog de Vicente Navarro
15 sep

Ejemplos de scripting en Windows: Añadir la fecha y la hora al nombre de un fichero

Siempre he tenido la idea de que la shell de Windows era terriblemente limitada, no sólo por las funcionalidades propias del CMD (algo mayores que las del viejo COMMAND.COM, en cualquier caso) sino también por la ausencia de esos viejos amigos del intérprete de comandos que tanto facilitan la vida en UNIX: grep, awk, sed, etc. Por eso, entre otras cosas, me gusta tanto el Cygwin, porque me permite tener toda la potencia de la shell de UNIX en Windows.

Pero para ser sincero, hablando de scripting en Windows, he de reconocer que he visto hacer cosas muy interesantes con VBScript e incluso de vez en cuando ves algún script de CMD que te llama mucho la atención, como me pasó hace unos días.

Tenía una aplicación en Windows que iba escribiendo en un fichero de log de esos que van rotando. Lo típico, la aplicación va escribiendo en un fichero logfile.log que cuando llega a un tamaño es renombrado a logfile.log.old reemplazando al anterior con ese mismo nombre y genera un nuevo logfile.log.

Pues bien, yo necesitaba recoger esos .old durante varios días apuntando la hora exacta a la que se había guardado, una tarea trivial en UNIX: Pones un script como el siguiente en el cron para que se ejecute cada minuto y un logfile.log.old se convierte en un logfile.log.200709141929 en un santiamén:

#!/bin/sh
if [ -f logfile.log.old ]
then
   mv logfile.log.old logfile.log.$(date +%Y%m%e%k%M)
fi

En el CMD de Windows esto no es tan sencillo, para empezar porque los comandos date y time de Windows no se puede decir que sean lo más versátil del mundo: ¡Sólo aceptan el parámetro /t!

C:\>date /t
14/09/2007

C:\>time /t
19:51

Una vez más, Google fue mi mejor amigo (GIYBF) y di con el documento: How can I append the date and time to a file?, que a mí me ha parecido muy interesante por el uso que hace del for y de las asignaciones de variables.

Por ejemplo, con las siguientes líneas (con cambios respecto al original):

for /f %%u in ('date /t') do set d=%%u
for /f %%u in ('time /t') do set t=%%u
if "%t:~1,1%"==":" set t=0%t%
set timestr=%d:~6,4%%d:~3,2%%d:~0,2%%t:~0,2%%t:~3,2%
echo %timestr%

podemos obtener una variable timstr que contieneo mismo que nuestro “date +%Y%m%e%k%M” de UNIX, es decir, algo como timestr=200709142022.

La línea:

for /f %%u in ('date /t') do set d=%%u

no tiene otro propósito que almacenar la salida del comando “date /t” en la variable %d% (nótese que la variable que usa el for tiene que llevar dos % si va dentro de un script). Es decir, el equivalente al “u=$('comando')” o “u=`comando`” de bash. Tanto es así que podríamos simular lo que hace ese for en nuestro Cygwin:

vnavarro@DARKSTAR ~
$ d=$(cmd /c date /t); echo $d
14/09/2007

vnavarro@DARKSTAR ~
$ t=`cmd /c time /t`; echo $t
22:53

Luego encontramos varias combinaciones como %t:~1,1% o %d:~6,4%. Significan exactamente lo mismo que significarían ${t:1:1} y ${d:6:4} en bash. Es decir, ${variable:x:y} nos devolvería y caracteres de $variable a partir del caracter x (comenzando la cuenta por 0).

Ejemplo bash:

$ echo $PATH                                                                     
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
$ echo ${PATH:4:7}
/local/

Ejemplo CMD:

C:\>echo %PATH%
C:\WINDOWS\system32;C:\WINDOWS

C:\>echo %PATH:~10,10%
\system32;

Es curioso que he intentado buscar documentación sobre operaciones similares con las variables y no la he conseguido encontrar en ninguna página de Microsoft, sólo en la salida del “set /?“, en la que encontramos este y otros interesantes usos:

May also specify substrings for an expansion.

    %PATH:~10,5%

would expand the PATH environment variable, and then use only the 5
characters that begin at the 11th (offset 10) character of the expanded
result.  If the length is not specified, then it defaults to the

La línea:

if "%t:~1,1%"==":" set t=0%t%

sirve porque el “time /t” puede devolver como hora tanto horas de 4 caracteres (1:34) como horas de 5 caracteres (23:12), de forma que si el segundo carácter (~1,1) es dos puntos (==":") le añadimos un 0 delante para que siempre haya 5 caracteres.

El script en UNIX que necesitábamos al principio quedaría, por tanto, así:

for /f %%u in ('date /t') do set d=%%u
for /f %%u in ('time /t') do set t=%%u
if "%t:~1,1%"==":" set t=0%t%
set timestr=%d:~6,4%%d:~3,2%%d:~0,2%%t:~0,2%%t:~3,2%
if exist lofgile.log.old rename logfile.log.old logfile.log.%timestr%

Ahora bien, esto sólo funciona bien en Windows XP. En Windows NT y Windows 2000 el comando “date /t” pone el día de la semana delante de la fecha:

C:\>date /t
Fri 14/09/2007

En estos casos, es necesario usar un for como el siguiente:

for /f "tokens=1,2" %%u in ('date /t') do set d=%%v

Puesto que los tokens se separan por defecto por espacios o tabuladores, el primer token (Fri) se almacenará en la variable %%u, y el segundo (14/09/2007) en la %%v, de forma que al quedarnos sólo con %%v en la variable %d% tenemos el equivalente al for que usábamos para Windows XP.

Y tenemos más alternativas para hace lo mismo usando tokens en el for y no haciendo extracciones de subcadenas como antes:

for /f "tokens=1,2,3 delims=/ " %%i in ('date /t') do set d=%%k%%j%%i
for /f "tokens=1,2 delims=:" %%i in ('time /t') do set t=%%i%%j
set timestr=%d%%t%

Nótese que para la fecha ponemos como delimitadores la barra (/) y el espacio. Y el espacio como token es necesario porque el date /t parece que pone un espacio al final de su salida, de forma que para ignorarlo tenemos que ponerlo como separador de tokens. En otro caso ocurriría esto:

C:\>for /F "tokens=1,2,3 delims=/" %i in ('date /t') do set d=%k%j%i

C:\>set d=2007 0915

Eso en Windows XP, claro. En Windows 2000 tendríamos que deshacernos del día de la semana, con lo que con una pequeña variación en el número de los tokens que nos quedamos, ya tenemos la adaptación hecha:

for /f "tokens=2,3,4 delims=/ " %%i in ('date /t') do set d=%%k%%j%%i

Por cierto, todos estos for me recuerdan muchísimo al awk. En UNIX, para hacer lo mismo, podríamos haber usado este simple comando:

$ echo 'Fri 15/09/2007 ' | awk -F '[/ ]' '{print $4$3$2}'
20070915

La página de Microsoft Frequently Asked Questions Regarding The Windows 2000 Command Processor, en la pregunta “How do I create a file name or folder name using the current date (YYYYMMDD)?” trata precisamente este tema. Pero además, propone un script para hacer esto mismo independientemente de si estamos en Windows 2000 o Windows XP e independientemente del formato de la fecha, ya que a hasta ahora habíamos asumido dd-mm-yyyy, pero muy bien podría ser mm-dd-yyyy o yyyy-mm-dd. Se basa en que el comando date sin parámetros, además de pedirnos la nueva fecha, nos muestra el formato a usar:

@echo off 
set $tok=1-3 
for /f "tokens=1 delims=.:/-, " %%u in ('date /t') do set $d1=%%u 
if "%$d1:~0,1%" GTR "9" set $tok=2-4 
for /f "tokens=%$tok% delims=.:/-, " %%u in ('date /t') do ( 
for /f "skip=1 tokens=2-4 delims=/-,()." %%x in ('echo.^|date') do ( 
set %%x=%%u 
set %%y=%%v 
set %%z=%%w 
set $d1= 
set $tok=))

El primer for y el if que le sigue sirven para delimitar si hay día de la semana o no. Si lo hay, trabaja con los tokens 2-4 ($tok=2-4), y si no, con los tokens 1-3 ($tok=1-3).

A continuación tenemos dos for anidados. El primero recoge en las variables u, v, w las tres partes de la fecha, estén en el orden que estén. El segundo recoge del comando date sin parámetros el formato de la misma y, para que el comando no se quede a la espera de introducción de datos como suele hacer, le metemos por la entrada estándar una línea vacía:

C:\>echo. | date
The current date is: 15/09/2007
Enter the new date: (dd-mm-yy)

Dentro del for tenemos que escapar el carácter de la pipa |. Lo que haríamos en UNIX con \, aquí lo hacemos con ^: ('echo.^|date').

Por tanto, el segundo for recoge en x,y, z los valores dd, mm, yy, estén en el orden que estén. Asignamos las variables u, v, w a las x, y, z, y ya lo tenemos. Veámoslo con una ejecución de ejemplo:

C:\>set $tok=1-3

C:\>for /F "tokens=1 delims=.:/-, " %u in ('date /t') do set $d1=%u

C:\>set $d1=15

C:\>if "1" GTR "9" set $tok=2-4

C:\>for /F "tokens=1-3  delims=.:/-, " %u in ('date /t') do (for /F "skip=1 tokens=2-4 delims=/-,().
" %x in ('echo.|date') do (
set %x=%u
 set %y=%v
 set %z=%w
 set $d1=
 set $tok=
) )

C:\>(for /F "skip=1 tokens=2-4 delims=/-,()." %x in ('echo.|date') do (
set %x=15
 set %y=09
 set %z=2007
 set $d1=
 set $tok=
) )

C:\>(
set dd=15
 set mm=09
 set yy=2007
 set $d1=
 set $tok=
)

Referencias:

How can I append the date and time to a file?

Command Output in a Variable

Microsoft Frequently Asked Questions Regarding The Windows 2000 Command Processor

Y todo el material de referencia para hacer scripts en Windows que, aunque está disponible en la web de Microsoft, tenemos en la ayuda de nuestros propios Windows, así como en la línea de comandos que, como comentaba, en ocasiones es incluso más completa (for /?, if /?, set /?, etc.)

Documentación de Microsoft:

Using batch files

El índice de la documentación sobre creación de ficheros batch.

Using batch parameters

Nos muestra ciertas expansiones muy interesantes que podemos hacer con los parámetros de entrada. Por ejemplo, si tenemos un batch como éste llamado parametros.cmd:

@echo off
echo El fichero es %~n1
echo La extensión es %~x1
echo El path es %~p1
echo La unidad es %~d1

una ejecución de ejemplo sería esta:

C:\>parametros.cmd "c:\path1\path2\fichero.txt"
El fichero es fichero
La extensión es .txt
El path es \path1\path2\
La unidad es c:

Using command redirection operators

Muy similares a las de UNIX

Using filters

Ejemplos con more, find (no es como el find de UNIX, sino como el grep) y sort.

Command shell overview

Nos cuenta cómo usar las variables de entorno, qué variables hay por defecto en un Windows XP y cómo ejecutar más de un comando en la misma línea (&&, ||, etc.).

for, if, set y echo

Command-line reference A-Z

:wq

Entradas relacionadas

7 Comentarios a “Ejemplos de scripting en Windows: Añadir la fecha y la hora al nombre de un fichero”

  • blackadder dice:

    Buenas.

    No sé si conoces de la existencia de las variables %TIME% %DATE%. Me parece que están del w2k para arriba.
    De todas formas, sigue siendo limitado. Hay cosas triviales en una shell de *nix que en los herederos de *DOS te cuesta un cojón hacer.

    Ahora con el powershell y los SFU parece que esto va a cambiar. De todas formas, si miras en el calendario, estamos en el año 2007, y dentro de diez años todavía arrastraremos las limitaciones (respecto a este tema) de los windows de hoy.

    En fin, sólo una reflexión dominguera.
    Peace.

  • blackadder ¡Uy! Pues no, no las conocía. ¡Muchas gracias por comentarlo!

    En cualquier caso, me temo que tendríamos que haber hecho exactamente las mismas tareas con sus salidas (salida de un W2K en castellano):

    C:\>echo %time%
    10:57:42,35
    
    C:\>echo %date%
    dom 16/09/2007

    Respecto al futuro, pues sí… Ya comento que el VBScript permite hacer muchas cosas pero no es inmediato de usar. El Windows PowerShell también parece bastante potente según veo en el artículo de la Wikipedia sobre el MSH… ¡pero salió en Noviembre del 2006! ¡A buenas horas cuecen habas!

  • Iván dice:

    No sabía que se pudieran hacer tantas cosas con el “scripting” del cmd. Siempre he pensado que estaba muy limitado (y realmente lo está), pero algo es algo.

    Gracias por el artículo, me lo guardo.

    Saludos, Iván.

  • Alberto dice:

    Hola

    En http://support.microsoft.com/kb/555314 tenis la forma de darle algo de formato.

    Saludo y felicidades por tu trabajo, Alberto

  • Alberto ¡Muchas gracias por tus amables comentarios y por el útil enlace!

  • loco_script dice:

    q hay gente.. un favor necesito un script q haga algun tipo de mantenimiento a la computadora porejemplo backup o o cualquier cosa q sea relacionado con mantenimiento pero q sea en windows xp o server 2003 haber si me ayudan me queda 3 dias pa presentar el avance espero su ayuda gracias porfa si encuentran o tienen algo escribirme a matador-1985@hotmail.com gracias

Trackbacks y pingbacks:

Tema LHYLE09, creado por Vicente Navarro