A simple way to source a batch library in your scripts

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

A simple way to source a batch library in your scripts

#1 Post by jfl » 08 Dec 2016 16:20

Unrelated to the other thread revived today, and using a different technique...

I've just added a simple step to my Library.bat batch debugging library, that (I hope) should be a giant leap for batchkind :-)
It makes it easy to use that library from other scripts, without having to use complex tricks: Just place Library.bat somewhere in your PATH, and insert at the beginning of your script:

Code: Select all

set ^"ARGS=%*^"                 &:# Prepare the argument line
setlocal EnableDelayedExpansion &:# Required for now
call Library.bat source         &:# Initialize all debugging macros

The remote call mechanism at the beginning of Library.bat is a very classic one:
It checks if the first argument is "call", and if so, it executes %* and exits.

What makes this really powerful is the way to make everything transparent to local or remote calls:
All my debuging library is based on macros, that hide the functions actually doing the job.
For example there's an %ECHO% macro that extents what echo does. The job is done by an :Echo routine, but you don't see it.
Or there's an %EXEC% macro, that runs a command, unless the script is running in no-exec mode (same as the WhatIf mode in PowerShell).
And in debug mode, %EXEC% displays the command executed, and its exit code in the end.
And of course there are the %FUNCTION%, %UPVAR%, and %RETURN% macros, that make it easy to create traceable structured routines, without having any understanding of the complex mechanisms involved to return strings back across the endlocal barriers.

I've simply added an %LCALL% macro, that allows calling into the library. For example, to call the :strlen routine there, you do:

Code: Select all

%LCALL% :strlen STRINGVAR RETVAR

And all the debug macros are dynamically updated to hide the external library call:
- Inside the library, LCALL=call
- In your script, LCALL=call Full\Pathname\of\Library.bat call
Likewise
- Inside the library, EXEC=call :Exec"
- In your script, EXEC=call Full\Pathname\of\Library.bat call :Exec

All this makes it very easy to write small scripts with the full power of Library.bat at hand.


A few examples:

test1.bat shows two basic macros for handling arguments and variables:
- %POPARG% gets script or function arguments, even with tricky characters, and independently of the current expansion mode.
- %ECHOVARS% displays the commands for recreating a list of variables. This is very useful for reproducing issues in another cmd window.
Also note that by convention (similar to that in many other scripting languages), %ARGS% is the name of the argument list.

Code: Select all

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set ^"ARGS=%*^"         &:# Prepare the argument line
setlocal EnableDelayedExpansion
call Library.bat source
%ECHOVARS% ARGS         &:# Display the initial argument line
%POPARG%                &:# Pop one argument off the ARGS argument line
%ECHOVARS% ARG ARGS     &:# Display the first argument and the remaining argument line

Try it out with several test cases. Ex:

Code: Select all

test1.bat "Adam & Eve.txt" "An original story^!" Don't miss it

displays

Code: Select all

set "ARGS="Adam & Eve.txt" "An original story!" Don't miss it"
set "ARG=Adam & Eve.txt"
set "ARGS="An original story!" Don't miss it"

The above example also shows the handling of tricky characters & and !.
Notice that you need to ^escape the ! in the command line for it to survive the call.
And (a debatable Library.bat shortcoming) the debug output does not display a ^caret in front of the !. I chose not to display one, because there's NO caret in the ARGS value. So browsing the debug output shows you what ARGS exactly contains.
So, you'll have to insert that caret manually again, if you copy and paste these commands into another cmd window, to recreate the variable there.
As a general rule, Library.bat only guaranties that the tricky characters listed in the (cmd /?) help will be passed correctly IF they're within quoted arguments.
That is: " &()[]{}^=;!'+,`~<>|"


Another example of a traceable function called recursively.
fact.bat relies on three debug macros for structured programming:
- %FUNCTION% declares a structured function with local variables. It replaces the setlocal command that you would use if you were doing it manually.
- %UPVAR% declares variables that will be returned up to the caller. (Inspired from Tcl)
- %RETURN% does all the return magic automatically. For advanced users: It replaces endlocal & set VAR1=%VALUE1% & etc..., handling all tricky characters in any expansion mode.

Code: Select all

@echo off
setlocal EnableExtensions EnableDelayedExpansion &:# No tricky args expected here
set ^"ARGS=%*^"         &:# Prepare the argument line
call Library.bat source
goto :main

:Fact                    :# Compute the factorial of %1.
%FUNCTION% EnableDelayedExpansion
%UPVAR% RESULT          &:# Return the result in variable RESULT
%POPARG%                &:# Get the argument. We could have used %1, which would be faster in this simple case
if not defined ARG set ARG=0
if %ARG%==0 (
  set RESULT=1
) else (
  set /A PREV=ARG-1
  call :Fact !PREV!
  set /A RESULT=ARG*RESULT
)
%RETURN%

:main
%POPARG%                &:# Pop one argument off the argument line
if "!ARG!"=="-d" %LCALL% :Debug.On & goto :main &:# Turn on debug mode in the library
call :Fact !ARG!        &:# Compute the factorial of !ARG!
%ECHOVARS% RESULT       &:# Display the variable RESULT set value

Example:

Code: Select all

fact.bat 4

outputs

Code: Select all

set "RESULT=24"

And

Code: Select all

fact.bat -d 4

outputs

Code: Select all

call :Fact 4
  call :Fact 3
    call :Fact 2
      call :Fact 1
        call :Fact 0
          return 0 & set "RESULT=1"
        return 0 & set "RESULT=1"
      return 0 & set "RESULT=2"
    return 0 & set "RESULT=6"
  return 0 & set "RESULT=24"
set "RESULT=24"

Note how the output is indented by call depth. This makes it easy to see the call stack leading to a problem: Just look up and to the left!
Thanks to that, I've never had the need for identifying line numbers in the scripts, or dump a call stack.


Using a shared library has pros and cons.
The main advantage is that this keeps your script simple and shorts.
The drawbacks are that the performance is not as good, due to the external calls; And also there's always the risk that a library update breaks something in your script.
To avoid that, it's always possible to extract the core modules of Library.bat, and insert them in your script.
For an example of a script using such an internal copy of these core modules, see regx.bat.
This script is a front end to reg.exe, that presents the registry as if it were a file system. Keys are directories, and Values are files. (The default value is a file called "")
Then it uses all the familiar file management commands (dir, type, tree, set, del, rd, ...) to manipulate those directories and files.
For example to list the contents of HKEY_CURRENT_USER\Environment, run:

Code: Select all

regx dir HKCU\Environment

To remind yourself of what regx commands are needed for getting this information, run it in no-exec mode:

Code: Select all

regx -X dir HKCU\Environment

Finally to understand how regx.bat does this, run it in debug mode:

Code: Select all

regx -d dir HKCU\Environment

I'm not showing the output here, because this would make this (already too long) post much longer still. But I encourage you to try: Seeing is believing!


Last but not least, you can even source Library.bat directly into the cmd.exe shell! (You need one started with /V:ON). Just run:

Code: Select all

Library.bat source

Want to see the value of a variable called P, without displaying the zillion other variables beginning with a P? Type:

Code: Select all

%ECHOVARS% P

Want to copy that extremely long PATH you painfully built, into another cmd window for another test? Type:

Code: Select all

%ECHOVARS% PATH | clip

Then right click on the other cmd window, and press enter.


Enjoy!

Jean-François

Post Reply