Use content of a file in a string - batch-file

I have a file (let's call it version.txt) that contains a version number and some text:
v5.02
Some text explaining
where and how this
number is used
Based on this answer, I use
set /p version=<version.txt
to store the first line of the file in the version variable. Now I'm trying to write a batch script that operates on folders that contain this version number in their name. However, I get unexpected results because something seems to go wrong when I insert the variable in a path. For example, this script
#set /p version=<version.txt
#echo C:\some\folder\%version%\some\file.exe
prints
C:\some\folder\v5.02
instead of
C:\some\folder\v5.02\some\file.exe
What's going on? I have a feeling there are hidden characters of some sort at the end of the text in the variable, because setting the variable by hand to a constant in the script works.
Edit: I'm using Windows 10 with Notepad++ as my editor, if it helps.

I can only replicate your issue, when version.txt uses Unix line endings (LF) instead of Windows (CRLF). for /f is immune to this issue:
for /f "delims=" %%a in (version.txt) do set "verion=%%a" & goto :skip
:skip
echo C:\some\folder\%version%\some\file.exe
goto :skip breaks the loop after reading the first line.

Since everything I tried didn't seem to work, the solution I found in the end is to call the batch script from a Python script. The Python script reads the first line of the version file and passes it as an argument to the batch script. Out of context, it is a bit of an inelegant solution, but in my case the batch script was already called by a Python script, so it's not that terrible.
Here is a minimal example:
version.txt
v5.02
Some text explaining
where and how this
number is used
script.bat
#echo C:\some\folder\release\%1\some\file.exe
script.py
import os
with open("version.txt") as f:
version = f.readline().rstrip()
os.system("cmd /c script.bat %s" % version)
Edit: Following Stephan's comment, I tried to change the line ending in the text file from LF to CRLF and it indeed solves the problem. However, since I don't really have control over everything that writes in that file, the solution above remains the most feasible in my case.
Edit 2: Stephan's answer (with the for loop) is actually a better solution than this one since it avoids having to transfer part of the work to the calling Python script.

Related

Escape characters of a file path argument for a batch file

I was making a batch file to take dragged-and-dropped folders for program input. Everything was working fine until I passed a folder, which for the sake of this post, called foo&bar.
Checking what %1 contained inside the batch file looked like C:\path\to\foo or C:\path\to\foo\foo. If the file path were in quotes it would work, so the only working code that slightly takes this into effect is :
set arg1=%1
cd %arg1%*
set arg1="%CD%"
Which changes directory to the passed argument using wildcards. However this only works once for if there is another folder with un-escaped characters inside the parent folder, passing the child folder would result in the parent folders' value.
I tried the answer of this post, which suggests to output the argument using a remark and redirection statement during an #echo on sequence. However no progress occurred in rectifying the problem. Any suggestions?
To recap, I am looking for ways to pass folders with un-escaped characters as arguments to a batch file. The implementation should preferably be in a batch file, but answers using VBScript are welcome. However the starting program must be in batch as this is the only program of the 3 that accepts files as arguments.
To test this, create a batch file with following code:
#echo off
set "arg1=%~1"
echo "the passed path was %arg1%"
pause
Then create folders called foobar and foo&bar. Drag them onto the batch file to see their output. foo&bar will only return C:\path\to\foo.
OK, so the problem is that Explorer is passing this as the command line to cmd.exe:
C:\Windows\system32\cmd.exe /c ""C:\path\test.bat" C:\path\foo&bar"
The outermost quotes get stripped, and the command becomes
"C:\working\so46635563\test.bat" C:\path\foo&bar
which cmd.exe interprets similarly to
("C:\working\so46635563\test.bat" C:\path\foo) & bar
i.e., bar is considered to be a separate command, to be run after the batch file.
The best solution would be to drag-and-drop not directly onto the batch file but onto, say, a vbscript or a Powershell script or a plain old executable. That script could then run the batch file, either quoting the argument appropriately or putting the directory path into an environment variable rather than on the command line.
Alternatively, you can retrieve the original command string from %CMDCMDLINE% like this:
setlocal EnableDelayedExpansion
set "dirname=!CMDCMDLINE!"
set "dirname=%dirname:&=?%"
set "dirname=%dirname:" =*%"
set "dirname=%dirname:"=*%"
set "dirname=%dirname: =/%"
for /F "tokens=3 delims=*" %%i in ("%dirname%") do set dirname=%%i
set "dirname=%dirname:/= %"
set "dirname=%dirname:?=&%"
set dirname
pause
exit
Note the exit at the end; that is necessary so that cmd.exe doesn't try to run bar when it reaches the end of the script. Otherwise, if the part of the directory name after the & happens to be a valid command, it could cause trouble.
NB: I'm not sure how robust this script is.
I've tested it with the most obvious combinations, but YMMV. [It might be more sensible to use delayed expansion exclusively, I'm not sure. It doesn't seem to be necessary except in the first set command. Jeb's answer here might be a better choice if you're going this route.]
For the curious, the script works like this:
Load the original command line into dirname [necessary for the reason pointed out by jeb]
Replace all the & characters with ?
Replace all the quote marks with *
If a quote mark is followed by a space, suppress the space.
NB: it is necessary to suppress the space to deal with both the case where the path contains a space (in which case Explorer adds quote marks around it) and the case where it doesn't.
Replace all remaining spaces with /
NB: ? * and / are illegal in file names, so these replacements are safe.
At this point the string looks like this:
C:\Windows\system32\cmd.exe//c/**C:\path\test.bat**C:\path\foo?bar**
So we just need to pull out the third asterisk-delimited element, turn any forward slashes back into spaces and any question marks back into ampersands, and we're done. Phew!

Batch file: pass a block of commands instead of a script.tcl

I run a .tcl script using a .bat file as per the simplified example below:
script.tcl
set a "this is the script"
puts $a
script.bat
echo off
set tclpath="C:\tclsh.exe"
set filepath="C:\script.tcl"
%tclpath% %filepath%
I wonder whether I can include in the .bat file the commands of the .tcl script, so instead of having two files I just have one .bat file that runs tclsh.exe and passes the commands to the tcl shell.
Is this possible? and how can I do it?
For many things, there are pages on the Tcler's Wiki that can be looked to for interesting things. In particular, this old page has some really useful techniques. As you read through, you'll see a history of techniques tried. They depend on the fact that Tcl commands can be prefixed with :: usually (marking them as a weird label in the batch file language) and you can comment out blocks of code in Tcl with if 0 (with Tcl not parsing the contents, beyond ensuring that it is brace-balanced, which code usually is).
The best technique is one that doesn't just make the code multilingual, but also makes it easily readable. Preserving readability is the key to not going crazy.
::if 0 {
#rem This code in here is pure batch script
echo off
set tclpath="C:\tclsh.exe"
%tclpath% "%~f0" %*
#rem Put this at the end; it means terminate since the “eof” label is end-of-file
#goto eof
}
# This code is the pure Tcl code
set a "this is the script"
puts $a
The other bits to be aware of:
"%~f0" — This gets the full path to the “zero-th argument”, which is the name of the script you're running.
%* — This is all the remaining arguments. It's a good idea to pass them on, and you can access them from Tcl using the list in the global argv variable.
I wonder whether I can include in the .bat file the commands of the .tcl script, so instead of having two files I just have one .bat file
that runs tclsh.exe and passes the commands to the tcl shell.
Easy peasy. . .
You can use a CALL to a subroutine in a batch script that will append the commands to the dynamically created script file which you specify with the set filepath variable.
This way you have everything in the batch script and you do not need to worry about the tcl script file other than ensuring the :tclshScript routine that creates it has the correct syntax, etc.
You essentially build the tcl script logic with batch ECHO commands and it'll create it per run.
Use caution with special characters though as the carat ^ symbol may be needed to escape certain character to the tcl script if batch interprets those otherwise or you notice an issue.
echo off
set tclpath="C:\tclsh.exe"
set filepath="C:\script.tcl"
IF EXIST "%filepath%" DEL /Q /F "%filepath%"
CALL :tclshScript
%tclpath% %filepath%
EXIT
:tclshScript
ECHO set a "this is the script">>%filepath%
ECHO puts $a>>%filepath%
GOTO EOF
Further Resources
CALL
ECHO
Escape

Input Error: There is no file extension

If this is a rookie mistake I apologize for wasting your time.
As part of a larger batch file to create a directory with today's date, copy and paste files to be backed up, change permissions and delete files older then X days I've run into a problem. Here's where it happens:
echo var D = new Date() > tmp.js
echo D = (D.getFullYear()*100+D.getMonth()+1)*100+D.getDate() >> tmp.js
echo WScript.Echo( 'set YYYYMMDD='+D ) >> tmp.js
echo #echo off > tmp.bat
cscript //nologo tmp.js >> tmp.bat
call tmp.bat
The command prompt spits back this error:
Input Error: There is no file extension in "C:\Users\name\Desktop\Error:".
So obviously the rest of the batch file fails but the weird thing is this works on one machine on the network but not the other.
Any help is greatly appreciated.
EDIT: there is one more line to that code that is returning the error, my mistake.
After I call tmp.bat:
mkdir "\\network\file\%YYYYMMDD%"
This code, as is, will not generate the indicated error
BUT, if the path to the batch file contains spaces, and the code is something like
cscript //nologo %~dp0\tmp.js
you will get the indicated error as the argument to cscript is incorrectly parsed because the space in the path to the js file. If this is the case (or something similar), quotes in the filename should solve it
cscript //nologo "%~dp0\tmp.js"
Also, the temporary batch file is not needed. Output from cscript can be directly read with the help of for command
set "tmpJS=%~dp0\tmp.js"
>"%tmpJS%" echo with(new Date()){WScript.StdOut.WriteLine(getFullYear()*10000+(getMonth()+1)*100+getDate())};
for /f %%D in ('cscript //nologo //B "%tmpJS%"') do set "YYYYMMDD=%%D"
Wow - that is a very convoluted way to get the current date in a variable.
This is not a direct answer to your question, but I think I can make your life much easier :-)
There really is no need for a temporary batch file. FOR /F could process the output of your temporary JS script directly.
But I have an even easier solution - GetTimeStamp.bat is a hybrid JScript/batch utility that can do nearly any date and time computation on a Windows machine.
The utility is pure script that will run on any modern Windows machine from XP forward - no 3rd party executable required.
Assuming getTimestamp.bat is in your current directory, or better yet, somewhere within your PATH, then the following simple call will define a YYYYMMDD variable containing today's date in YYYYMMDD format:
call getTimeStamp -f {yyyy}{mm}{dd} -r YYYYMMDD
There are a great many options for specifying the base date and time, many options for adding positive or negative offsets to the date and time, many options for formatting the result, and an option to capture the result in a variable. Both input and output can be directly expressed as local time, UTC, or any time zone of your choosing. Full documentation is embedded within the script.
Figured it out:
running cscript on tmp.js led to an error begin put into tmp.bat
call tmp.bat then returned the weird input error because of the error message inside of tmp.bat.
tmp.bat looked like this when editing
Cscript Error: Can't find script engine "JScript" for script "C:\Users\name\Desktop\tmp.js".
That's why calling tmp.bat returned this error message:
Input Error: There is no file extension in "C:\Users\name\Desktop\error:".
The problem ended up being with jscript.dll. It needed to be (re)registered.
regsvr32 %systemroot%\system32\jscript.dll
^Fixed my problem. Thank you all for the help.

