Wednesday 30 April 2008

Batch Programming Tip #04: Working with arguments - Part 3

6. Using SHIFT

I mentioned previously that you cannot assign more than 9 arguments to a batch call.
Well, that isn't altogether true because the shift command will allow you to read any number of arguments by replacing %1 with %2, %2 with %3, ... on each call.

Let's get straight to a real example (let's call it shiftargs.bat):

@echo off


We will use the arg_count variable to count the number of variables and show this at the end of the program.

:init_vars
set arg_count=0
goto load_args


Now let's load the arguments recursively and do something with each.


:load_args
if "%1" == "" goto end_script
set current_arg=%~1
goto action


In the action part we keep track of the argument count and more importantly shift to the next. Once we have called shift we have in effect "lost" %1 which has been replaced with %2 which itself is replaced with %3, etc.

:action
set /A arg_count+=1
shift
echo Argument %arg_count%: %current_arg%
goto load_args


At the end of the script, we display the argument count or a specific message if non were passed.


:end_script
if %arg_count%==0 goto no_args
echo Number of arguments: %arg_count%
goto eof

:no_args
echo No arguments provided.
goto eof


We finish it all off with the usual.

:eof
echo Press any key to quit...
pause > NUL
goto blackhole

:blackhole



If you just run the batch without passing any arguments it will display:

No arguments provided.
Press any key to quit...


We can also create an additional batch to call our shiftargs.bat with command line arguments as follows:

@echo off

call shiftargs the hungry fox and lovely plump goose are both peckish and so am I


This will of course display something more interesting:

Argument 1: the
Argument 2: hungry
Argument 3: fox
Argument 4: and
Argument 5: lovely
Argument 6: plump
Argument 7: goose
Argument 8: are
Argument 9: both
Argument 10: peckish
Argument 11: and
Argument 12: so
Argument 13: am
Argument 14: I
Number of arguments: 14
Press any key to quit...


And there we have it, we have passed more than 9 arguments to our batch command!

Bye for now.

Tuesday 29 April 2008

Batch Programming Tip #04: Working with arguments - Part 2

5. The %0 argument

I mentioned in my previous post that you could 'read' arguments using %1 through %9 (with or without the ~).

There is another argument you can get: %0. Of course, numbering had to start at 0, we are in geek world after all, right?

The first argument (%0) is simply the first thing that appears on the command line, which is the command itself.

The following batch code illustrates this:

@echo off

:init_vars
SET cmdln=%~0
if "%cmdln%"=="" goto no_cmdln
goto show_cmdln

:show_cmdln
echo Argument 0: %cmdln%
goto eof

:no_cmdln
echo No argument %%0.
goto eof

:eof
echo Press any key to quit...
pause > NUL
goto blackhole

:blackhole


Executing this will show the complete path to the current batch file.

Now let's call this same batch from within another simple batch:

@echo off
:: show_arg0 is the first batch file's name
call "show_arg0"


This will show:

Argument 0: show_arg0


Note that we never actually get to the no_cmdln condition!

Well, it's getting late I am afraid, but do come back for the next post, we'll be seeing how to use a variable number of arguments, and how to retrieve more than 9... so, until next time.

Monday 28 April 2008

Batch Programming Tip #04: Working with arguments - Part 1

This is less a tip than a succinct how-to.

1. The basics
Passing arguments to a script (or subroutine) is very simple: add the arguments just after the call on the same line. The arguments are separated by space. If you need to pass an argument which itself contains one or more spaces, make sure to enclose it using quotes.

Let's write a short example. The idea is simply to write two words which are passed as arguments.

2. Our first write words script
First let's clear up the caller syntax by writing the following to call_write_words.bat:

call write_words hello "hello world"

We pass two arguments:
hello
"hello world"

Note that the second argument is quoted as it contains a space.

