Sonntag, 25. November 2007

BATCH_ input parameter with Ampersand

The Ampersand

What is the ampersand? The ampersand (or And-sign, '&') is used for conditinal execution in batch scripts. So ECHO one & ECHO two will give you the output
one
two

The problem with the ampersand is, that it is a special character that can occur in paths and filenames (other characters like >, <, : etc. are not allowed!) passed to batched scripts. Of course, these characters can occur in user input, when reading text files etc, but that's not the point here because I want to focus on the ampersand only.
When it occurs in a batch script parameter, it will be either quoted or escaped:
:: Standard file
C:\>script.cmd file1.txt
:: File with spaces needs quotes
C:\>script.cmd "file 1.txt"
C:\>script.cmd "C:\Documents and Settings\file1.txt"
:: same applies to the ampersand occuring in paths
C:\>script.cmd "C:\files & more\file.txt"
:: but an ampersand itself can be also escaped
C:\>script.cmd files^&more


Batch script parameters

You can refer to parameters to your batch script with %1 to %9. You will get the parameter just as it was entered. You will not "see" the carets (^) of an escaped string. So if the first parameter entered is "C:\files & more\file.txt", an ECHO will give you exactly that phrase:
ECHO My input is %1.
results in
My input is "C:\files & more\file.txt".

You can also assign the parameter's value to an environment variable with the statement SET PARAM=%1. But what can we do about the annoying quotes around the string? The batch allows us multiple parameter substitutions, which all look like %~[op]n. So %~d1 gives us the drive name of the first parameter regarding it as an absolute or relative file path. All substitutions remove the optional surrounding quotes; if you just want to remove the quotes, you use %~1 without any operator.

But if the string contains an escaped ampersand or you remove the surrounding quotes from a string with an ampersand, that character will be treated by the interpretor as the conditional command execution character which will obviously break you script.

Parsing and executing a batch script

The way the batch interpretor works itself through a batch file is very special. In short this looks like this:
  1. Read a line. Round brackets spanning lines count as a single line.
  2. Expand all environment variables and batch script parameters by replacing them through their values. Optional substitution operations can be performed.
  3. Parse the line, identify commands, strings, operators etc.
  4. Perform delayed variable expansion on environment variables if enabled. Optional substitution operations can be performed.
  5. Execute all identified commands in order.
I assume you are already familiar with round brackets and substitution operations, so I will only explain the delayed variable expansion. Variables to be delayed expanded are written with exclamation marks instead of percent signs:
ECHO This %VARIABLE% expands before this !VARIABLE!.
Because the expansion takes place AFTER parsing, all special characters (including ampersand, caret, pipe etc.) will be indirectly escaped. That's why you can use this technique to handle strings with ampersands!
You enable delayed variable expansion with
SETLOCAL ENABLEDELAYEDEXPANSION


Working with Variables

As I told above, parameters with ampersand easily break your script. So you have to either quote or escape your parameters when using them. Just - you cannot escape parameters in a script, that only works for variables! Alright, let's quote - but what if a parameter is already quoted?
I told you above that through parameter substitution you can remove the quotes. With %~1 you are left with an unquoted string possibly containing one ore ampersands waiting to break your script. So ALWAYS use something like
ECHO "This is my first parameter: %~1."
SET PARAM="%~1"
IF "%~1" == "a" ECHO First parameter is a!

Okay, now you have a quoted string - but that sometimes sucks like in the ECHO line, because that command also echoes the quotes.
Aaaahh, yes, I wrote something about substitution operations.
SET STR="%~1"
ECHO This is my parameter: %STR:~1,-1%.
That removes the first and the last character, which are my manually added quotes. But dammit, it's unquoted now which breaks the script again! Hmmm, wait, there was something about "delayed" variable expansion after parsing? Right, that's the way to use unquoted strings WITH ampersands! As the variables are expanded AFTER parsing, special characters won't break your code because they will always appear as strings.
SET STR="%~1"
ECHO This is my parameter: !STR:~1,-1!.
Assignment works as well:
SET STR="%~1"
SET STR=!STR:~1,-1!
An now we got it: The variable STR contains the unquoted input parameter string. So if you want to use that variable, you either have to quote it again or use delayed expansion:
ECHO The variable STR contains the value !STR!.
ECHO "The variable STR contains the value %STR%".

Hmm, but what about when one actually cannot use delayed expansion? For example when using subroutines with local variables of which one should be left after return?
:MY_SUBROUTINE
SETLOCAL
.... assign many variables here etc. ....
ENDLOCAL & SET STR="%STR:"=^"%"& SET STR=!STR:~1,-1!
GOTO :EOF
This special technique to allow both local and global context needs two SET statements (note the missing space between quote and ampersand!!!)? That looks really bad, but is in fact the only clean solution.
However, if you are sure that the only special character contained in that string is the ampersand (e.g. it is an original file path), you can manually escape that string:
ENDLOCAL & SET STR=%STR:&=^&%
That has the same effect like above, but only for the ampersand!

