Windows command line tilde operators for full environment variables? - windows

In cmd, you can use the tilde "operator" to do some cool tricks with arguments passed in. For example, %~dp0 returns the pathname of the current script.
Can you do that for any environment variable? For example:
set foo=1234.exe
echo %~nfoo%
Is there a way to accomplish this?

You can also filter your variable through a for loop instead of a subroutine:
setlocal
set foo=1234.exe
for %%I in ("%foo%") do echo %%~nI

Yes, sort of.
In the following file (I named test1.cmd) there is an example of passing a file path and name to a subroutine in the same .cmd file and getting the drive letter and path back. By setting more environment variables on the last line of the subroutine you could return more combinations of drive letter, path, file name, attributes, etc.
That last line is the important part. The Windows command processor evaluates a line at a time, so that line first expands environment variables to their text values and then proccesses the line. That expanded line destroys the scope of the subroutine, returning to the outer scope (endlocal), sets a new variable (mytest2) to the value of the subroutine's (expanded) %dp1, then executes a goto :eof, which returns to the calling line.
setlocal
set mytest=c:\windows\a file with spaces in name.txt
call :mytest2 "%mytest%"
echo %mytest2%
:ender
endlocal
goto :eof
:mytest2
setlocal
echo %~dp1
endlocal && set mytest2=%~dp1 && goto :eof

Related

Why does my batch script not interpret a string correctly

In my second if statement, I want to filter out "tool" or "tool.bat" from the final list of filenames. However, the final list of filenames includes "tool" and total_bags is being incremented. I was wondering what I did incorrectly that's causing the program to not catch this case.
set /A total_bags=0
set target=%~1
if "%target%"=="" set target=%cd%
set LF=^
rem Previous two lines deliberately left blank for LF to work.
for /f "tokens=1 delims=. " %%i in ('dir /b /s /a:-d "%target%"') do (
set current_file=%%~ni
echo !unique_files! | find "!current_file!:" > nul
if NOT !ERRORLEVEL! == 0 (
if NOT !current_file! == "tool.bat" (
set /A total_bags=total_bags+1
set unique_files=!unique_files!!current_file!:
)
)
)
echo %unique_files::=!LF!%
echo %total_bags%
endlocal
The condition if NOT "%current_file%" == "tool.bat" as initially used does not work because of %current_file% is replaced already by current string of the environment variable current_file respectively an empty string on Windows command processor is processing the entire command block starting with ( and ending with matching ) before executing command FOR. That can be seen on debugging the batch file. See also Variables are not behaving as expected for a very good and short example explaining how the Windows command interpreter (CMD.EXE) parses scripts.
It is in general not advisable to assign the string already assigned to a loop variable to an environment variable which is not further modified inside a FOR loop. It would be better to use %%~ni everywhere in your code on which the current file name needs to be referenced.
The usage of delayed expansion requires enabling it with setlocal EnableDelayedExpansion (or with setlocal EnableExtensions EnableDelayedExpansion to enable explicitly also the command extensions enabled by default) as it is not enabled by default in comparison to the command extensions. Then the Windows command processor parses each command line a second time and expands !current_file! on execution of command IF.
But even if NOT !current_file! == "tool.bat" evaluates always to true for the batch file with name tool.bat because of set current_file=%%~ni results in assigned to the environment variable current_file only the string tool (file name without file extension) and the left string is not enclosed in double quotes while the right string is always enclosed in double quotes. The command IF does not remove the double quotes from right string before comparing the two strings.
The batch file in question misses also set unique_files= above the FOR loop to undefine explicitly the environment variable unique_files in case of being already defined by chance on starting the batch file, for example from a previous execution within a command prompt window.
Another problem with the batch file in question is that maximum string length of variable name + equal sign + string assigned to the environment variable is 8191 characters which is a problem on several thousands of file names are concatenated to a long string assigned to one environment variable like unique_files.
I suggest to use this batch file with comments explaining it.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Delete all environment variables of which name starts very unusual
rem with a question mark existing already by chance (with exception of
rem those environment variables with multiple question marks in name).
for /F "delims=?" %%I in ('set ? 2^>nul') do set "?%%I?="
rem Search with the string passed as first argument or simply within current
rem directory recursively for all files and define for each file name an
rem environment variable with a question mark at beginning and one more at
rem end of the variable name. A file name cannot contain a question mark.
rem The value assigned to the environment variable does not matter. As it
rem is not possible to define multiple environment variables with same name
rem and environment variable names are case-insensitive, there is just one
rem environment variable defined on multiple files have same file name.
rem The batch file itself is ignored because of the IF condition.
for /F "delims=" %%I in ('dir "%~1" /A-D /B /S 2^>nul') do if not "%%I" == "%~f0" set "?%%~nI?=1"
rem Initialize the file counting environment variable.
set "FileCount=0"
rem Output all file names which are the environment variable names sorted
rem alphabetically with the question marks removed and additionally count
rem the number of file names output by this loop.
for /F "eol=| delims=?" %%I in ('set ? 2^>nul') do set /A "FileCount+=1" & echo %%I
rem Output finally the number of unique file names excluding file extensions.
echo %FileCount%
rem Restore initial execution environment which results also in the
rem deletion of all environment variables defined during batch execution.
endlocal
It does not use delayed expansion and for that reason works also for file names containing one or more ! in file name which would be processed wrong on enabling delayed expansion on line set current_file=%%~ni because of the exclamation mark(s) in file name would be interpreted as begin/end of a delayed expanded environment variable reference.
There is defined an environment variable for each unique file name. The number of environment variables is limited only by the total available memory for environment variables which is 64 MiB. That should be enough even for several thousands of unique file names in the directory tree.
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.
call /? ... explains %~f0 which references full name of argument 0 which is the full qualified file name of the currently processed batch file and %~1 referencing first argument with perhaps existing surrounding " removed from argument string.
dir /?
echo /?
endlocal /?
for /?
if /?
rem /?
set /?
setlocal /?
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on the FOR command lines to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir or set command line with using a separate command process started in background with %ComSpec% /c and the command line within ' appended as additional arguments.