trying to pass variables adds extra characters

Ive seen others address this several years back but they didnt seem to give a working answer.
When I try to pass these two commands in a batch file (they work when done by hand) :
nltest /dsgetsite>c:\windows\temp\site.txt
set /p CurrentADSite<c:\windows\temp\site.txt
but when I attempt to issue the commands via a batch file I get this:
C:\working>nltest /dsgetsite 1>c:\windows\temp\site.txt
C:\working>set /p CurrentADSite 0<c:\windows\temp\site.txt
The syntax of the command is incorrect.
How on earth do I actually get this to work? Is there an easier way to pass the dsgetsite results straight into a variable?
You can use for /f to avoid the temp file (also you [usually] can't write to the Windows directory so that will blow up anyway):
for /f %%x in ('nltest /dsgetsite') do if not defined CurrentADSite set CurrentADSite=%%x
According to http://ss64.com/nt/set.html you may have missed out an =
"To place the first line of a file into a variable:
Set /P _MyVar=<MyFilename.txt "
If all else fails you could always fall back on:
:: start the temp batch with start of set command
echo set CurrentADSite=>c:\windows\temp\site.bat
:: add to the temp bat : the variable assignment
nltest /dsgetsite>>c:\windows\temp\site.bat
:: run the temp batch and return here
call c:\windows\temp\site.bat

Why won't a batch file run when being called from within another batch file?

I have a batch file that first creates another batch file containing a ClearCase cleartool command and second, runs it:
ECHO cleartool lsactivity -long "%ACTIVITY%"^>"%OUTPUTFILE%">FILETORUN.bat
CALL FILETORUN.bat
When running the batch, FILETORUN.bat is generated with the correct format, but the CALL to it is completely ignored.
If I ECHO output after the CALL to a log file, I can see that the script just skips over it.
What could it be?
I have tried removing CALL but it makes no difference.
EDIT: SOLUTION
Thank you all for the input. I found the problem. Before the write to batch and batch call in the script there was a command that read information into a variable from a file:
SET /p FILETODELETE=<rmname_%CLEARCASE_USER%.tmp
It reads only the first line. For some reason this created a conflict with temporary batch file, and I have no idea why. I used a different solution for reading the first line from a file and the conflict doesn't happen anymore:
(set FILETODELETE=)
for /f "delims=" %%q in (rmname_%CLEARCASE_USER%.tmp) do if not defined FILETODELETE set FILETODELETE=%%q
If anyone can shed some light it would be great!
SET /P waits for user input, so it actually will finish the command with what you are trying to execute after that and consume the input buffer, which might produce different results on each machine.
See set command reference for more details

Resources