Este es un breve tutorial que cuenta cómo se trabaja con argumentos en los scripts VBScript. Conoceremos el objeto wshArguments, que es una colección del objeto WScript con los argumentos que ha recibido el script.
Para diferenciar los argumentos propios de un script de los de WScript.exe o CScript.exe, los argumentos de estos dos ejecutables van precedidos siempre por dos barras de división. Los argumentos propios de estos ejecutables son
Es decir que si, por ejemplo, lanzamos un script así, el script se lanzará sin mostrar el logotipo (muy util en scripts que devuelven listados):
cscript //nologo script.vbs
Los argumentos de un script VBScript pueden ser con nombre o sin nombre. Los argumentos sin nombre van escritos sin más:
cscript //nologo sin_nombre1 sin_nombre2 sin_nombre3 ...
Los argumentos con nombre tienen una etiqueta definida después de una barra de división. Los hay de dos tipos:
La etiqueta de los argumentos con nombre no está limitada a un solo caracter, puede ser una palabra completa (ejemplo: /servidor).
Tanto los argumentos con nombre como los argumentos sin nombre puede que incluyan espacios en su contenido. Cuando esto sucede es necesario que se encierre entre comillas (las comillas no se incluirán como parte del valor):
cscript script.vbs /a:Este es el parámetro con nombre a y este es el parámetro sin nombre 1
Daría como resultado un parámetro con nombre /a cuyo contenido sería Este y nada menos que 14 argumentos sin nombre (es, el, parámetro, con, nombre, a, y, este, es, el, parámetro, sin, nombre, 1). Como realmente sólo queríamos pasar un parámetro con nombre y uno sin nombre, la línea debe ser esta otra:
cscript script.vbs /a:"Este es el parámetro con nombre a" "y este es el parámetro sin nombre 1"
No sólo se debe encerrar entre comillas los valores, también se debe hacer con las etiquetas de los argumentos con nombres si estas incluyen espacios en medio (sí, se puede hacer :-)):
cscript script.vbs /primer argumento con nombre:"su valor"
Esta línea daría como resultado un modificador, /primer y tres argumentos sin nombre: argumento, con y nombre:su valor (nótese cómo se ha convertido en un sólo argumento nombre:"su valor" y cómo han desaparecido las comillas). La línea debería haberse escrito así:
cscript script.vbs /"primer argumento con nombre":"su valor"
¿Cuándo es mejor un tipo de argumento frente a otro? De manera general, podemos decir que siempre son mejores los argumentos con nombre, pues nos permiten pasar modificadores y parámetros en cualquier orden, mientras que los argumentos sin nombre son interpretados en el script según el órden en que se pasen. Esto hace que con parámetros sin nombre sea más fácil que se produzcan confusiones (¿que parámetro debía poner antes?).
Generalmente se usan los parámetros sin nombre cuando se trata de pocos parámetros y todos del mismo tipo. No suele ser deseable mezclar ambos tipos de argumentos, siendo mejor usar sólo sin nombre o sólo con nombre. No obstante, hay un caso en el que si es interesante el mezclarlos: cuando tenemos modificadores y parámetros opcionales y uno dos parámetros requeridos, puede ser muy útil poner los modificadores y parámetros opcionales como argumentos con nombre y los parámetros requeridos como argumentos sin nombre. Por ejemplo, un script que copie un fichero a una carpeta no necesita que ambos parámetros tengan nombre:
cscript copiar-fichero.vbs c:\listados\orcos.txt e:\moria
Si este mismo script preguntase si debe sobreescribir el fichero en caso de que exista en el destino, podría llevar un modificador que así lo indicase, evitando el preguntarlo:
cscript copiar-fichero.vbs /s c:\listados\orcos.txt e:\moria
Una vez recibe los argumentos un script, deberían ser siempre revisados, para comprobar que no se han pasado argumentos de más, que no se han pasado argumentos con nombre no contemplados en el script y que han sido pasados todos los argumentos requeridos .
El objeto wshArguments tiene una propiedad Count que devuelve el número de argumentos pasados. Esta cuenta es la suma de argumentos con y sin nombre. Si, por ejemplo, la suma de todos los parámetros es 5, podemos comprobar si se han pasado argumentos de más de la siguiente forma:
If WScript.Arguments.Count > 5 Then
WScript.Echo "Error 1: se han pasado demasiados argumentos."
WScript.Quit 1
End If
No obstante, esto tiene sus problemas ¿qué pasa si hay un argumento sin nombre requerido y se pasan cinco argumentos con nombre, aunque uno de ellos no sea propio del script? En estos casos podemos desear contar los argumentos sin nombre por un lado y los con nombre con otro. Esto se puede hacer gracias a dos propiedades del objeto wshArguments: la propiedad Unnamed y la propiedad Named. Ambas propiedades son objetos de tipo colección que tienen una propiedad Count, que devuelve el número de argumentos que contienen. Así, para controlar el ejemplo anterior, deberíamos hacer esto otro:
'Controlamos que el número de argumentos sin nombre no sea más de uno
If WScript.Arguments.Unnamed.Count > 1 Then
WScript.Echo "Error 1: se han pasado demasiados argumentos sin nombre."
WScript.Quit 1
'Controlamos que el número de argumentos sin nombre no sea inferior a uno
ElseIf WScript.Arguments.Unnamed.Count < 1 Then
WScript.Echo "Error 2: no se ha pasado el argumento sin nombre requerido"
WScript.Quit 2
End If
'Controlamos que el número de argumentos con nombre no sea superior a cuatro
If WScript.Arguments.Named.Count > 4 Then
WScript.Echo "Error 3: se han pasado demasiados argumentos con nombre"
WScript.Quit 3
End If
Obviamente, el control primero podría haber sido preguntando, sencillamente, si el número de argumentos sin nombre era distinto de uno, pero no habría tanta precisión en el informe del error.
Para hacer esta comprobación hay que relizar un enfoque un poco indirecto. Personalmente no suelo hacer esta comprobación, pues ¿qué más da? Si se pasa un parámetro con nombre que no es contemplado por el script, sencillamente será ignorado. No obstante, si queremos ser totalmente pulcros, se debería avisar al usuario de que ha pasado un argumento con nombre que no es contemplado por el script. Habrá tantas soluciones como desarrolladores. Por ejemplo, esta sería una manera de hacerlo.
Consiste en recorrer la colección completa de argumentos, no la colección de argumentos con nombre. Cuando hacemos esto, el objeto wshArguments devuelve como valor del argumento su etiqueta más valor. A base de trocear cadenas, podemos acabar obteniendo la etiqueta y por tanto sabiendo si el argumento con nombre es contemplado o no. Pongamos que tenemos un script que contempla los argumentos /A, /B, /C y /D.
'Array con los nombres de argumentos contemplados
Dim arr_Argumentos
arr_Argumentos = Array("A","B","C","D")
'Contador de elementos del array
Dim int_Elemento
'Almacenará dónde están los dos puntos en los parámetros
Dim int_Puntos
'Almacenará la etiqueta del argumento con nombre
Dim str_Etiqueta
'Contador de argumentos
Dim int_Argumento
'Se pondrá a True si el argumento se encontró
Dim bol_Encontrado
'Almacenará el mensaje en caso de encontrar un argumento no
'contemplado
'Recorremos todos los argumentos en un bucle For
For int_Argumento = 0 To WScript.Arguments.Count - 1
'Revisamos si el argumento recibido es con nombre o sin él.
'Para ello miramos si comienza por barra de dividir
If Left(WScript.Arguments(int_Argumento),1) = "/" Then
'Obtenemos la posición de los dos puntos que separan la
'etiqueta del valor, en el caso de los parámetros
int_Puntos = InStr(1,WScript.Arguments(int_Argumento),":")
'Se ha encontrado el caracter de dos puntos, es un parámetro
If int_Puntos > 0 Then
'La etiqueta es, por tanto desde el segundo caracter
'(la barra de división la quitamos) hasta el anterior a
'los dos puntos
str_Etiqueta = Mid(WScript.Arguments(int_Argumento), _
2,int_Puntos - 2)
'No se ha encontrado el caracter de dos puntos, es un
'modificador
Else
'La etiqueta es todo el argumento menos la barra de
'división inicial
str_Etiqueta = _
Right(WScript.Arguments(int_Argumento), _
Len(WScript.Arguments(int_Argumento)) - 1)
End If
'Marcamos como no encontrado el argumento
bol_Encontrado = False
'Veremos ahora si está la etiqueta dentro del Array
For int_Elemento = LBound(arr_Argumentos) To _
UBound(arr_Argumentos)
If Ucase(arr_Argumentos(int_Elemento)) = _
Ucase(str_Etiqueta) Then
bol_Encontrado = True
Exit For
End If
'Si no se ha encontrado el argumento, lo almacenmos
'en el mensaje
Next 'int_Elemento
If Not bol_Encontrado Then
str_Mensaje = str_Mensaje & "El argumento " & _
str_Etiqueta & " no está cont" & _
"emplado en este script." & _
vbCrLf
End If
End If
Next 'int_Argumento
'Si el mensaje tiene contenido...
If Len(str_Mensaje) > 0 Then
'mostramos el mensaje y terminamos el script con error 1
WScript.Echo "Error 1: Parámetro/s no contemplado/s " & _
"por este script. El/los parámetro/s no" & _
" contemplado/s es/son:" & vbCrLf & vbCrLf & _
str_Mensaje
WScript.Quit 1
Else
'Todos los argumentos pasados son contemplados por el script,
'lo indicamos y salimos del script sin error
WScript.Echo"Argumentos correctos"
WScript.Quit 0
End If
Para comprobar que están todos los argumentos requeridos, debemos tener en cuenta si los argumentos requeridos son con nombre o sin nombre.
Los argumentos sin nombre requeridos tan sólo pueden ser revisados contándolos. Para ello usamos la propiedad Count de la colección Unnamed. Supongamos que un script recibe dos argumentos sin nombre requeridos, el control a establecer sería algo así:
If WScript.Arguments.Unnamed.Count <> 2 Then
WScript.Echo "Número de argumentos sin nombre erroneo." & _
" El número de argumentos sin nombre debe" & _
" ser dos."
WScript.Quit 1
Else
WScript.Echo "Número de parámetros sin nombre correcto"
WScript.Quit 0
End If
Esto es una limitación y hace que sea obvio que en el momento en que no todos los argumentos sin nombre sean requeridos, será necesario que pasemos los opcionales como argumentos con nombre, para así poder tener mejor control.
Es muy fácil comprobar si ha sido pasado un argumento con nombre gracias al método Exists de la propiedad Named. A este método se le pasa como parámetro la etiqueta del argumento (sin la barra de división) y devuelve True en el caso de que el argumento haya sido pasado y False si no lo ha sido. Pongamos que se debe pasar el argumento con nombre /R:
If Not WScript.Arguments.Named.Exists("R") Then
WScript.Echo "Error 1: El argumento /R es un argumento requerido"
WScript.Quit 1
End If
Como se puede ver, el uso de argumentos con nombre es mucho mejor que el de argumentos sin él, pues permite preguntar por un argumento en concreto, independientemente del número de argumentos pasado y del orden en que lo haya sido.
Vamos a ver ahora cómo se obtiene el valor de los parámetros que se han recibido.
La forma más básica es por medio de la propiedad Item del objeto wshArguments. Esta propiedad nos permite consultar el valor de un argumentos según el órden en que ha sido pasado. Si lanzamos este script:
cscript //nologo script.vbs sin_nombre1 /R:con_nombre1 sin_nombre2 /L:con_Nombre2
Este bucle mostrará en pantalla los argumentos recibidos según el orden en que fueron pasados:
For i = 0 To WScript.Arguments.Count - 1
WScript.Echo WScript.Arguments.Item(i)
Next 'i
La propiedad Item es la predeterminada del objeto wshArguments, con lo que se puede omitir y obtener lo mismo:
For i = 0 To WScript.Arguments.Count - 1
WScript.Echo WScript.Arguments(i)
Next 'i
En ambos casos la devolución será la misma:
sin_nombre1
/R:con_nombre1
sin_nombre2
/L:con_nombre2
Como vemos, se mezcla todo, lo que hace que no sea una forma deseable de referirse a los argumentos. Por ello, es preferible recorrer los argumentos sin nombre, con Unnamed, y los con nombre por otro, con Named:
WScript.Echo "Argumentos sin nombre:"
For i = 0 To WScript.Arguments.Unnamed.Count - 1
WScript.Echo WScript.Arguments.Unnamed(i)
Next 'i
WScript.Echo
WScript.Echo "Argumentos con nombre:"
For i = 0 To WScript.Arguments.Named.Count - 1
WScript.Echo WScript.Arguments.Named(i)
Next 'i
La devolución de este script daría los parámetros ordenados:
Argumentos sin nombre:
sin_nombre1
sin_nombre2
Argumentos con nombre:
con_nombre1
con_nombre2
No obstante, en el caso de los argumentos con nombre no es necesario recorrerlos con un bucle, gracias al método Exists de Named, que nos permite preguntar diréctamente si un argumento con nombre ha sido o no pasado. Para referirse al argumento, tanto en el método Exists como a la hora de que Named nos devuelva su valor, se utiliza la etiqueta sin la barra de división:
'Comprobamos si ha sido pasado el argumento con nombre F
'y, en caso afirmativo, guardamos su valor en la variable
'str_Fichero
If WScript.Arguments.Named.Exists("F") Then
str_Fichero = WScript.Arguments.Named("F")
End If
Para finalizar. este script que nos devuelve los argumentos pasados, indicando si son con nombre o sin él, y en el caso de tener nombre nos dice cuál es este nombre. Reune en sí varias de las técnicas que hemos visto a la hora de tratar con los argumentos:
Dim int_Argumento
'Recorremos todos los argumentos
For int_Argumento = 0 To WScript.Arguments.Count - 1
'Mostramos el argumento tal y como lo devuelve
'Arguments
WScript.Echo "Argumento nº " & int_Argumento & _
": """ & WScript.Arguments( _
int_Argumento) & """"
'Como estamos refiriendonos a los argumentos
'directamente con Arguments, los arguemtos con nombre
'son devueltos con la etiqueta incluída, por lo que
'empiezan por barra de dividir. Con este If separamos
'los argumentos con nombre de los sin nombre
If Left(WScript.Arguments(int_Argumento),1) = "/" Then
'Obtenemos la posición de los dos puntos en el
'parámetro. En caso de que se trate de un
'modificador, no serán encontrados y la devolución
'de InStr será cero
int_Puntos = InStr(1,WScript.Arguments(int_Argumento),":")
'Si los dos puntos han sido encotrados...
If int_Puntos > 0 Then
'Extraemos el nombre del argumento usando Mid, en
'lo devuelto por Arguments, como lo que hay entre
'la barra de dividir inicial y los dos puntos, y
'su contenido pasando lo anterior como identificador
'a Named
WScript.Echo """" & Mid(WScript.Arguments( _
int_Argumento),2,int_Puntos - 2) & _
""" es un parámetro con nombre, cuyo" & _
" valor es """ & WScript.Arguments.Named( _
Mid(WScript.Arguments( _
int_Argumento),2,int_Puntos - 2)) & """"
'No hay dos puntos, es un modificador
Else
'Extraemos el nombre del argumento usando Mid, en
'lo devuelto por Arguments, como lo que hay desde
'la barra de dividir inicial hasta el final
WScript.Echo """" & Right(WScript.Arguments( _
int_Argumento), _
Len(WScript.Arguments( _
int_Argumento)) - 1) & _
""" es un modificador, por tanto " & _
"no tiene contenido."
End If
'Como el argumento no comienza por barra de dividir se trata
'de un argumento sin nombre
Else
'Mostramos el valor del argumento sin nombre
WScript.Echo """" & WScript.Arguments(int_Argumento) & _
""" es un argumento sin nombre."
End if
Next 'int_Argumento
WScript.Quit 0