That was the easier part, now let's look at how we get hold of the arguments and then print them on screen. To do this we will create a second batch file containing the code just below which is probably the simplest form of getting and printing two arguments.

To get the value of the arguments, use %1, %2, ..., %9:

@echo off

:action
echo Sentence1: %1
echo Sentence2: %2
goto eof

:eof
echo Press a key to quit...
pause > NUL
goto blackhole

:blackhole

This will output:

Sentence1: hello
Sentence2: "hello world"
Press a key to quit...

With our current example, if you don't provide values for those two arguments however, the program will ouput:

Sentence1:
Sentence2:
Press a key to quit...

In this particular case, it doesn't really matter as empty values will not cause an error (and/or make the program halt or falter in any way).

Mostly though you will want to check the values that are stored in your arguments and you may even provide default values in some cases.

3. Checking arguments
In the following batch program, we will create two variables to hold the values of the arguments passed to it. If no value is provided a default value is used instead.


@echo off

:init_vars
SET default=No sentence passed!
SET sentence1=%1
SET sentence2=%2
if "%sentence1%"=="" set sentence1=%default%
if "%sentence2%"=="" set sentence2=%default%
goto action

:action
echo Sentence1: %sentence1%
echo Sentence2: %sentence2%
goto eof

:eof
echo Press a key to quit...
pause > NUL
goto blackhole

:blackhole


Important note: to compare empty string values, enclose the string with the character of your choice. I usually use the " character but the [ and ] characters are also often used.

For instance:
if "%1"=="" ...
or
if [%1]==[] ...
have exactly the same meaning, the bottom line being that if the condition returns true, your %1 must be empty. In that case, use the default value you previously prepared or exit with an error message as needed.

Using:

call write_words "hello world"


You get the following output:

Sentence1: "hello world"
Sentence2: No sentence passed!
Press a key to quit...


You may be wondering how to get rid of the quotes around your "hello world" string. Indeed, you have to add the quotes to passe the argument but you may just have been meaning to pass "hello world" without the quotes, except that is technically impossible.
The good news is there's a nifty little feature that let's you do this: instead of using %1, %2, ... %9 use %~1, %~2, ... %~9.
Using this in our first simplified version, we get the following:

@echo off

:init_vars
:: added the ~
SET sentence1=%~1
:: added the ~
SET sentence2=%~2
goto action

:action
echo Sentence1: %sentence1%
echo Sentence2: %sentence2%
goto eof

:eof
echo Press a key to quit...
pause > NUL
goto blackhole

:blackhole

A much nicer result without the quotes.
It may seem a little superficial here but quotes can be a realy nuisance (when working with file names, and paths for instance) and you would mostly "strip" your arguments even if to add quotes later on when needed.

4. The argument wildcard
Finally, for today, another way of handling arguments is to use the argument wildcard %*. This will get all the arguments in one go.

Let's prepare our call:

@echo off
call write_words_star well, that will be it for today, tchuss until next time...


And now handle the call in a separate batch (write_words_star.bat):

@echo off

:init_vars
goto action

:action
echo Arguments: %*
goto eof

:eof
echo Press a key to quit...
pause > NUL
goto blackhole

:blackhole

The console will show the following:

Arguments: well, that will be it for today, tchuss until next time...
Press a key to quit...


Well, that really will be it for today. Until next time...

Sunday 27 April 2008

Batch Programming Tip #03: Using The Boring Black Hole

Batch scripts can quickly end up looking like spaghetti code. In my opinion this is mostly because you commonly use the (Really, Really Nasty) "goto" instruction.

