Dos Batch Scripting: String length with spaces. Easy Points.

Hello,
I have a callscript (.bat file), the purpose of which is to obtain the length of a string.

The script works, except when I pass a token that contains a space, even if between quotes.

I am running under windows xp.
set strLength=0
if [%1]==[] goto returnStrLen

SET #=%1
:count
if defined # (set #=%#:~1%&set /A strLength += 1&goto count)

:returnStrLen
echo %strLength%

Open in new window

airvectorAsked:
Who is Participating?
 
oBdAConnect With a Mentor Commented:
Use %~1 to retrieve the argument 1 without quotes (if specified) (and do yourself a favor and don't use special characters as variable names).
@echo off
set strLength=0
if "%~1"=="" goto returnStrLen

SET s=%~1
:count
if defined s (set s=%s:~1%&set /A strLength += 1&goto count)

:returnStrLen
echo %strLength%

Open in new window

0
 
airvectorAuthor Commented:
I got that idea from the net :).

Thanks for the help, here are your crispy points. On your way, could you explain to me why DOS chokes on quotes, lets say we had SET S=123"456 rather than SET s=%~1  ?

Cheers!
0
 
t0t0Commented:
When passing parameters, always use double-quotes if there is a likelyhood the parameter may contain spaces.

There are many ways to get the length of a string in DOS.

Here's my personal favourites (I've included some simple sample code):



To get the length of a string passed as the first command line parameter, use this:

   echo %~1>len
   for %%a in (len) do set /a len=%%~za - 2

   echo %len%

Normally, when passing parameters, DOS ignores leading and trailing spaces.



To test the length of a string, use this:

   set string=%~1

   echo %string%>len
   for %%a in (len) do set /a len=%%~za - 2

   echo %len%



To trim off leading and trailing spaces before testing for the lentgh of a string, use this:

   set string=%~1
   echo What it looks like before [%string%]

   :leading
   if "%string:~0,1%"==" " (
      set string=%string:~1%
      goto leading
   )

   :trailing
   if "%string:~-1%"==" " (
      set string=%string:~0,-1%
      goto trailing
   )

   echo What it looks like afterwards [%string%]

   echo %string%>len
   for %%a in (len) do set /a len=%%~za - 2

   echo %len%
0
Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

 
airvectorAuthor Commented:
t0t0, your code works like a charm. 2 qtns though.

1st, assuming %%~za gets the length of a token in a for loop, could we not have done something similar withouth the for construct, something like set len=%string:~z%, if it exists.

2nd, would there be a way to pipe other commands into your nifty script? I tried dir | t0t0len.bat, and it gave me 12, while the dir command outputs 31 files.

Thanks for that,
airvector
0
 
t0t0Commented:
1st. %%-variables are commonly known as FOR-variables. They only work inside the scope of the FOR where it was created.

Don't be put of by the term 'FOR' - which is normally associated with FOR-loops. Here, FOR is executed only once. It's only purpose is to return the size of a file as instructed by %%~z. This is standard practice. There is no equivalent command as suggested by you code "set len=%string:~z%"

2nd. Piping and redirection are massive topics. I haven't yet managed to pipe output from one abtch file (or a command) into another batch file however, the best way to pass data to batch file is either by using command line parameters or via a file.

The best way to get output from a command into a batch file is by using FOR ie,

   FOR /f "tokens=*" %%a in ('dir /b /a-d *.*"') do (
      echo %%a
   )


You can use this to pass each filename (in this example) to another batch file (or better still, a sub-routine within the same batch file) as in:

   FOR /f "tokens=*" %%a in ('dir /b /a-d *.*"') do (
      call process "%%a"
   )

