You are not logged in.

#1 28 Jan 2018 16:55

Dheath
Member
Registered: 06 Jan 2018
Posts: 11

Return Array of Values?

I tried writing a little script to sort arguments into switches and targets to be processed, but I realised I couldn't use a for loop after endlocal, since the iteration through variables would expand after the variables have been cleared. Does anyone know if there a better way of doing this? I don't think it is possible to use enabledelayedexpansion without setting local, and I don't want to set permanently defined environmental variables. The only alternative I can think of is to echo the array as output, but then the array would need to be re-indexed, and the script wouldn't be able to provide normal output (since output would always have to be redirected).

Incidentally, I'm sure there's a much more efficient way of doing this, like `if "%1"=="/*" `, but I wasn't able to find it.

Here is the code I originally wrote (with echo for testing). Criticism is welcome. tongue

@echo off
setlocal enabledelayedexpansion


set /a ArgumentCount=0
set /a SwitchCount=0
set /a TargetCount=0


echo.
for %%a in (%*) do (
  set /a ArgumentCount+=1
  set "Argument[!ArgumentCount!]=%%~a"
  rem I have not found a way to to reference !A[!B!]! ; only works if one variable uses %.
  
  echo %%~a|findstr /r "^/" 2>&1>NUL
  
  if !ERRORLEVEL! leq 0 (
    set /a SwitchCount+=1
    set "Switch[!SwitchCount!]=%%~a"
    ) else (
      set /a TargetCount+=1
      set "Target[!TargetCount!]=%%~a"
      )
  
  )


endlocal & (
  rem set /a ArgumentCount=%ArgumentCount%
  set /a SwitchCount=%SwitchCount%
  set /a TargetCount=%TargetCount%
  
  for /L %%a in (1,1,%SwitchCount%) do (
    echo Switch %%a: "!Switch[%%a]!"
    set "Switch[%%a]=!Switch[%%a]!"
    )
  for /L %%a in (1,1,%TargetCount%) do (
    echo Target %%a: "!Target[%%a]!"
    set "Target[%%a]=!Target[%%a]!"
    )
  
  )


exit /b 0

Offline

#2 28 Jan 2018 17:43

Dheath
Member
Registered: 06 Jan 2018
Posts: 11

Re: Return Array of Values?

Update on my stupidity:

I just realised I can dump the arrays into strings and pass those normally. I don't know what the limitations of this method might be, but it will probably meet my needs. I am still curious if there is a way to pass the array itself though.

I also realised that, while `if` does not seem to support the * wildcard, the string could probably be concatenated like `if "%%a:~0,1" equ "/" `. I'm not yet sure if this works, but I will give it a try.

Offline

#3 28 Jan 2018 19:02

Dheath
Member
Registered: 06 Jan 2018
Posts: 11

Re: Return Array of Values?

Interesting that FOR doesn't seem to accept variable values for tokens.

:Testing
  for /L %%a in (1,1,%SwitchCount%) do (
    for /f "tokens=%%a" %%b in ("%SwitchString%") do (
      echo Switch %%a: "%%~b"
      )
    )

This method works, but not if %SwitchString% is designated as a string like "%SwitchString%". I guess batch for loops are still a mystery to me. hmm

:Testing
  set /a Index=0
  for %%a in (%SwitchString%) do (
    set /a Index+=1
    echo !Index!: "%%~a"
    )

Last edited by Dheath (28 Jan 2018 19:16)

Offline

#4 29 Jan 2018 13:36

Pyprohly
Member
Registered: 26 Nov 2014
Posts: 37

Re: Return Array of Values?

Just don’t `endlocal`—don’t return anything—keep it global, then you can do away with all that `endlocal & (…)` nonsense. I don’t think there’s a way to handle those variables in a dynamic way after doing `endlocal`.

Dheath wrote:
echo %%~a|findstr /r "^/" 2>&1>NUL

Hmm. I don’t think `2>&1>NUL` does what you think it does.

Dheath wrote:

Incidentally, I'm sure there's a much more efficient way of doing this, like `if "%1"=="/*" `, but I wasn't able to find it.

If you can’t `find` it then `findstr` it:

