I have 2 command lines and I need the variables to be the same in both command lines.
For example:
command line 1:
set testvar=this does not work
For /l %%a in (0 0 1) do echo %testvar%
command line 2:
set testvar=this works
Command line 1 is started first.
The result of the 2nd command line should be "this does not work" multiple times in a row until I open the 2nd command line, then it should change to "this works" multiple times in a row.
I have tried a lot of different methods, for example storing the variable in a file on the harddisk but this isn't fast enough, i has to be memory based. The setx command is also not fast enough.
If anyone knows a solution pleas tell me.
You cannot access process memory of a different process from a batch file (and even with actual programming languages you usually shouldn't, but then there are better synchronization primitives anyway). setx cannot work because it simply changes the registry and thus only affects new processes, not already existing ones.
A file is actually your best bet in batch files. Have you actually tried whether it's fast enough (files are still backed by memory, so creating a file and reading it should not incur constant disk thrashing in this case)? Note that you probably should include some sort of sleeping (e.g. via ping) in your consuming batch file to avoid busy waiting.
If you're only interested in a single bit (looks like you are), then you can just check for existence of a file which is faster than reading it:
1.cmd
=====
for /l %%a in (0,0,1) do (
if exist this.works (echo this works) else (echo this does not work)
ping localhost -n 2 >nul 2>&1
)
2.cmd
copy nul this.works
For me, without more information, files "should" be fast enough
#echo off
setlocal enableextensions enabledelayedexpansion
set "flagFile=flagfile.txt"
>"%flagFile%" type nul
if "%~1"=="second" goto :secondCopy
start "" "%~f0" second
:firstCopy
set /p "var=Input something: "
>>"%flagFile%" echo !time! !var!
goto :firstCopy
:secondCopy
echo Waiting for data
<"%flagFile%" (
for /l %%a in (0 0 1) do (
set /p "var=" && (
echo !time! !var!
)
)
)
This will allow you to compare the time difference between the write and the read time.
Related
I'm a computational biologist and I'm trying to run large batches of similar code with a single command, but my implementation has hit a brick wall.
I'm using the NEURON simulation environment, which uses MinGW for its Windows interface, which is where my research has shown my problem arises.
Currently, I am using a batch file to run all of these similar pieces of code, to iterate across the "collection" subfolders:
#echo off
for /D %%a in ("%cd%\all_cells\cell_*.*") do cd "%%a\sim1\" & START neuron sim.hoc
The problem arises when I have more than 32 subfolders; the additional instances won't run and will error with a "console device allocation failure: too many consoles" error.
My research has shown me that this is a known problem with Cygwin/MinGW.
However, working around this manually (ensuring that there is no more than 32 "collection" folders) is extremely time consuming when I am now dealing with hundreds of instances (each refers to a simulated cell and I want to gather statistics on hundreds of them), so I am trying to find a solution.
That said, I am terrible at writing batch files (I'm a terrible programmer who is used to scientific languages) and I can't figure out how to code around this.
It would be great if someone could help me either find a way around the 32 limit, or failing that, help me write a batch file that would do this:
-iterate over up to 32 folders
-wait for the instances to finish
-do it again for the next 32, until I reach the end of the folder.
I have tried using the /wait command to do them one at a time, but it still opens all 32. (And this wouldn't be ideal as I'd like to use all 16 cores I have.
The following is adapted from https://stackoverflow.com/a/11715437/1012053, which shows how to run any number of processes while limiting the total number run simultaneously in parallel. See that post for some explanation, though the code below is fairly well documented with comments.
I've eliminated the /O option and the code to work with PSEXEC from the original script.
The script below runs everything in one window - the output of each process is captured to a temporary lock file, and when finished, the full output of each process is typed to the screen, without any interleaving of process output. The start and end times of each process are also displayed. Of course you can redirect the output of the master script if you want to capture everything to a single file.
I've limited the total number of parallel processes to 16 - of course you can easily modify that limit.
The code will not work as written if any of your folder paths include the ! character. This could be fixed with a bit of extra code.
Other than that, the code should work, provided I haven't made any silly mistakes. I did not test this script, although the script it was derived from has been thoroughly tested.
#echo off
setlocal enableDelayedExpansion
:: Define the maximum number of parallel processes to run.
set "maxProc=16"
:: Get a unique base lock name for this particular instantiation.
:: Incorporate a timestamp from WMIC if possible, but don't fail if
:: WMIC not available. Also incorporate a random number.
set "lock="
for /f "skip=1 delims=-+ " %%T in ('2^>nul wmic os get localdatetime') do (
set "lock=%%T"
goto :break
)
:break
set "lock=%temp%\lock%lock%_%random%_"
:: Initialize the counters
set /a "startCount=0, endCount=0"
:: Clear any existing end flags
for /l %%N in (1 1 %maxProc%) do set "endProc%%N="
:: Launch the commands in a loop
set launch=1
for /D %%A in ("%cd%\all_cells\cell_*.*") do (
if !startCount! lss %maxProc% (
set /a "startCount+=1, nextProc=startCount"
) else (
call :wait
)
set "cmd!nextProc!=%%A"
echo -------------------------------------------------------------------------------
echo !time! - proc!nextProc!: starting %%A
2>nul del %lock%!nextProc!
cd "%%A\sim1\"
%= Redirect the output to the lock file and execute the command. The CMD process =%
%= will maintain an exclusive lock on the lock file until the process ends. =%
start /b "" cmd /c 1^>"%lock%!nextProc!" 2^>^&1 neuron sim.hoc
)
set "launch="
:wait
:: Wait for procs to finish in a loop
:: If still launching then return as soon as a proc ends
:: else wait for all procs to finish
:: redirect stderr to null to suppress any error message if redirection
:: within the loop fails.
for /l %%N in (1 1 %startCount%) do 2>nul (
%= Redirect an unused file handle to the lock file. If the process is =%
%= still running then redirection will fail and the IF body will not run =%
if not defined endProc%%N if exist "%lock%%%N" 9>>"%lock%%%N" (
%= Made it inside the IF body so the process must have finished =%
echo ===============================================================================
echo !time! - proc%%N: finished !cmd%%N!
type "%lock%%%N"
if defined launch (
set nextProc=%%N
exit /b
)
set /a "endCount+=1, endProc%%N=1"
)
)
if %endCount% lss %startCount% (
timeout 1 /nobreak >nul
goto :wait
)
2>nul del %lock%*
echo ===============================================================================
echo Thats all folks^^!
You could install screen or tmux in cygwin.
Then you can start all neuron instances in a screen/tmux session.
They will not open a new window, so there is no limit anymore.
I'm trying to create a batch file to insert a string from a .txt file at a specific place inside a string in 225 batch files - i.e., inserted into one line in the file at a specific place - but this question concerns the inserting part, and not the loop part, so I've left out the latter in my code example. It's also currently just displaying the text on-screen; not actually writing it to files.
The target files are a bunch of launch .bat files used for running a game server cluster using a tool, so I will have to leave each of them with the same names as they start with (Start XXYY.bat). They contain something along these lines:
start /high ShooterGame\Binaries\Win64\ShooterGameServer.exe Ocean?ServerX=0?ServerY=0?AltSaveDirectoryName=0000?ServerAdminPassword=1234?MaxPlayers=50?ReservedPlayerSlots=25?QueryPort=50002?Port=5002?SeamlessIP=192.168.1.225?RCONEnabled=true?RCONPort=28450 -log -server -NoBattlEye
exit
Where the ServerX, ServerY, AltSaveDirectoryNamen and all three Port settings are unique to each server, so these will have to remain unchanged.
I need to add several more settings, from another .txt file in the final version, but for this example I will just put the additions (the word INSERT added after the ReservedPlayerSlots setting, while keeping each setting divided by question marks) directly into this script.
My code is actually doing exactly what I want it to, but unfortunately it doesn't stop at that point, and decides to append more text than I wanted; specifically, everything I add to the ECHO command which is not a variable name.
To clarify, I get the exact output that I want... Plus the unwanted addition of a bunch of question marks and the word INSERT, which apparently come from my ECHO command, but I just have no idea why they get re-added.
My knowledge of batch scripting is fairly limited, so there might well be something basic that I've overlooked.
I've tried replacing the question marks in the output (which are required to be questions marks in the final version) with normal letters instead, but it doesn't change the behaviour; they were still appended to the expected output, just like the question marks they replaced.
#ECHO OFF
SET FileNum=0000
REM I will have the code loop through 225 files (0000-1414) in the final version, but for test purposes I just set it to one single file number manually here.
SET FileName=Start %FileNum%.bat
REN "%FileName%" temp.txt
FOR /F "tokens=1,2,3,4,5,6,7,8,9,10,11,12 delims=?" %%a IN (temp.txt) DO (
ECHO %%a?%%b?%%c?%%d?%%e?%%f?%%g?INSERT?%%h?%%i?%%j?%%k?%%l
)
REN temp.txt "%FileName%"
I expect this code to output this:
start /high ShooterGame\Binaries\Win64\ShooterGameServer.exe Ocean?ServerX=0?ServerY=0?AltSaveDirectoryName=0000?ServerAdminPassword=1234?MaxPlayers=50?ReservedPlayerSlots=25?INSERT?QueryPort=50002?Port=5002?SeamlessIP=192.168.1.225?RCONEnabled=true?RCONPort=28450 -log -server -NoBattlEye
exit
But what I am getting is this:
start /high ShooterGame\Binaries\Win64\ShooterGameServer.exe Ocean?ServerX=0?ServerY=0?AltSaveDirectoryName=0000?ServerAdminPassword=1234?MaxPlayers=50?ReservedPlayerSlots=25?INSERT?QueryPort=50002?Port=5002?SeamlessIP=192.168.1.225?RCONEnabled=true?RCONPort=28450 -log -server -NoBattlEye
exit???????INSERT?????
Which is the expected output, but with the unexpected re-addition of every symbol in the ECHO command which did not designate a variable at the end of the output (in this case ???????INSERT?????), just after the exit.
I'm stumped... I hope someone has an idea what I'm doing wrong here.
Okay, I applied the idea that aschipfl provided, and it seems to work now.
The IF NOT "%%b"=="" line seems to have done the trick, after I added the final line with the exit using another ECHO. My full script (including loop and write to file) is now like this:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET "Insert=SettingOne=True?SettingTwo=False?SettingThree=1.000000"
FOR /l %%x IN (0, 1, 14) DO (
FOR /l %%y IN (0, 1, 14) DO (
IF %%x LSS 10 (SET XNum=0%%x) ELSE (SET XNum=%%x)
IF %%y LSS 10 (SET YNum=0%%y) ELSE (SET Ynum=%%y)
SET "FileNum=!XNum!!YNum!"
SET "FileName=Start !FileNum!.bat"
ECHO Filename: !FileName!
REN "!FileName!" temp.txt
(
FOR /F "tokens=1-12 delims=?" %%a IN (temp.txt) DO (
IF NOT "%%b"=="" (
ECHO %%a?%%b?%%c?%%d?%%e?%%f?%%g?%Insert%?%%h?%%i?%%j?%%k?%%l
ECHO exit
)
)
) >edited.txt
REN edited.txt "!FileName!"
DEL /q temp.txt
ECHO has been updated
)
)
This is now working exactly as intended.
It's quite possible that there is a more elegant way of doing this, and I am cartain that there is a way of making this more general and less hard-coded, but it's good enough for my purposes.
I thank you for your help!
I started learning Batch files commands and I succeeded to create some basic scripts to simplify some tasks at work.
Now I'm looking to automate a repetitive task that takes a lot of time to be done... and to say I'm doing it manually each day :
I have a bunch of .txt files grouped in the same folder and the content of these texte files is like below :
Comments lines
START (name of electronic component) (Number of pins)
Program body line 1
Program body line 2
.
Program body line X
END
Comments lines
What I'm doing is to copy the "name of the electronic part" that I'm working on
and paste it after END. Here is an example :
START BC547 3
Program body line 1
Program body line 2
.
Program body line X
END BC547
There are numerous blank END in the same file, you can imagine filling 200 to 300 text files every day manually ...
In some cases the structure changes to :
Comments lines
START (name1) (Number of pins)
Comments lines
START (subcircuit1) (Number of pins)
Program body line 1
.
Program body line X
END (subcircuit1)
Comments lines
START (subcircuit2) (Number of pins)
Program body line 1
.
Program body line X
END (subcircuit2)
Comments lines
START (subcircuitx) (Number of pins)
Program body line 1
.
Program body line X
END (subcircuitx)
Comments lines
END (name1)
Comments lines
I would be much thankful if someone can make batch code to copy the full next word after START and paste it in the next END below. The script have to be able to detetct the second case when STAR syntaxes are consecutive.
Thank you in advance for your help!
The community has determined that open ended questions asking for de novo code to meet a set of business requirements is out of scope for StackOverflow batchfile questions. But I am bored and couldn't help myself.
The algorithm is fairly strait forward. Iterate all the lines of the source file. If the line begins with START, then parse out the ID and push the value on a stack (array). If the line begins with END then append the last stack value to the line and pop the stack. If the line does not begin with END then simply write the original line.
But nothing in batch is simple.
FOR /F disregards lines that are empty, so FINDSTR /N is used to prefix each line with a line number, followed by a colon. String manipulation is performed within the loop to strip off the line number prefix.
Batch doesn't have formal support for arrays, but the code shows how to emulate arrays.
Delayed expansion is required within a parenthesized block of code, so an array member may not be accessed as !ID.%i%. The code shows how to transfer the current i value to a FOR variable so you can use !ID.%%I instead.
Delayed expansion is toggled ON and OFF within the loop to protect any ! that may be present within the source file.
You cannot write to the file you are reading from. So the result must be written to a temporary new file, which is later MOVEd to replace the original.
The script below should be called with one or more file masks that specify which files should be processed. If the script is named "fixEnd.bat", then fixEnd test.txt would process test.txt in the current directory. fixEnd "c:\somePath\*.txt" would process all .txt files within the "c:\somePath" folder. fixEnd file1.txt file2.txt would process those two files in the current directory.
#echo off
setlocal disableDelayedExpansion
for %%F in (%*) do (
set /a i=0
set "ID.0="
>"%%F.new" (
for /f "delims=" %%L in ('findstr /n "^" "test.txt"') do for /f "tokens=1,2,3 delims=: " %%A in ("%%L") do (
if "%%B" == "START" (
set /a i+=1
setlocal enableDelayedExpansion
for %%I in (!i!) do (
endlocal
set "ID.%%I=%%C"
)
)
if "%%B" == "END" (
setlocal enableDelayedExpansion
for %%I in (!i!) do (
(echo END !ID.%%I!)
endlocal
set "ID.%%I="
set /a "1/i, i-=1)" 2>nul %= division by zero error prevents negative i values =%
)
) else (
set "ln=%%L"
setlocal enableDelayedExpansion
(echo(!ln:*:=!)
endlocal
)
)
)
move /y "%%~F.new" "%%F" >nul
)
As much as I enjoy the challenge of working with batch, I long ago came to the conclusion that it is not practical to use pure batch to manipulate text files except for really simple cases. And in many cases batch simply is not up to the task. Which is why I wrote JREPL.BAT, a regular expression text processing utility that is pure script (hybrid JScript/batch) that runs natively on any Windows machine from XP onward.
JREPL has myriad options that give it tremendous power, especially the ability to incorporate user supplied JScript on the command line. Using JREPL, the same algorithm is implemented in a much more straightforward way, and the code is much faster than pure batch:
#echo off
for %%F in (%*) do for %%F in (test.txt) do call jrepl^
"^START\b\s*(\S*)/^END\b.*"^
"id[id.length]=$2;$txt=$0/$txt='END '+(id.length?id[id.length-1]:'');id=id.slice(0,-1)"^
/jbeg "var i=0, id=[]" /t "/" /jq /f "%%F" /o -
Use jrepl /?? from the command line to view the entire documentation, one screen at a time. jrepl /?help lists all available types of help. jrepl /?options gives a brief summary of all available options. jrepl /?/t would show the help for the /T option. Etc.
I need to read a file and replace a value in GB by a value in MB. The value is not always in GB and most of the time a decimal. The only way I found was the following:
for /F "tokens=1,2,3,4 delims=," %%A in (file.log) do (
echo.%%D|findstr "GB Available" >nul 2>&1
if errorlevel 0 if not errorlevel 1 (
set value=%%D
echo value:!value! REM looks like "Available= 12.53GB"
set value_short=!value:~11!
set value_short=!value_short: GB=! REM looks like "12.53"
echo value_short:!value_short!
set /a value_converted=!value_short:.=!*10
echo value_converted:!value_converted! MB REM looks like "1253 MB"
set "line=%%D"
set "line=!line: !value_short!=!value_converted!!"
echo LINE: !line! REM Need to have something like: "Available= 1253MB"
)
if errorlevel 1 (
echo no
)
)
Unfortunately set "line=!line: !value_short!=!value_converted!!" doesn't work, I also tried with % instead of ! but it's not working. What is wrong with this line?
The main goal is to have values in MB in my file and for that I read the file and write the lines in MB without touching them in a new file, and re-write the lines with GB by converting them. If you have a simpler way, it's more than welcome!
You are assuming the GB value always has 2 decimal places, and also that the value unit uses multiples of 1000 and not multiples of 1024. I am going to assume that is correct.
There is a simpler notation for conditional execution using the && (success) and || (failure) operators.
Your FINDSTR search is incorrect. It will match any line that contains GB or Available. You want and instead. I would use a regular expression search `"^Available=.*GB$".
Your REMark claims the source value is 12.53GB, but your code implies it is 12.53 GB. I'm going to assume your code is correct and there is a space before the GB. Your code also assumes there is always a space before the number. I'm also going to assume that is correct.
Your failing line needs two layers of variable expansion. The search and replace terms must be expanded before the outer variable expansion. %var% would work if you were not in a code block, but you are, so it won't. The solution is to transfer the values to FOR variables.
for %%A in ("!value_short!") do for %%B in ("!value_converted!") do set "line=!line:%%~A=%%~B!"
If either value contained * or ? then you would have to switch to using FOR /F with a ("string") value.
But there is a much simpler way to accomplish your task.
for /F "tokens=1-4 delims=," %%A in (file.log) do (
for /f "tokens=1-4 delims=. " %%a in ("%%D") do if "%%a"=="Available=" if "%%d"=="GB" (
set "line=%%a %%b%%c0 MB"
echo !line!
) else (
echo no
)
)
The above only fixes your existing code - it does not replace any values in your original file.
I wouldn't use any of the above pure batch to modify the file. Instead, I would use a hybrid JScript/batch utility called REPL.BAT that performs a regular expression search/replace on stdin and writes the results to stdout. It is pure script that will run natively on any Windows machine from XP onward.
type "file.log" | repl "(Available= )([0-9]+)\.([0-9][0-9]) GB" "$1$2$30 MB" >"file.log.new"
move /y "file.log.new" "file.log" >nul
I am writing a batch file (I asked a question on SU) to iterate over terminal servers searching for a specific user. So, I got the basic start of what I'm trying to do.
Enter a user name
Iterate terminal servers
Display servers where user is found (they can be found on multiple servers now and again depending on how the connection is lost)
Display a menu of options
Iterating terminal servers I have:
for /f "tokens=1" %%Q in ('query termserver') do (set __TermServers.%%Q)
Now, I am getting the error...
Environment variable __TermServers.SERVER1 not defined
...for each of the terminal servers. This is really the only thing in my batch file at this point. Any idea on why this error is occurring? Obviously, the variable is not defined, but I understood the SET command to do just that.
I'm also thinking that in order to continue working on the iteration (each terminal server), I will need to do something like:
:Search
for /f "tokens=1" %%Q in ('query termserver') do (call Process)
goto Break
:Process
for /f "tokens=1" %%U in ('query user %%username%% /server:%%Q') do (set __UserConnection = %%C)
goto Search
However, there are 2 things that bug me about this:
Is the %%Q value still alive when calling Process?
When I goto Search, will the for-loop be starting over?
I'm doing this with the tools I have at my disposal, so as much as I'd like to hear about PowerShell and other ways to do this, it would be futile. I have notepad and that's it.
Note: I would continue this line of questions on SuperUser, except that it seems to be getting more into programming specifics.
Ok, those are quite a few questions/issues/etc. in one :-)
And I still don't quite get where exactly you're headed with that script.
First of all, the syntax for the set command is
set <variable name>=<value>
If you do just
set <variable name>
then it will list all environment variables starting with <variable name>. If there are none, then it will output the error message you're seeing.
If you want to define a variable without actually caring about its value, you still need to provide a value. I usually use 1 for such flags, since it's then more an on/off switch than an actual variable holding a value:
set Foo=1
In your case you probably want something else, though. There are no arrays per se in batch files, you can mimic them by creating a number of variables and holding a count somewhere. I've written about that once before (a little outdated by now, but still valid).
In your case you want to iterate over a number of servers and for each server over a number of users. You can do that with a nested loop:
for /f "tokens=1" %%Q in ('query termserver') do (
for /f "tokens=1" %%U in ('query user ... /server:%%Q' do (
...
)
)
As for your two questions there:
No, the loop variable is only valid inside the loop, not when calling a subroutine. You can pass it to the subroutine, however:
for ... in (...) do call Process %%Q
You can then access it with %1 in the subroutine. Honestly, though, in most cases I think the nested loops are easier to read.
Yes.
Another error (one that will bite you): As mentioned before, the set syntax is
set variable=value
Note that there is no space around the = sign. If there is, then you have a space at the end of the variable name or at the start of the value:
> set foo = bar
> echo %foo%
%foo%
> echo %foo %
bar