Notes

  • The %0 parameter (storing the script name) can also be placed in a path containing ampersands. So the rules stated above also apply to this parameter.
  • Avoid using %*. It merges all parameters into one single string. That means, it can contain quoted parameters, so you cannot quote %*, and it can contain unquoted, escaped parameters, so you must use quotes. You see, there is no way handling it correctly, so you should use the shift command instead.
  • A common IF expression is
    IF "%TEST%" == "abcd" GOTO :DO_IT
    This works unless the variable contains double quotes. You could use other characters like [] to surround your variables in case they are empty, however this would still break in case of an ampersand. So here, you can use two equitable construtions: escape the quotes or use delayed variable expansion:
    IF "%TEST:"=^"%" == "abcd" GOTO :DO_IT
    IF "!TEST!" == "abcd" GOTO :DO_IT
  • As for FOR constructs, you should be very careful and use delayed expansion and quotation whenever possible. Sometimes you have to work the right solution out by trial and error.
Diego (Gast) - 10. Okt, 12:33

Ampersand & RunAs command

Hi!
Your post was one the best I found in Google.
But I'm still cannot figure out how to do this:

runas /u:%user% "cmd /c %1"

%1 contains "&" symbol
Example: "X:\path\A & B.lnk"

I'll really appreciate your help.
Diego

xdmvDELETE-THIS[at]yahoo.com

cypressor - 22. Okt, 22:10

Re: Ampersand & RunAs command

Hi Diego,

the Batch replaces your variable %1 and after that it parses the command. As the ampersand has a higher weight than the quotes it breaks down your input into the two commands runas /u:username "cmd /c X:\path\A and B.lnk"

To avoid this, you have to escape the ampersand in your parameter:
set temp="%~1"
runas /u:%user% "cmd /c %temp:&=^&%"

Please note:
  1. You cannot use replace (%<var>:<search>=<replacement>%) on parameters, so you have to assign it to a local variable first.
  2. To assign the variable correctly, the value has to be quoted. To avoid double quoting (if the parameter is already quoted), the quotes are being removed using the tilde (~).
Hope that I could be of any help.
Squash (Gast) - 17. Feb, 22:36

Exclamation

Gave your idea a try thinking the same would hold true for an exclamation.
SET STR="%~1"
ECHO This is my parameter: !STR:~1,-1!.

But if your data has an exclamation in it, it will get rid of the exclamation in the data. The only way to echo an exclamation is to turn off expansion. But I can't do that because I have to use it to do other things in my batch file.

I have a batch file that combines all files into one file but at the same time it appends the original filename it came from to the end of each line.

This is the main portion of my batch file
@echo off & setlocal enableextensions enabledelayedexpansion
Set _Opsheet=appendlog.log
set _Tcount=0
FOR /F "tokens=*" %%A IN ('dir /b /a-d *.TXT') DO (
set _count=0
set _Tfile=%%~nA .
set _Filename=!_Tfile:~0,15!
echo "Working on file: %%~nA"
FOR /F "Delims=" %%B IN (%%A) DO (
set /a _count+=1
set _tmpstr="%%B"
echo !_tmpstr:~1,-1!!_Filename! >> FILES_COMBINED.tmp
)
echo.!_Filename! count is !_count! >>%_Opsheet%
set /a _Tcount=!_Tcount!+!_count!
)
echo.Input Count is !_Tcount! >>%_Opsheet%

cypressor - 12. Jun, 20:57

Re: Exclamation

Try use SETLOCAL around the echo:

SETLOCAL DISABLEDELAYEDEXPANSION
ECHO.%_tmpstr:~1,-1%%_Filename% >> FILES_COMBINED.tmp
ENDLOCAL
jeb (Gast) - 26. Aug, 21:37

Output of exclamation marks

Exclamation marks CAN also be output with enabled delayed expansion.
The problem in your case is not the output, it's the assignment!
set "STR=%~1"
First the %1 expands to ex. "Hello!"
But then the parser tries to expand the single exclamation mark, this results in removing of the exclamation mark.

So it is enough to enable the delayed expansion after the SET-statement.

SET "STR=%~1"
setlocal EnableDelayedExpansion
ECHO This is my parameter: !STR:~1,-1!.

Reflog

Informationstechnische Howtos, Hinweise und Merkwürdiges

Batchlib v1.0 2008-03-29

Aktuelle Beiträge

HOWTO_ O2 DSL Surf &...
Der O2 DSL Surf & Phone-Router ist für die alleinige...
cypressor - 12. Feb, 19:57
Uptweak Windows XP Home...
There are a lot of annoying limitations in Windows...
cypressor - 9. Okt, 19:30
BATCHLIB_ Batchlib package...
Download Batchlib package v1.0 (5 KB zip file) What...
cypressor - 29. Mär, 19:10
BATCHLIB_ Batchlib library...
The batchlib library string.cmd is part of the batchlib...
cypressor - 29. Mär, 18:10

Homepage Ticker

Links

Status

Online seit 6579 Tagen
Zuletzt aktualisiert: 28. Jun, 11:32
RSS XML 1.0 Button-Get-Firefox

batch
batchlib
howto
tech
video
Profil
Abmelden
Weblog abonnieren