echo(%1| findstr "^/" >nul && echo Switch, it’s a switch! || echo Just an ordinary argument passing through…
Dheath wrote:

I also realised that, while `if` does not seem to support the * wildcard, the string could probably be concatenated like `if "%%a:~0,1" equ "/" `. I'm not yet sure if this works, but I will give it a try.

This won’t work, clearly; dynamic variables cannot be indexed, but hopefully you found the previous example useful.

On a different note, you might want to check on your definition of “concatenation”…

Dheath wrote:

Interesting that FOR doesn't seem to accept variable values for tokens.

It’s not the tokens’ fault. The `for /f` loop options are parsed before delayed expansion variables and `for`-loop dynamic variables get a change to evaluate, so they won’t work here. Using a subroutine and parameter variables can help evade this limitation though.

Dheath wrote:

This method works, but not if %SwitchString% is designated as a string like "%SwitchString%". I guess batch for loops are still a mystery to me. hmm

Well not for me, and not for thee, as examples are key, as you will see:

@echo off
setlocal EnableDelayedExpansion

set /a Index=0
for %%a in (a b c) do (
	set /a Index+=1
	echo !Index!: "%%~a"
)
echo(

set /a Index=0
for %%a in ("a b" c) do (
	set /a Index+=1
	echo !Index!: "%%~a"
)
echo(

set /a Index=0
for %%a in ("a b c") do (
	set /a Index+=1
	echo !Index!: "%%~a"
)
echo(

Output:

1: "a"
2: "b"
3: "c"

1: "a b"
2: "c"

1: "a b c"

Simply, the double quotes negate delimiters.


I’m long past that stage of trying to put together an argument parser in batch. Not trying to prematurely end your road of discovery, but any path is going to lead to disappointment: a good batch parser cannot be written in batch itself.

Offline

#5 31 Jan 2018 15:50

Dheath
Member
Registered: 06 Jan 2018
Posts: 11

Re: Return Array of Values?

Thanks for the advice! I realise this might not be the most productive script but it's been a good learning experience since I'm new to programming. Your review is greatly appreciated. smile


Pyprohly wrote:

Just don’t `endlocal`—don’t return anything—keep it global, then you can do away with all that `endlocal & (…)` nonsense. I don’t think there’s a way to handle those variables in a dynamic way after doing `endlocal`.

Even if I don't explicitly `endlocal`, the variables still seem to be cleared when the script ends. They are passed to scripts called from the script in which they are defined, but not to the script that calls them. Unless I'm missing something, maybe `endlocal` is implicit since I use `setlocal enabledelayedexpansion`? I am not clear on all the differences between `exit /b` and `goto :eof`, but I read that `goto :eof` does invoke `endlocal` automatically if `setlocal` was set. I didn't think `exit /b` would have the  same effect, but perhaps it does.


Pyprohly wrote:

Hmm. I don’t think `2>&1>NUL` does what you think it does.

Could you tell me what it actually does? I had some mixed results while I was tinkering, but it seems to work fine now. The only thing I'm not sure of is whether one expression can be used to redirect twice, or whether it would be more reliable to use `2>1 ` and `1>NUL` as two expressions separated by a space. My goal was to not ever print any output from the command.


Pyprohly wrote:

If you can’t `find` it then `findstr` it:

echo(%1| findstr "^/" >nul && echo Switch, it’s a switch! || echo Just an ordinary argument passing through…

Curiously, I encountered some trouble using the echo|findstr method when calling from another script's recursive dir listing. Only the recursive cases choked on the pipe. I ended up matching against a substring to get around this but I'm not sure what caused it. I don't think it's necessary to debug at this point, but perhaps this is a common issue?


Pyprohly wrote:

This won’t work, clearly; dynamic variables cannot be indexed, but hopefully you found the previous example useful.

Thanks for clarifying about dynamic variables, I didn't even know that was a term. To get around this, I just set `set "Argument=%%~a"` and referenced the substring of !Argument!.


Pyprohly wrote:

On a different note, you might want to check on your definition of “concatenation”…

You're absolutely right. I guess this would be truncation? tongue


Pyprohly wrote:

It’s not the tokens’ fault. The `for /f` loop options are parsed before delayed expansion variables and `for`-loop dynamic variables get a change to evaluate, so they won’t work here. Using a subroutine and parameter variables can help evade this limitation though.

Great tip! I can imagine this would get out of hand quickly if the script isn't well structured, but that's probably a good incentive.

Pyprohly wrote:

Well not for me, and not for thee, as examples are key, as you will see:

@echo off
setlocal EnableDelayedExpansion

set /a Index=0
for %%a in (a b c) do (
	set /a Index+=1
	echo !Index!: "%%~a"
)
echo(

set /a Index=0
for %%a in ("a b" c) do (
	set /a Index+=1
	echo !Index!: "%%~a"
)
echo(

set /a Index=0
for %%a in ("a b c") do (
	set /a Index+=1
	echo !Index!: "%%~a"
)
echo(

Output:

1: "a"
2: "b"
3: "c"

1: "a b"
2: "c"

1: "a b c"

Simply, the double quotes negate delimiters.

That's a nice example. I'm still not sure about some of the FOR modes, but I think I understand how double quotes affect the unspecified mode now. It seems like ' and ` are just interpreted as normal characters, but they do have a technical effect in the FOR /F mode as described on the ss64 page about that.


Pyprohly wrote:

I’m long past that stage of trying to put together an argument parser in batch. Not trying to prematurely end your road of discovery, but any path is going to lead to disappointment: a good batch parser cannot be written in batch itself.

Don't worry about disappointing me, this is mostly just an exercise to learn batch, and programming in general. I know there's a lot more comoplexity in sanitisation and such, but the script I have now meets my basic needs for now. Please don't feel obligated to look it over again, but here's what I have now in case you're curious:


@echo off
setlocal enabledelayedexpansion


set /a SwitchCount=0
set /a TargetCount=0
set "SwitchString="
set "TargetString="


for %%a in (%*) do (
  set "Argument=%%~a"
  
  if ["!Argument:~0,1!"] equ ["/"] (
    if ["!Argument:~1,1!"] neq [""] (
      set /a SwitchCount+=1
      set "SwitchString=!SwitchString! "%%~a""
      )
    ) else (
      set /a TargetCount+=1
      set "TargetString=!TargetString! "%%~a""
      )
  
  )


endlocal & (
  
  set /a SwitchCount=%SwitchCount%
  set "SwitchString=%SwitchString%"
  
  set /a TargetCount=%TargetCount%
  set "TargetString=%TargetString%"
  
  )


exit /b 0

Thanks again for taking the time to point me in the right directions. big_smile

Offline

#6 01 Feb 2018 12:29

Pyprohly
Member
Registered: 26 Nov 2014
Posts: 37

Re: Return Array of Values?

I’ve misunderstood your setup. If the argument parser part is actually in a separate script then that “keep it global” advice suddenly became irrelevant. In this case you will need to return the results.

One way would be to output the results to stdout and have the parent script process this in a FOR /F loop.

The second way, as you’ve demonstrated in your revised script, would be to lose the array as to avoid the need to use delayed expansion in the return block.

Dheath wrote:

Even if I don't explicitly `endlocal`, the variables still seem to be cleared when the script ends. They are passed to scripts called from the script in which they are defined, but not to the script that calls them. Unless I'm missing something, maybe `endlocal` is implicit since I use `setlocal enabledelayedexpansion`? I am not clear on all the differences between `exit /b` and `goto :eof`, but I read that `goto :eof` does invoke `endlocal` automatically if `setlocal` was set. I didn't think `exit /b` would have the  same effect, but perhaps it does.

You’re correct about the implied `endlocal`. An implicit `endlocal` happens whenever you run `goto :eof`, `exit /b`, or when the script ends (as mentioned in `endlocal /?`), during any `setlocal`.

I’m just waiting for that day when someone finds a difference, but as far as I’m aware, `goto :eof` behaves exactly like `exit /b` in all cases.

Dheath wrote:
Pyprohly wrote:

Hmm. I don’t think `2>&1>NUL` does what you think it does.

Could you tell me what it actually does? I had some mixed results while I was tinkering, but it seems to work fine now. The only thing I'm not sure of is whether one expression can be used to redirect twice, or whether it would be more reliable to use `2>1 ` and `1>NUL` as two expressions separated by a space. My goal was to not ever print any output from the command.

It appears that `2>&1>NUL` is the same as `2>&1 >NUL`, which, if you’re looking to hide both the stdout and stderr streams then this doesn’t achieve that. It needs to be the other way around: `>NUL 2>&1`.

What `2>&1 >NUL` does is discard stdout, and redirect stderr to stdout. So stderr output is converted to stdout output and stdout output is discarded.

Dheath wrote:

Curiously, I encountered some trouble using the echo|findstr method when calling from another script's recursive dir listing. Only the recursive cases choked on the pipe. I ended up matching against a substring to get around this but I'm not sure what caused it. I don't think it's necessary to debug at this point, but perhaps this is a common issue?

I can’t quite visualise the situation. You’ll need to clarify this before I can make a comment.

Dheath wrote:

It seems like ' and ` are just interpreted as normal characters, but they do have a technical effect in the FOR /F mode as described on the ss64 page about that.

Precisely. Single quotes and back ticks are ordinary characters in a FOR loop, it’s only when you use a FOR /F loop that you have to choose your quotes wisely.

Last edited by Pyprohly (01 Feb 2018 12:31)

Offline

#7 09 Feb 2018 21:56

Sponge Belly
Member
From: Ireland
Registered: 01 Jan 2014
Posts: 10

Re: Return Array of Values?

Hi Dheath,

Read this Stack Overflow Answer by Dave Benham.

HTH! smile

Offline

Board footer

Powered by