If variable can't have spaces? - windows

I am making an experimental program in batch for a simple chatting interface. In this one, I made a function where if there is the word r placed in chat, it ignores it and just redisplays the text file again. It works fine if I put r and it just refreshes, and if I put one word it works fine, but if I put a word and a space and another word, it breaks and shows the following error:
Chat(Put r for refresh):hey hi
hi was unexpected at this time.
Does anyone know how to fix this? Thanks.
Code:
#echo off
cls
cd %USERPROFILE%\Desktop\Chat
for /f "delims=" %%A in (chat.txt) do (
set %%A
)
echo %chatt%
echo %chatp%
echo %chatn%
cd %USERPROFILE%\Desktop\Chat\Servers\%chatt%
:1
cls
type %chatn%.chat
set /p in=Chat(Put r for refresh):
if %in% == r goto 1
echo %chatp%: %in%>>%chatn%.chat
goto 1

The usual way to deal with spaces in a string variables contents is to wrap it in quotes. This is the case here. When you use the variables contents with %in% the contents are inserted verbatim, so the suspect line would look like this:
if hey hi == r goto 1
It starts off okay if hey but then instead of seeing a comparison operator like == it sees hi and chokes. So wrap it all in quotes:
if "%in%" == "r" goto 1
That way it will be interpreted like
if "hey hi" == "r" goto 1
and the bat engine will know that "hey hi" should be treated as one entity.

Related

How to compare characters in Batch script when left hand side is double quote