Unable to append to environment variable in batch file

I'm trying to append four directories to %pythonpath%.
The directories are:
C:\src\tensorflow\models\research
C:\src\tensorflow\models\research\object_detection
..\utils
..\mail
When user is User42, %pythonpath% is always set to:
;C:\src\tensorflow\models\research\object_detection;..\utils;..\mail
Why is the first path ignored/overwritten?
#echo off
if "%username%"=="User42" (
set pythonpath=%pythonpath%;C:\src\tensorflow\models\research
set pythonpath=%pythonpath%;C:\src\tensorflow\models\research\object_detection
) else (
:: Other path
)
:: This is common to all users
set pythonpath=%pythonpath%;..\utils;..\mail
echo %pythonpath%
Windows command processor replaces all environment variable references with syntax %VariableName% in a command block starting with ( and ending with matching ) during parsing phase of the next command line to execute on which a command block begins. In this case this means all %pythonpath% in both branches of the IF condition are substituted already by the current value of environment variable pythonpath before the IF condition is executed at all. This behavior can be seen by running the batch file without #echo off from within a command prompt window as in this case Windows command processor outputs the command lines after being parsed before execution.
The solution is using delayed expansion as also explained by help of command SET output on running in a command prompt window set /? on an IF and a FOR example, or avoiding the definition or modification of an environment variable and referencing it once again in same command block.
Here is a solution which works without usage of delayed expansion:
#echo off
set "Separator="
if defined pythonpath if not "%pythonpath:~-1%" == ";" set "Separator=;"
if /I "%username%" == "User42" (
set "pythonpath=%pythonpath%%Separator%C:\src\tensorflow\models\research;C:\src\tensorflow\models\research\object_detection"
) else (
rem Other path is added here to environment variable pythonpath.
)
rem This is common to all users. Variable pythonpath is defined definitely now.
for %%I in ("%CD%") do set "ParentPath=%%~dpI"
set "pythonpath=%pythonpath%;%ParentPath%utils;%ParentPath%mail"
echo %pythonpath%
It additionally makes sure there is not ;; in value of pythonpath in case of there is already a semicolon at end. And it makes sure pythonpath is not defined with a ; at beginning if this environment variable does not exist at all before first IF condition.
Further there are no relative paths added to pythonpath because of determining absolute path for ..\utils and ..\mails before appending them to pythonpath.
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 /?
if /?
rem /?
set /?
BTW: Invalid labels :: as comments should not be used in command blocks. That can result in undefined behavior on execution. It is safer to use command REM for comments.
%pythonpath% used twice between parentheses will be evaluated on read before execution begins.
This is why %pythonpath% has the same value on the 2nd set.
You can use call set to force evaluation on the variable with doubled %s.
This has a similar effect to enabledelayedexpansion seen in setlocal /?
and if /?.
#echo off
if "%username%"=="User42" (
set "pythonpath=%pythonpath%;C:\src\tensorflow\models\research"
call set "pythonpath=%%pythonpath%%;C:\src\tensorflow\models\research\object_detection"
) else (
:: Other path
)
:: This is common to all users
set "pythonpath=%pythonpath%;..\utils;..\mail"
echo %pythonpath%