The batch file Process.bat would pick up the contents of %%a (from this batch file) in %1 (it's own command line variable). The same applies to sub-routines however, sub-routine names are preceeded by a ':' (colon) as in ':process'. In this example, the CALLed routine would expand the variable with %~1 to drop the double-quotes.


 
0
 
airvectorAuthor Commented:
--
2nd. Piping and redirection are massive topics. I haven't yet managed to pipe output from one abtch file (or a command) into another batch file
--

It's amazing how such a useful tool is left in the dark like this. You're not the first to tell me that, and it's surprising.

Thanks for clarifying the ambiguous FOR construct. I was wondering why I couldn't go
   echo %string%>len
   for %%a in (len) do echo %%a

Thing is, why the -2 at set len=%%~za - 2

Well, I'll be posting more questions in new threads. Feel free to comment.
0
 
t0t0Commented:
Try this:

   set string=abc
   echo %string%>len

This ouputs the 3 letters'abc' to the file 'len'.


Now, if you examine the file 'len' by performing a simple 'DIR len' command, you'll see something like this:

   05/04/2010  23:10                 5 len
                      1 File(s)              5 bytes

So how come when sending 3 characters to a file, we actually end up with a filesize of 5?


Well, if you look inside the file using hex viewer you'll notice the file consists of 5 characters - not 3.

At the DOS prompt enter the following command:

   debug len
   u 100 104

You'll something like this:

   1515:100  61 62 63 0D 0A                    abc . .

These are hex values where 61 = 'a', 62 = 'b' and 63 = 'c'. You'll also notice 0D and OA. These are hex vaues for the decmal numbers 13 and 10 - the ASCII values for the 'Carriage Return' and 'Linefeed' characters.


DOS automatically adds 2 characters onto the end all lines ECHO'd (unless you suppress them). The 2 characters are: [CR] and [LF] (Carriage Return and Linefeed).

Try it yourself. Even if you just send a single character to a file, the filesize ends up being 3 - not 1.


So, %%~za returns the filesize - not the actual number of characters sent to the file. The filesize will always (usually) be 2 greater than the length of the data sent to it.

Therefore, to compensate for this, '%%~za - 2' returns the length of the data (not the size of the file).

By the way, I'm assuming the file contains just a single line here.



IMPORTANT NOTE:

Be very careful with your spaces. Doing this:

   echo %string%>len

Is NOT the same as:

   echo %string% >len

(Notice the space just before the redirection '>' character) The first echo sends 5 characters to the file while the 2nd echo send SIX characters to the file!! Don't believe me? Check it out!!


Does that answer your question?
0
 
t0t0Commented:
airvector

I have been as helpful as I could be. I have spent much time on this question.

I feel you have not closed this question fairly.

You have chosen to accept oBdA's solutions instead of mine.

oBdA's solution does not work! Try it for yourself. Try it like this:

   OBDA abc

(where 'OBDA' is the name of his batch file). The result is: 4. Obviously, this is not correct.


oBdA has approached this question with a programatic solution whereas, I have used natural (and common-practice) DOS constructs.

For some reason, you failed to accept that FOR is used in the way in which I demonstrated in my code.

You stated my code works "like a charm" yet despite this, you did not even award me a share of the points.

In view of this, I have requested attention from the moderators as I feel you have not closed this question fairly.

Furthermore, in view of oBdA's errornous code, I would expect you NOT to award him points for his solution.

I have also suggested to the moderator that should you prefer a programatic approach then the following code is about as close as you're likely to get to emulating how DOS deals with enclosing double-quotation marks.

Try the code like this:

   T0T0                            = 0

   T0T0 ""                        = 0

   T0T0 a                         = 1

   T0T0 "a"                      = 1

   T0T0 abc                     = 3

   T0T0 "abc"                  = 3

   T0T0 abc abc              = 7

   T0T0 "abc abc"           = 7

   T0T0 abc        abc      = 14

   T0T0 "abc        abc"   = 14

They all work perfectly as shown by the valuse in the right-hand column.



Here's the code:

   @echo off
   setlocal enabledelayedexpansion

   set string=%*
   set count=0

   echo.
   echo Command tail: [%string%]

   if not defined string (
      goto :end
   )

   if ^%string:~0,1%==^" (
      if ^%string:~-1%==^" (
         set string=%string:~1,-1%
      )
   )

   if not defined string (
      goto :end
   )

   :loop
      if not "!string:~%count%,1!"=="" (
      set /a count+=1
      goto loop
   )

   :end
   echo String:       [%string%]
   echo Length:       [%count%]

   exit /b %count%



Below is a shorter version of the code. I have left out the visual stuff:

   @echo off
   setlocal enabledelayedexpansion

   set string=%*
   set count=0

   if not defined string (
      goto :end
   )

   if ^%string:~0,1%==^" (
      if ^%string:~-1%==^" (
         set string=%string:~1,-1%
      )
   )

   if not defined string (
      goto :end
   )

   :loop
      if not "!string:~%count%,1!"=="" (
      set /a count+=1
      goto loop
   )

   :end
   echo Length:       [%count%]

   exit /b %count%