I have Windows Batch code where I'm trying to test if the first character of value is a hyphen but the code fails when the first character of value is a double quote. (value is coming from elsewhere in the real code.)
set value=""This is a test""
set arg="%value%"
set prefix=%arg:~1,1%
if "%prefix%" == "-" ...
Evaluates to
if """ == "-"
and generates The syntax of the command is incorrect. I tried inserting a caret into both sides of the equality check
if "^%prefix%" == "^-" ...
but that generates the same error with
if "^"" == "^-"
You can use the caret to escape a single character, like
if ^"^%prefix%^" == "-" ...
The trick is to escape the quotes, too, else the inner caret has no special meaning.
This can't work for more than one character, but you could switch to the even simple delayed expansion. It's expansion is always safe.
setlocal EnableDelayedExpansion
set "value="This is a test""
set "arg=!value!"
set "prefix=!arg:~1,1!"
if "!prefix!" == "-" ...
test if the first character of value is a hyphen
Another approach (using the original string, no additional variables):
#echo off
setlocal EnableDelayedExpansion
set value1=-correct
set value2=xfail
set value3="fail
set value
echo ---------
echo !value1!|findstr /bc:"-" >nul && echo hyphen || echo something else
echo !value2!|findstr /bc:"-" >nul && echo hyphen || echo something else
echo !value3!|findstr /bc:"-" >nul && echo hyphen || echo something else

How to remove comments from text file

My text file contains one line comments that all being with "// ". Two forward slashes and a space. These may either take up the whole line or just the last part of a line. Each comment does not extend beyond the line that it's on. So no /* */ type comments crossing multiple lines.
In simple terms, all comments start with "//space" anywhere on the line. Anything starting with "//space" should be removed and trailing spaces on that line should also be removed. Leading spaces should stay. Any blank lines should be removed.
Sample file:
// This is a comment
x = 1 // This is also a comment after the double slash
x = 2
x = 3 // The above is a blank line
// Comment on this record but nothing precedes it, so should be deleted.
y = 4 // A line with leading spaces that should be kept.
z = "//path"; // The first double slashes are not a comment since the space is missing after the "//"
// Last comment line.
Result file (no trailing spaces, but keep leading spaces.:
x = 1
x = 2
x = 3
y = 4
z = "//path";
I can remove the blank lines using gc file.txt | Where-Object { $_ -ne ''} > result.txt. However I'm having trouble with reading just the beginning part of a line up to the "//" comment part.
I also tried findstr but haven't found how to read each line up to the "//" and then trim spaces out.
I could write a script program to loop throught the file and do this, but it seems like there should be a way to accomplish it using a simple one or two line powershell or bat file command.
What is the easiest way (shortest amount of code) to remove these comments while keeping the uncommented contents of the file?
Since you seem to equate "easy" with "short", here's a fairly simple solution:
gc .\samplefile.txt|%{$_-replace"(.*)(// .*)",'$1'}|?{$_}
if it's really that important to you :-)
A bit more verbose version (still using regex):
Get-Content .\samplefile.txt | Where-Object {
-not ([String]::IsNullOrEmpty($_.Trim()) -or $_-match"^\s*// ")
} |ForEach-Object { $_ -replace "(.*)(// .*)",'$1' }
That being said, I would (personally) go for a more verbose and easier-to-read/maintain solution:
To remove everything after //, the easiest way is to find the first occurrence of // with String.IndexOf() and then grab the first part with String.Substring():
PS C:\> $CommentedString = "Content // this is a comment"
PS C:\> $CommentIndex = $CommentedString.IndexOf('// ')
PS C:\> $CommentedString.Substring(0,$CommentIndex)
Content
For the indented comments you can also use String.Trim() to remove whitespace from the beginning and end of the string:
PS C:\> " // Indented comment" -match '^//'
True
You can use the ForEach-Object cmdlet to go through every line and apply the above:
function Remove-Comments {
param(
[string]$Path,
[string]$OutFile
)
# Read file, remove comments and blank lines
$CleanLines = Get-Content $Path |ForEach-Object {
$Line = $_
# Trim() removes whitespace from both ends of string
$TrimmedLine = $Line.Trim()
# Check if what's left is either nothing or a comment
if([string]::IsNullOrEmpty($TrimmedLine) -or $TrimmedLine -match "^// ") {
# if so, return nothing (inside foreach-object "return" acts like "coninue")
return
}
# See if non-empty line contains comment
$CommentIndex = $Line.IndexOf("// ")
if($CommentIndex -ge 0) {
# if so, remove the comment
$Line = $Line.Substring(0,$CommentIndex)
}
# return $Line to $CleanLines
return $Line
}
if($OutFile -and (Test-Path $OutFile)){
[System.IO.File]::WriteAllLines($OutFile, $CleanLines)
} else {
# No OutFile was specified, write lines to pipeline
Write-Output $CleanLines
}
}
Applied to your sample:
PS C:\> Remove-Comments D:\samplefile.txt
x = 1
x = 2
x = 3
Like a great many text processing problems, there is a simple solution using JREPL.BAT - a powerful regex text processing utility for the Windows command line. It is pure script (hybrid JScript/batch) that runs natively on any Windows machine from XP onward. Full documentation is embedded within the script.
jrepl "^(.*?)\s*// " "$1!=''?$1:false" /jmatch /f test.txt /o out.txt
You can overwrite the original file by specifying - as the output file:
jrepl "^(.*?)\s*// " "$1!=''?$1:false" /jmatch /f test.txt /o -
I've tested, and it gives the exact output you are looking for.
If you put the command within a batch script, then you must use call jrepl
Tha Batch file below do what you want. Sorry, but there is not an "easy short code" way to do this...
#echo off
setlocal EnableDelayedExpansion
rem Set the maximum number of trailing spaces as a power_of_2-1 value. For example, for 15 spaces:
set spcPow2=4
set "spaces= "
for /L %%i in (1,1,%spcPow2%) do set "spaces=!spaces!!spaces!"
set /A spcPow2-=1
rem Process all lines, excepting empty ones and lines that start with "/"
setlocal DisableDelayedExpansion
for /F "eol=/ delims=" %%a in (test.txt) do (
set "line=%%a"
rem Split line at "// " and get the first part
setlocal EnableDelayedExpansion
for /F "delims=¡" %%b in ("!line:// =¡!") do (
endlocal
set "line=%%b"
)
rem Eliminate trailing spaces
setlocal EnableDelayedExpansion
set spc=0
for /L %%b in (%spcPow2%,-1,0) do (
set /A "newSpc=spc+(1<<%%b)"
for %%n in (!newSpc!) do if "!line:~-%%n!" equ "!spaces:~-%%n!" set "spc=%%n"
)
if !spc! gtr 0 for %%n in (!spc!) do set "line=!line:~0,-%%n!"
rem Show resulting line
if defined line echo !line!
endlocal
)
EDIT: New solution added
#set #x=1 // & CScript //nologo //E:JScript "%~F0" < samplefile.txt & goto :EOF
WScript.Stdout.Write(WScript.Stdin.ReadAll().replace(/(.*)\/\/ .*/g,"$1"))
Copy previous code into a file with .BAT extension, that is, it is a Batch file!

Something like a function/method in batch files?

is there anything that mimicks a method like one knows it from Java, C# etc.? I have 5 lines of commands in a batch file, those 5 lines are used at more than one place inside the batch file. I can't use a goto, because depending on the errorlevel created by those 5 lines I have different actions that follow. I tried putting my 5 lines inside a batch file 5lines.bat, but the original batch file original.bat only calls 5lines.bat and doesn't execute the commands after the call to 5lines.bat ): That's how my original.bat looks like:
5lines.bat
echo this gets never called, how to make sure this gets called?
There's no exit or something like this in 5lines.bat! How can I make sure the line after 5lines.bat gets called?
You could use the call command :
call:myDosFunc
And then define the function this way :
:myDosFunc - here starts the function
echo. here the myDosFunc function is executing a group of commands
echo. it could do a lot of things
goto:eof
Source : Batch Functions
Just for completeness, you can also pass parameters to the function:
Function call
call :myDosFunc 100 "string val"
Function body
:myDosFunc
echo. Got Param#1 %~1
echo. Got Param#2 %~2
goto :eof
Placing the reusable functions into a separate batch file would certainly work to simulate a function.
The catch is that you have to use the call command in order to ensure that control returns to the caller after the second batch file finishes executing.
call 5lines.bat
echo this will now get called
Solution:
#ECHO OFF
call:header Start Some Operation
... put your business logic here
... make sure EXIT below is present
... so you don't run into actual functions without the call
call:header Operation Finished Successfully
EXIT /B %ERRORLEVEL%
:: Functions
:header
ECHO =================================================
ECHO %*
ECHO =================================================
EXIT /B 0
Important to put EXIT /B at the end of each function, as well as before function definitions start, in my example this is:
EXIT /B %ERRORLEVEL%
You could try to use the examples listed on DOS Batch - Function Tutorial
Alternatively, you could put the common lines into another batch file that you call from the main one
Coming from a Java background, I have tried to incorporate some familiar conventions when creating procedures for .bat scripts.
The script below demonstrates the definition of two procedures.
#ECHO OFF
SET firstInstanceVariable="Hello world!"
SET secondInstanceVariable="Good bye world!"
GOTO:MAIN
:firstMethodName
SETLOCAL ENABLEDELAYEDEXPANSION
SET firstArgumentPassedIn=%~1
SET secondArgumentPassedIn=%~2
ECHO %firstInstanceVariable%
ECHO "The first argument passed in was %firstArgumentPassedIn%"
ECHO "The second argument passed in was %secondArgumentPassedIn%"
ENDLOCAL
EXIT /B 0
:secondMethodName
SETLOCAL ENABLEDELAYEDEXPANSION
SET firstArgumentPassedIn=%~1
SET secondArgumentPassedIn=%~2
ECHO %secondInstanceVariable%
ECHO "The first argument passed in was %firstArgumentPassedIn%"
ECHO "The second argument passed in was %secondArgumentPassedIn%"
ENDLOCAL
EXIT /B 0
:MAIN
call:firstMethodName "The Quick Brown" "Fox Jumps Over"
call:secondMethodName "1 2 3 4" 3.14
Notice that an explicit GOTO:MAIN is necessary to skip over the procedure definitions.
This is because you must skip over the procedure before deciding to read it. Otherwise, the procedure will be executed.
The code below demonstrates a close Java-equivalent of the above .bat script.
public class MyObject {
private String firstInstanceVariable = "Hello world!";
private String secondInstanceVariable = "Good bye world!";
public void firstMethodName(Object... arguments) {
String firstArgumentPassedIn = arguments[0].toString();
String secondArgumentPassedIn = arguments[1].toString();
System.out.println(firstInstanceVariable);
System.out.format("The first argument passed in was %s", firstArgumentPassedIn);
System.out.format("The second argument passed in was %s", secondArgumentPassedIn);
}
public void secondMethodName(Object... arguments) {
String firstArgumentPassedIn = arguments[0].toString();
String secondArgumentPassedIn = arguments[1].toString();
System.out.println(secondInstanceVariable);
System.out.format("The first argument passed in was %s", firstArgumentPassedIn);
System.out.format("The second argument passed in was %s", secondArgumentPassedIn);
}
public static void main(String[] args) {
MyObject myObject = new MyObject();
myObject.firstMethodName("The Quick Brown", "Fox Jumps Over");
myObject.secondMethodName(new Integer[]{1,2,3,4}, 3.14);
}
}
Here's a 'hack' that will allow you to have "anonymous" functions in batch files:
#echo off
setlocal
set "anonymous=/?"
:: calling the anonymous function
call :%%anonymous%% a b c 3>&1 >nul
:: here the anonymous function is defined
if "%0" == ":%anonymous%" (
echo(
echo Anonymous call:
echo %%1=%1 %%2=%2 %%3=%3
exit /b 0
)>&3
::end of the anonymous function
The anonymous function block should be placed right after the call statement and must end with exit statement
the trick is that CALL internally uses GOTO and then returns to the line where the CALL was executed. With the double expansion GOTO help message is triggered (with %%/?%% argument) and then continues the script. But after it is finished it returns to the CALL - that's why the if statement is needed.
For another great tutorial on writing reusable batch file code -- see Richie Lawrence's excellent library.
I'm not sure if it was obvious from other answers but just to be explicit I'm posting this answer. I found other answers helpful in writing below code.
echo what
rem the third param gives info to which label it should comeback to
call :myDosFunc 100 "string val" ComeBack
:ComeBack
echo what what
goto :eof
:myDosFunc
echo. Got Param#1 %~1
echo. Got Param#2 %~2
set returnto=%~3
goto :%returnto%
Below may make it look like a function.
call :myFunc %para1% %para2%
:myFunc <para1> <para2>
echo %1
echo %2
EXIT /B
Example
#echo off
echo PROGRAM_NAME:%~nx0 Start
echo.
================================
SET debugMode=%1
call :myFunc1 %debugMode%
call :myFunc2 para1 "para2 hello"
================================
echo PROGRAM_NAME:%~nx0 End & pause>nul
EXIT /B
:: 👇 define the function under below
:myFunc1 <isDebug>
:: So that you can know the %1 means: isDebug.
if "%1" == "1" (
echo debug mode
)
EXIT /B
:myFunc2 <para1> <para2>
:: output: para1
echo %1
:: output: "para2 hello"
echo %2
EXIT /B

Filtering file using reg exp and concatenate certain lines together (command-prompt)

I have to filter a text file filter.tmp containing two types of lines, this shows the difference:
findstr /r "^[0-9][0-9]*.*$" filter.tmp > filter-numbers.tmp
findstr /r "^[^0-9][^0-9]*.*$" filter.tmp > filter-text.tmp
What I need to do is to append lines containing text together like this and if line does contain number just put it to output file:
IF "current line" contains text THEN
previous line = concatenate "previous line" + "/" + "current line"
ELSE
echo "previous line" >> filter.out
echo "current line" >> filter.out
filter.tmp contains something like:
Hello
World
Foo
Bar
45: this is some line
Trouble
with code
66: another line
filter.out should look like:
Hello/World/Foo/Bar
45: this is some line
Trouble/with code
66: another line
I realize, this is very simple, but I just can not get it working. As I am thinking about it, it would be much easier to use C++....
This is a quite verbatim translation of your pseudocode and your regexes, based on the assumption that »contains numbers« really means »starts with two digits« (which is what your regexes show):
#echo off
setlocal enabledelayedexpansion
set Prev=
for /f "delims=" %%x in (filter.tmp) do (
set "Line=%%x"
if "!Line:~0,2!" GEQ "00" if "!Line:~0,2!" LEQ "99" (
if not "!Prev!"=="" (>>filter.out echo !Prev!)
>>filter.out echo !Line!
set Prev=
) else (
if "!Prev!"=="" (set "Prev=!Line!") else (set "Prev=!Prev!/!Line!")
)
)
if not "!Prev!"=="" (>>filter.out echo !Prev!)
This uses several things. First of all, we need delayed expansion which enables us to manipulate environment variables within the loop. Then we iterate over the lines in the file with for /f. Note that this will skip empty lines in the file, but you cannot avoid that. Inside the for /f loop the variable Line holds the current line and Prev the previous one (if there has been a previous one). I swapped the then and else branches of the condition since numbers at the start of the line are easier to check for than non-numbers.
With the echo you'll notice that I moved the redirection to the start of the line; this is to prevent trailing numbers in Prev or Line from having an effect on the redirection (and also to avoid trailing spaces).
If you're not adverse to PowerShell, you can use the following:
$(switch -Regex -File filter.tmp {
'^\D' { if ($prev) { $prev += "/$_" } else { $prev = $_ } }
'^\d{2}' { if ($pref) {$prev}; $_; $prev = '' }
}
if ($prev) { $prev }
) | Set-Content filter.out

What syntax will check if a variable name containing spaces is defined?

Windows user defined environment variable names can contain any character except =.
Special characters can be included by escaping them. A simpler method is to simply enclose the entire SET expression within quotes. For example:
set "A weird & "complex" variable=My value"
set A weird ^& "complex" variable=My value
Both expressions above give the same result. The variable name is A weird & "complex" variable and the value is My value
The IF DEFINED construct is used to test if a variable is defined. Quotes don't work for this test, special characters in the name (including quotes) must be escaped.
set "A&B=value"
if defined A^&B echo This works
if defined "A&B" echo This does not work
The above escaped test works just fine. The quoted test does not work
But how can I test if a variable containing spaces exists?
set "A B=value"
if defined A^ B echo this does not work!
It seems like the above should work, but it doesn't!
I'm looking for an answer that does NOT involve expanding the variable using %A B% or !A B!
Interessting question (I love this syntax base questions).
Obviously you know how to check it with delayed expansion and also FOR-parameters works.
#echo off
setlocal
set "AAA BBB=value"
set ""AAA BBB"="
set "AAA="
for %%a in ("AAA BBB") do if defined %%~a echo FOR: This works
setlocal EnableDelayedExpansion
set "varname=AAA BBB"
if defined !varname! echo Delayed: This works
if defined %varname% ( echo percent: Never comes here
) ELSE ( echo percent: Never comes here ? )
if defined AAA^ BBB ( echo escape1: Never comes here
) ELSE ( echo escape1: fails )
set AAA=Hello
if defined AAA^ BBB (
echo escape2: It only test for AAA the BBB will be "removed"
) ELSE ( echo escape2: fails )
set "space= "
if defined AAA!space!BBB echo inject space: This works
if defined "AAA BBB" (echo Quote1: Never comes here
) ELSE ( echo Quote1: Fails )
set ""AAA BBB"=value"
if defined "AAA BBB" echo Quote2: This works, it checks for "AAA BBB" with quotes
In my opionion, in the escape2 example the parser first split the line into tokens this way:
<if> <defined> <AAA BBB> <echo ....
But at the execution time of the if defined it rescan the <AAA BBB> token so it only gets the AAA.
You can't inject a second escape like AAA^^^ BBB as this only searches for the variable named AAA^
I can't see a solution without delaying/FOR, as the escaping of the space always fails.
EDIT: It can also be solved with SET <varname>
The solution of ijprest uses the SET command to test the variable without the need of escaping the varname.
But it also shows interessting behaviour with spaces inside and at the end of a varname.
It seems to follow these rules:
SET varname searches for all variables beginning with varname, but first it removes all characters after the last space character of varname, and it removes all leading spaces.
So you can't search for variables with beginning with space (but it is also a bit tricky to create such a varname).
The same behaviour is also active if the variablename is enclosed into quotes, but then exists one more rule.
First remove all characters after the last quote, if there are at least two quotes.
Use the text inside of the quotes, and use the "space"-rule.
Sample.
set " abc def ghi" junk junk
*** 1. removes the junk
set " abc def ghi"
*** 2. removes the quotes
set abc def ghi
*** 3. removes all after the last space, and the trailing spaces
set abc def
*** Search all variables beginning with abc def
I also love this sort of question! :)
Here's another possible solution I came up with... using SET itself to test the existence, and using the ERRORLEVEL result:
set "A B=foo"
set A B >nul 2>nul&& echo 1. This works
set "A B ">nul 2>nul&& echo 2. This works
set "A weird & "complex" variable=foo"
set A weird ^& "complex" variable >nul 2>nul&& echo 3. This works
set "A weird & "complex" variable ">nul 2>nul&& echo 4. This works
Note that this only works if your variables are unique in the sense that no variable name is the prefix of another one. Otherwise you risk false positives, as SET's default behavior is to show all variables that start with the parameter you pass. If this could be the case, you could filter the results with findstr:
set "A B="
set "A B C=foo"
set "A B ">nul 2>nul&& echo 5. Failed (false positive)
set "A B "|findstr /B /L /C:"A B=" >nul||echo 6. This works (no false positive)
Also, the single trailing space after the variable name seems to be required. Without it, SET often mis-parses the input. Bizarrely, if you add an extra space between the "2>nul" and "&&" in case #3 it stops working (unless you remove the space before ">nul")... weird.
The other way is to reassign it to another variable (one without spaces) and test that. See here:
rem Prepare ONLY variable 'a b'
set "a b=123"
echo [a b]=%a b%
rem This will ouput: [a b] is defined
set var=%a b%
if defined var (
echo [a b] is defined
) else (
echo [a b] is not defined
)
rem This will output: [c d] is not defined
set var=%c d%
if defined var (
echo [c d] is defined
) else (
echo [c d] is not defined
)
I do it by defining a flag as TRUE if needed...
rem /* sample code */
set VAR_SET=
if <some condition> set VAR_SET=TRUE&set VAR=this data has spaces
rem /* test for VAR_SET using 'if defined' */
if defined VAR_SET (
rem /* do something with the other data in the variable %VAR% */
)
rem /* clear the flag */
set VAR_SET=

Resources