There are a few things you can do to avoid "I-wrote-this-program-but-cannot-for-the-life-of-me-remember-how-or-even-whether-it-works" syndrome:
  • Always use the same program structure: init_system (setlocal calls, set system variables, etc.), init_vars (read program arguments and initalize variables), function routines and subroutines, eof (where you go to end and pause the program in interactive mode), blackhole (where you go when you want nothing more to happen).
  • Comment code profusely when needed as things like missing argument, undeclared variable, etc. will not be considered like compile-time or even runtime errors. So you're stuck with you ... and you to work out what's going on when things start going wrong.
  • Although this may not be considered best practice, using intermediary variables can make your code much more easy to read.
  • When calling a subroutine, pass variables as arguments where possible i.e. don't rely on caller program to prepare variables for the sub-routine (something similar to using "globals"), rather pass the variables as arguments (rather like passing arguments to a function instead of relying on global variables). This just makes the script more obvious and easy to understand, as with most programming languages.

So, here's a basic example that simply does... well nothing:



@echo off

:init_system
goto init_vars

:init_vars
SET text=I am doing something...
SET end_text=This is eof...
SET press_key=Press a key to continue...
goto do_something

:do_something
call :pause_prog "%text%"
goto eof


:: this takes one argument
:: @param 1 - the text to show
:pause_prog
cls
echo Message: %~1
echo %press_key%
pause > NUL
goto blackhole

:eof
call :pause_prog "%end_text%"
goto blackhole

:blackhole


Any reusable code is stored in a subroutine (e.g. pause_prog) which ends swallowed in the black hole which is where the subroutine stops and the code then returns to the line which immediately follows the call to the subroutine. Hence the name... but we'll get back to those in a later post. Until then... tchuss.

Saturday 26 April 2008

Batch Programming Tip #02: Using cd /D

Using the /D option on the CD command is another very simple "trick" which can come in handy if you're not sure what the final location of your script will be or whether it will actually be stored on the same drive as where you want the working directory to be.

(Note: the CD command is used to Change Directory. It can be really useful in combination with the FOR loop as we will see in a later tip.)

On a two-drive computer (we'll call the drives C:\ and D:\, in our examples), try the following.

On drive C:\ create a batch file containing:

@echo off
cd D:\DirectoryName
echo Current location: %cd%
echo Press any key to exit...
pause > NUL


Although you have changed directories on the D:\ drive, you will find the %cd% variable still contains the drive path on drive C:\.

(Note: The %cd% variable expands to your current working directory. It's mostly considered like an environment variable, meaning you don't have to set it, it just "exists" natively.)

This is where the /D option comes in handy as it forces a drive change too, if necessary.

@echo off
:: change directory and force drive change
cd /D D:\DirectoryName
echo Current location: %cd%
echo Press any key to exit...
pause > NUL

This second example works because it also changes the drive letter automatically so you can now move your script to a different location on a different drive and still obtain the expected results.

In the following script we will save the original location, switch to a new working directory and then back to the original location once we have finished doing Some Really Useful Stuff. The script is a little more complicated and contains calls to a subroutine and also uses of the famous black hole both of which we'll get back to later on.

@echo off

:init_vars
SET original=%cd%
SET working=D:\DirectoryName
goto do_switch

:do_switch
:: the following will show original location
call :show_cd
:: change directory and force drive change
cd /D "%working%"
:: this will now show the new working directory
call :show_cd
goto do_something

:do_something
echo This is where we put Our Really Useful Code
goto end_script

:end_script
:: switch back to original location
cd /D "%original%"
call :show_cd
goto eof

:show_cd
echo Working directory is now: %cd%
goto blackhole

:eof
echo Press any key to exit...
pause > NUL

:blackhole


So basically using cd /D is a pretty effective way of keeping things nice and simple and avoiding those last minute surprises we could all do without :).

Thursday 24 April 2008

Batch Programming Tip #01: Variables and spaces

Well, this is probably the most basic of all tips. It's also one that will save you loads of time if you got it wrong in the first place (as I did...). Most programming languages don't worry too much about spaces unless they are part of a string.

For instance, in PHP you could declare:
$my_var = "hello world"; // there are spaces around the = sign
or
$my_var="hello world"; // there are no spaces around the = sign
indescriminately as both declarations have the same meaning.