How to delete quotation marks from a text file using batch file?

I am trying to edit out the quotation marks that have been inputed into a text file using .bat.
echo %name%>./User_Records/%username%.txt
in the text file it is saving as
"Firstname Lastname"
I am trying to add to the batch file so that it will edit the *.txt file and delete the quotation marks if they are saved in that text file.
Can anyone help me?
I have been trying to do this for weeks. I want the output to look like
Firstname Lastname
Try replacing all instances of " in the %name% variable by using Environment variable substitution (see set /? for more)
#echo off
set "name=%name:"=%"
echo %name%>./User_Records/%username%.txt
If you are trying to replace the quotation marks after the text file has been saved, then refer to this previous question
echo %name:"=%>.\User_Records\%username%.txt
should strip the quotes before they are recorded, if that's what your question is.
Note that path-separators in windows are \ not /.
But - if your question is about files that already exist then probably the easiest way is to use your editor. Depends a little on quite how many files you have to process - which you haven't specified.
The bat file you are using should really be altered, especially as the line you've provided from it has some issues, (mostly already mentioned).
SetLocal EnableDelayedExpansion
(Echo=!name:"=!)>"User_Records\%username%.txt"
EndLocal
If you have no control over that bat file then something like this should do what you want:
#For /F "UseBackQ Delims=" %%A In ("User_Records\%username%.txt") Do #Echo(%%~A
Alternatively:
#Set/P "FullName="<"User_Records\%username%.txt"
#Echo(%FullName:"=%
If the name read from text file is assigned to environment variable name using set name="..." syntax, then this is the cause of the double quotes in output string.
It makes a big difference if string assigned to environment variable is enclosed in double quotes or the entire parameter string of command SET. Read answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? to get knowledge about the big difference between using set "variable=string" versus set variable="string".
But it is of course possible to remove all double quotes from string assigned currently to environment variable name by using a string substitution:
set "name=%name:"=%"
All occurrences of " in string of name are replaced by an empty string and resulting string is assigned again to environment variable name.
But be aware that this command line
echo %name%>"./User_Records/%username%.txt"
could result in an unwanted behavior, for example if the name assigned to environment variable name is C&A. The ampersand found by Windows command interpreter after expanding environment variable name and before execution of command ECHO is interpreted now as AND operator and not anymore as literal character to output by ECHO.
One solution is using delayed expansion, for example:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Environment variable name is defined with delayed expansion disabled
rem making it possible to assign an exclamation mark as literal character
rem without the need to escape it as it would be necessary when delayed
rem expansion would be enabled already here at this time.
set "name=C&A!"
rem Enable delayed expansion which results in pushing on stack current
rem state of command extensions and of delayed expansion, the current
rem directory path and the pointer to current environment variables list
rem before creating a copy of all environment variables being used further.
setlocal EnableDelayedExpansion
rem Output the value of environment variable name using delayed expansion
rem with redirection into a text file being named like the currently used
rem user account name which of course can contain a space character or
rem other characters requiring enclosed file name with relative path in
rem double quotes.
echo !name!>".\User_Records\%username%.txt"
rem Delete all environment variables, restore pointer to previous set of
rem environment variables, restore current working directory from stack
rem restore states of command extension and delayed expansion from stack.
endlocal
rem Explicitly call ENDLOCAL once again for initial SETLOCAL command.
rem That would not be necessary because Windows command interpreter
rem runs implicitly ENDLOCAL for each local environment still being
rem pushed on stack.
endlocal
An alternate solution is using command FOR for an implicit delayed expansion:
#echo off
set "name=C&A!"
set "name=%name:"=%"
for /F "delims=" %%I in ("%name%") do echo %%I>".\User_Records\%username%.txt"
set "name=
See also the answers on Batch: Auto escape special characters.
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 /?
endlocal /?
for /?
set /?
setlocal /?
Read also the Microsoft article about Using Command Redirection Operators.

Extracting file name from array element in batch

I have an environment variable like this
set BINARY[0]=C:\binary.bin
From which I'm trying to extract the full file name
set "x=0"
:binloop
if defined BINARY[%x%] (
call echo %%BINARY[%x%]%%
FOR %%i IN ("%%BINARY[%x%]%%") DO (
set FNAME=%%~nxi
)
set /a "x+=1"
GOTO binloop
)
rem ...
However for some reason, it tries to do:
set FNAME=%BINARY[0]%
instead of
set FNAME=binary.bin
What's wrong with the code and why?
Open a command prompt window, run set /? and read the output help pages explaining when and how to use delayed expansion in a code block for the commands IF and FOR.
%% in a batch file is interpreted as literal percent character which is the reason why a loop variable in a command executed directly in a command prompt window must be specified with just one percent sign while the same loop in a batch file requires two percent signs on referencing the loop variable.
When the Windows command processor encounters an opening parenthesis which marks the beginning of a command block, it searches for the matching closing parenthesis and replaces all environment variables references with syntax %VariableName% by the current value of the variable or nothing in case of variable does not exist. Then after the entire command block was parsed the IF or FOR is executed and used is once or more times the already preprocessed command block.
You could use
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "BINARY[1]=C:\binary1.bin"
set "BINARY[0]=C:\binary0.bin"
set "x=0"
:binloop
if defined BINARY[%x%] (
call echo %%BINARY[%x%]%%
for %%i in ("!BINARY[%x%]!") do (
set FNAME=%%~nxi
set FNAME
)
set /a "x+=1"
goto binloop
)
endlocal
which outputs
C:\binary0.bin
FNAME=binary0.bin
C:\binary1.bin
FNAME=binary1.bin
The command line
call echo %%BINARY[%x%]%%
is something special. This line is preprocessed before execution of command IF to
call echo %BINARY[0]%
respectively on second run to
call echo %BINARY[1]%
By usage of command CALL the single command line is processed like a subroutine or another batch file which means the line is preprocessed once more resulting in execution of
echo C:\binary0.bin
and on second run in execution of
echo C:\binary1.bin
which is the reason why the output is as expected here. But there is no double preprocessing for the environment variable reference in FOR.
Much better would be most likely the following code:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "BINARY[1]=C:\binary1.bin"
set "BINARY[0]=C:\binary0.bin"
for /F "tokens=1* delims==" %%I in ('set "BINARY[" 2^>nul') do (
set "FNAME=%%~nxJ"
set FNAME
)
endlocal
The command set outputs all variables with their name and equal sign and their values which start with the specified string when there is whether parameter /A or /P used and the parameter does not contain an equal sign in an alphabetically sorted list. So the output of
set "BINARY[" 2>nul
as used in the command FOR is
BINARY[0]=C:\binary0.bin
BINARY[1]=C:\binary1.bin
which is processed by the FOR loop which splits each line into two strings based on first occurrence of the equal sign because of tokens=1* delims==. The first string is the variable name assigned to loop variable I. And the second string is everything after first equal sign assigned to loop variable J being the next character in ASCII table.
2>nul is used to suppress the error message output by command SET to STDERR by redirecting it to device NUL if there is no environment variable defined with a name starting with BINARY[ in any case. The redirection operator > must be escaped with ^ as otherwise command processor would exit batch processing on this line because of 2>nul resulting in a syntax error on FOR command line at this position.
Note: Because of alphabetically sorted output by command SET the environment variable BINARY[10] is output after BINARY[0] and before BINARY[1] and BINARY[2]. So if the order is important, the first batch solution is needed or the environment variables are created with number in square brackets have all same number of digits with leading zeros, i.e. 00000, 00001, ..., 00002, 00010, 00011, ...
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.
call /?
echo /?
endlocal /?
for /?
goto /?
if /?
set /?
setlocal /?
And see also Microsoft article about Using command redirection operators.

cmd for loop mass renaming again oneliner

I'm over my head with this - spent too much time searching already - evidently I don't understand the basics of CMD variables etc. - and it always gives me such a headache
why wouldn't this work?
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
the above code outputs the value of %tmpx% in some other scope - and it is always constant
yes, i run setlocal ENABLEDELAYEDEXPANSION
basically i need to do a simple rename of all files in folder from constantstring_somenameXX.tif to somenameXX.tif, where i.e. constantstring=0000000005
i had to use set because other posts rightly suggested that %a in a for loop has a special behaviour, and the substitutions wouldn't work for it as it is.
i would prefer not to use scripts and/or powershell - unless not using them is impossible
thank you
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
The problem with the previous code is delayed expansion. Yes, you enabled it, but you have not used it, and depending on how you enabled it, it will not work
In cmd, when a line or block of lines (code inside parenthesis) is reached, it is first parsed and then executed. During the parse phase, variable read operations are removed from the command, replaced with the value in the variable before the command starts to execute. So, if you change the value of a variable inside a line/block you can not retrieve the changed value inside the same line/block as there are no variable reads (they were replaced)
setlocal enabledelayedexpansion allows to replace (where needed) the variable read syntax from %var% to !var!, indicating to the parser that the read operation will be delayed until the execution phase.
So, in your case, your code should have been something like
setlocal enabledelayedexpansion & for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )
BUT this will not work (in default configured environments).
cmd has two execution modes: batch file and command line. In your case, you are using command line (no escaped percent sign in for loop) and in command line mode the setlocal enabledelayedexpansion will not work. It is intended for batch files (see setlocal /?)
How to make it work from the command line? By default cmd is started with delayed expansion disabled and you can not enable it if not inside a batch file. But you can start cmd with delayed expansion enabled and run your command in this started instance (see cmd /?)
cmd /v:on /c "for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )"
Anyway, to solve your rename problem, delayed expansion is not needed
for %a in (*_*.tif) do for /f "tokens=1,* delims=_" %b in ("%~nxa") do echo ren "%a" "%c"
That is, for each tif file with an underscore, take the name and extension of the file (%~nxa) as a string, and using the underscore as a delimiter between tokens, retrieve the first token (the text on the left of the first underscore) in %b and the rest of the text (to the right of the underscore) into %c. Now, just rename the original file name (stored in %a) to the contents of %c (the text on the right of the underscore)
In this code rename operations are only echoed to console. If the output is correct, remove the echo command.
! is the character to use rather than % when wanting execution time value. % does when it's read value.
CMD was written by IBM engineers and they were trying to make MSDos a programming language while making sure Dos commands ran the same. So we get a hodge podge.
& seperates commands on a line.
&& executes this command only if previous command's errorlevel is 0.
|| (not used above) executes this command only if previous command's errorlevel is NOT 0
> output to a file
>> append output to a file
< input from a file
| output of one command into the input of another command
^ escapes any of the above, including itself, if needed to be passed to a program
" parameters with spaces must be enclosed in quotes
+ used with copy to concatinate files. E.G. copy file1+file2 newfile
, used with copy to indicate missing parameters. This updates the files modified date. E.G. copy /b file1,,
%variablename% a inbuilt or user set environmental variable
!variablename! a user set environmental variable expanded at execution time, turned with SelLocal EnableDelayedExpansion command
%<number> (%1) the nth command line parameter passed to a batch file. %0 is the batchfile's name.
%* (%*) the entire command line.
%<a letter> or %%<a letter> (%A or %%A) the variable in a for loop. Single % sign at command prompt and double % sign in a batch file.

Resources