NOTE: The repeated code (shjown below) is intentional for proper processing:

   if not defined string (
      goto :end
   )
0
 
oBdACommented:
t0t0,
as to your comments in http://www.experts-exchange.com/Community_Support/General/Q_25720642.html:

1. You posted your answer (31/03/10 04:34 PM) only *after* airvector had already accepted (31/03/10 04:02 PM) my (correct!) answer. "This question already has been closed and points assigned. Post additional comments only if you want to clarify or comment on the solution.".

2. The code I posted above (a slightly altered version of airvector's original version) DOES work.
I copied and pasted the code as posted above into a script "StringLength2.cmd" and used your examples; the result (copied and pasted from the command console) is correct and exactly as expected:

----------------------------------------------------------------
C:\Temp>StringLength2.cmd "this is a string"
16

C:\Temp>StringLength2.cmd a
1

C:\Temp>StringLength2.cmd "a"
1

C:\Temp>StringLength2.cmd abc
3
----------------------------------------------------------------

How you came up with the erroneous results you claim to have obtained with my script is beyond me, but not an error of the code.

Furthermore, using a "set" command to snip off characters from a string is as "natural (and common-practice)" as exporting output to a file for further processing.
What is far from best practice in your original script is that you're creating a (temporary) file in the folder in which the user started the script instead of the %Temp% folder; your script will fail if the user running the script doesn't have permissions to write to this folder.
In addition, you didn't delete the temporary file after it wasn't necessary anymore, leaving unnecessary files named "len" in whichever folder(s) the user started the script.
0
 
t0t0Commented:
To all concerned

I have posted the following applicable comments elsewhere.

-----------------------------------------------------------------------------------------------------
Oops! I did not notice your comment had been accepted when I had posted my first comment. It seems this may have occured while I was in the process of commenting and without first refreshing my browser's page - It's really annoying when that happens.

Even at the time of requesting attention, I did not take the time-thing into consideration. It appears I have blown off a lot steam and wasted a lot of time - not just mine but others' too.

I'm supposed to be an 'Expert'. Hmmm.... Looking at the time of day I made those comments it appears my brain goes to mush between the hours of midnight and daybreak!

In the light of the circumstances, I'm inclined to withdraw my complaint and bring this to a close by apologising to all concerned, and hopefully, remember to hit F5 occasionally before submitting comments.
-----------------------------------------------------------------------------------------------------

Oops! I just triple-checked your code.

The problem appears to be with copying and pasting from EE into Notepad++. An additional (unwanted) space is added onto the end of each line of code in particular, SET s=%~1.

So, instead of getting set s=[%~1] we're getting set s=[%~1 ].

Hmm... This is really annoying me. So much of my time is wasted debugging code due to this problem.

WHAT A WASTE OF TIME THIS HAS ALL BEEN.... !!!!!!!!

Apologies!
-----------------------------------------------------------------------------------------------------

As you can probably tell, my vitamin D has probably kicked in.
0
 
airvectorAuthor Commented:
:) It's all good! Thanks a ton for the help, you really deserved the points! I'm be following up on the other thread in the mean time, so chin up t0t0.

-p
0
 
airvectorAuthor Commented:
And, to be really fair, t0t0, your solution did work best. I'll be more patient next time.

If you want to know why it's best in my opinion, I think it's because you used %* at the start of the code rather than %~1, so it accepts the wierd input I like to test with, like "  12 "1" 23  "". Also, you take into account a _pair_ of quotes with the double IFs (see quoted code below), which is the smartest way to go about it.  So, I think you had that vitamin D in you all along. ;)
if ^%string:~0,1%==^" (
      if ^%string:~-1%==^" (
         set string=%string:~1,-1%
      )
   )

Open in new window

0
 
t0t0Commented:
I'd better say thank you for you for taking the time to reply.... before the skies turn completely  dark again....

yeah, a couple of good features there. Lately, I've become increasingly involved with parsing the command line and validating user input so these are two areas I'm quite hot on at the moment.

Apologies for my earlier rant.
0
All Courses

From novice to tech pro — start learning today.