Try the following in batch:

@echo off
SET my_var=world
echo Hello %my_var%
pause > NUL

This will work as expected and output hello world to the standard output (which would normally be the console). Notice we do not use quotes.

If you try this second example, however, you may be in for a surprise.

@echo off
SET my_var = world
echo Hello %my_var%
pause > NUL

Indeed the script doesn't work as expected: it just outputs Hello followed by ...nothing. The reason for this behaviour is that batch programming variable names may contain spaces. Yup, scary, huh?

So to make the second example "work", we need to add the space in the variable reference as follows:

@echo off
SET my_var = world
:: there is a space before the second % below
echo Hello %my_var %
pause > NUL

This will output Hello  world. Notice that there are two spaces between hello and world because the value which is assigned to the %my_var % variable starts immediately after the = sign.

So the next fix is to remove that leading space:

@echo off
:: the space between = and world has been removed
SET my_var =world
echo Hello %my_var %
pause > NUL


Let's finish with a working example of a variable name containing spaces.

@echo off
:: ask the user to enter first name
SET /P first name=Please enter your first name:
SET hello=Hello
echo %hello% %first name%
pause > NUL


Enter your first name and hey presto, it hellos you as planned.

Although I would never suggest using variables with spaces as they can lead to serious confusion, this tip would usually be a good place to start when variables seem to unset themselves like by (black) magic between one line and the next.

Well, that will be it for today...

@echo off

:::
:: Init variables
:

:init_vars
SET goodbye=See you later,
SET end_goodbye=Thanks for reading...until next time ;-).
SET anonymous=Anonymous
SET attempts=0
SET max_attempts=3
goto ask_for_first_name

:::
:: Ask for first name
:: Loop for max_attempts if necessary or give up.
:

:ask_for_first_name
SET /P first name=Please enter your first name, so I can be polite and say goodbye:
SET /A attempts+=1

if "%first name%"=="" (
:: LSS is definitely an Win NT trick
if %attempts% LSS %max_attempts% goto ask_for_first_name
SET first name=%anonymous%
)
goto say_goodbye


:::
:: Output goodbye
:

:say_goodbye
cls
echo %goodbye% %first name%
echo %end_goodbye%
goto eof


:::
:: Pause for reading
:

:eof
echo.
echo (Press any key to quit...)
pause > NUL

Hello and welcome

What more can I say? A new day and a new blog... let's hope this one "sticks".

I will be starting with a few "tips" relating to my latest fad: batch programming.
Weird, huh? But also jolly useful at times. I am using the NT flavour of batch programming so I am not sure how much of the code I will be posting here will be usable on other systems.

Obviously, I couldn't start to thank Rob van der Woude enough for compiling the ultimate scripting site: http://www.robvanderwoude.com/. In it he describes batch commands, batch syntax and even provides scores of examples.

Here is a compilation of the tips I hope to be expanding shortly:
  • Tip#01: Variables and spaces
  • Tip#02: Using: cd /D
  • Tip#03: Using The Boring Black Hole
  • Tip#04: Working with arguments
  • Tip#05: Using CON ... or not
  • Tip#06: Getting the batch file path
  • Tip#07: Retrieving and using file size
  • Tip#08: Reading from a file
  • Tip#09: Using: for /D
  • Tip#10: Making and calling a subroutine
  • Tip#11: Using The Substring Equivalent
  • Tip#12: Creating a timestamp
  • Tip#13: Using: setlocal enabledelayedexpansion
  • Tip#14: The difference between :: and REM
  • Tip#15: Getting file attributes

Of course, this list will probably change over time in ways I cannot imagine just yet so bare with me. Oh and please do comment on anything you want, that's what the comment link is for and, in my experience, it is usually shamefully underused. So, dither no more... :)

Online Marketing
Add blog to our blog directory blog search directory Blog Directory Blogarama - The Blog Directory