Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get directory name from array in Batch [duplicate]

Tags:

batch-file

I have a list of paths from which I want to extract folder name

I wrote:

@echo off

set paths[0]="C:\p\test1" 
set paths[1]="C:\p\test2" 
set paths[2]="C:\p\test3"

(for %%p in (%paths%) do (
   for %%F in (%%p) do echo Processing %%~nxF
))

but seems that nothing is shown.

I expected to see:

Processing test1

Processing test2

Processing test3

like image 580
Snake Eyes Avatar asked Jan 10 '20 14:01

Snake Eyes


2 Answers

It makes a big difference if first " is specified on a set command line left to variable name or left to variable value. In most cases it is better to specify it left to the variable name, especially if a variable value holding a path should be concatenated later with a file name to a full qualified file name.

See also: Why is no string output with 'echo %var%' after using 'set var = text' on command line?

The solution for this task is:

@echo off

set "paths[0]=C:\p\test1"
set "paths[1]=C:\p\test2"
set "paths[2]=C:\p\test3"

for /F "tokens=1* delims==" %%I in ('set paths[ 2^>nul') do echo Processing %%~nxJ

The command FOR with option /F and a set enclosed in ' results in starting one more command process running in background with %ComSpec% /c and the command line specified between the two ' appended as further arguments. So executed is in this case with Windows installed to C:\Windows:

C:\Windows\System32\cmd.exe /c set paths[ 2>nul

The command SET outputs all environment variables of which name starts with paths[ line by line using the format VariableName=VariableValue to handle STDOUT of started background command process.

It could be that there is no environment variable of which name starts with paths[ which would result in an error message output to handle STDERR by command SET which would be redirected from background command process to handle STDERR of the command process which is processing the batch file and for that reason would be displayed in console window. For that reason a possible error message is redirected by the background command process to device NUL to suppress it with using 2>nul.

Read the Microsoft article about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded set command line with using a separate command process started in background.

FOR captures in this case everything written to handle STDOUT of started background command process and process this output line by line after started cmd.exe terminated itself.

Empty lines are ignored by FOR which does not matter here as there are no empty lines to process.

FOR would split up a non-empty line into substrings using normal space and horizontal tab as string delimiters and would assign just first space/tab separated string to specified loop variable, if it does not start with default end of line character ;. This default line splitting behavior is not wanted here. For that reason the option delims== defines the equal sign as string delimiter.

The option tokens=1* instructs FOR to assign in this case the variable name to specified loop variable I and assign everything after the equal sign(s) after variable name without any further string splitting on equal signs to next loop variable according to ASCII table which is in this case J. That is the reason why loop variables are interpreted case-sensitive while environment variables are handled case-insensitive by the Windows command processor.

In this case only the variable value is of interest in the body of the FOR loop. For that reason just loop variable J is used on ECHO command line while I is not used at all.

The modifier %~nxJ results in removing surrounding double quotes from string value assigned to loop variable J and next get the string after last backslash or beginning of string in case of the string value does not contain a backslash at all. This is the name of the last folder in folder path string.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • echo /?
  • for /?
  • set /?

UPDATE:

There is a big advantage of this solution in comparison to the other two solutions posted up to now here:

There is not used delayed environment variable expansion which is always problematic on working with file or folder names on not being 100% sure that no folder and no file contains ever an exclamation mark in its name.

Let us compare the three solutions with unusual folder names containing !.

@echo off
rem Make sure there is no environment variable defined of which name starts with
rem paths[ as suggested by Compo which is a very valuable addition on my code.
for /F "delims==" %%I in ('set paths[ 2^>nul') do set "%%I="

set "paths[0]=C:\p\test1!"
set "paths[1]=C:\p\!test2"
set "paths[2]=C:\p\!test!3"

echo/
echo Results of solution 1:
echo/
for /F "tokens=1* delims==" %%I in ('set paths[ 2^>nul') do echo Processing %%~nxJ

echo/
echo Results of solution 2:
echo/
SetLocal EnableDelayedExpansion
for /L %%i in (0,1,2) do (
    for %%j in (!paths[%%i]!) do echo Processing %%~nxj
)
endLocal

echo/
echo Results of solution 3:
echo/
Setlocal EnableDelayedExpansion
Call :process paths "!paths[0]!" "!paths[1]!" "!paths[2]!"
Endlocal
echo/
pause
goto :EOF

:process
Set P_C=0
Set /a P_C-=1

For %%a in (%*) DO (
    CALL :populate %1 "%%~a"
)

Set /a P_C-=1

For /L %%b in (0,1,!P_C!) DO (
    ECHO Processing %1[%%b] = "!%1[%%b]!"
)
GOTO :EOF

:populate
Set "%1[!P_C!]=%~2"
Set /a P_C+=1
GOTO :EOF

The output on running this batch file is:

Results of solution 1:

Processing test1!
Processing !test2
Processing !test!3

Results of solution 2:

Processing test1
Processing test2
Processing 3

Results of solution 3:

Processing paths[0] = "C:\p\test1\p\\p\3"

Solution 1 as posted here works for all three folder names correct.

Solution 2 omits for first and second folder name the exclamation mark which will most likely cause errors on further processing. The third folder name is modified to something completely different. Enabled delayed expansion results in parsing a second time echo Processing %%~nxj after %~nxj being replaced by !test!3 with interpreting test in folder name now as environment variable name of which value is referenced delayed. There was no environment variable test defined on running this batch file and so !test!3 became just 3 before echo was executed by Windows command processor.

Solution 3 produces garbage on any folder name contains an exclamation mark, even on full qualified folder name defined before enabling delayed expansion and referenced with delayed expansion on calling the subroutine process.

Well, folder and file names with an exclamation mark in name are fortunately rare which makes the usage of delayed expansion usually no problem. But I want to mention here nevertheless the potential problems which could occur on any folder name containing one or more !.

like image 155
Mofi Avatar answered Oct 22 '22 03:10

Mofi


Something like that should work :

@echo off
set paths[0]="C:\p\test1" 
set paths[1]="C:\p\test2" 
set paths[2]="C:\p\test3"

SetLocal EnableDelayedExpansion
for /L %%i in (0,1,2) do (
    for %%j in (!paths[%%i]!) do echo Processing %%~nxj
)
pause
like image 2
Hackoo Avatar answered Oct 22 '22 02:10

Hackoo