Writing to different lines with Batch - batch-file

I'm trying to make a reminder system within batch in which there are different lines of reminders. My batch program will write to different lines in a .txt file, but it isn't working. Could you please help and try to find the issues?
#echo off
echo Enter slot # for reminder
set /p n=
cls
echo Please type in the assignment name
set /p a=
echo ----------------------------------
echo Please type in the class
set /p c=
echo ----------------------------------
echo Please type in the date due
set /p d=
cls
if %n%==1 goto l1
if %n%==2 goto l2
if %n%==3 goto l3
if %n%==4 goto l4
if %n%==5 goto l5
if %n%==6 goto l6
:l1
echo Reminder for %c% Homework! %a%,%d% > Reminder.txt
:end
:l2
echo Reminder for %c% Homework! %a%,%d% >> Reminder.txt
:end
:l3
echo Reminder for %c% Homework! %a%,%d% >>> Reminder.txt
:end
:l4
echo Reminder for %c% Homework! %a%,%d% >>>> Reminder.txt
:end
:l5
echo Reminder for %c% Homework! %a%,%d% >>>>> Reminder.txt
:end
:l6
echo Reminder for %c% Homework! %a%,%d% >>>>>> Reminder.txt
:end

Hints to fix what you've got:
The > character won't let you write to specific lines, and there's no native support in Windows batch to do such a thing.
There are two operators that use the > character: >, which redirects output to a file (replacing any existing content), and >>, which appends (adds to the end of) a file.
You've got multiple instances of :end, but that's invalid. :end is a label, which is a unique reference to that point in the code. When you add more than one, some get ignored and you get undefined behaviors, which is bad.
It looks like you're trying to use :end to exit. Use goto :EOF for that. It jumps to the built-in label :EOF, short for End Of File.
You need to handle the case where n is none of the predefined values. Currently if someone entered 7 for n, your program would get to the logic after :l1 and run it, which is wrong. Put a goto :EOF there just in case.
How to approach solving this type of issue with batch:
The only way I can think of off the top of my head to modify a specific line is to iterate through all lines using a for /f loop, rewriting each line (to a temporary file) until you encounter the one you want to change, then write your new content instead of the existing content. Then when you're done iterating, you can replace the original file with that temporary file.
You would have to do this each time you wanted to change a new line. Batch is a really simple language that doesn't have useful constructs like arrays, or the many external tools that a shell scripting language like Bash would have. It's also got some really unsophisticated runtime evaluation.
Here's a partial solution that you can combine with a few lines from your code above to achieve what you want. It prompts you for a line number, then puts the content of the newContent variable (replace with your implementation) into the file at the specified line:
REM suppresses the echo of the commands in the program
#ECHO OFF
REM sets a feature that overcomes some of the weak runtime evaluation limitations that batch has
setlocal ENABLEDELAYEDEXPANSION
REM The name of your file
set fname=file.txt
REM If our file doesn't already exist, make a new one with 6 empty lines since that's all we want for now.
if EXIST "%fname%" goto alreadyExists
for /l %%b in (1,1,6) do echo.>>"%fname%"
:alreadyExists
REM The name of a temp file
set tfile=f2.txt
REM A counter to track the line number
set counter=0
REM Input to get the line number you wish to replace
set /p replacementLine=Type the line number that should be replaced:
REM The content that goes on the replaced line
set newContent=New entry
REM Read the file, iterate through all lines.
for /f "tokens=*" %%a in (file.txt) do (
REM Add one to the counter
set /a counter=!counter!+1
REM Use the redirect '>' operator for the first line
if "!counter!"=="1" (
if "!counter!"=="%replacementLine%" (
REM We're on the line we wish to replace, so use the replacement line content
echo.%newContent% >f2.txt
) else (
REM We're NOT on the line we wish to replace, so use the original line content
echo.%%a >f2.txt
)
) else (
REM else for lines other than the first, use the append redirect '>>'
if "!counter!"=="%replacementLine%" (
REM We're on the line we wish to replace, so use the replacement line content
echo.%newContent% >>f2.txt
) else (
REM We're NOT on the line we wish to replace, so use the original line content
echo.%%a >>f2.txt
)
)
)
REM Delete the original file
del "%fname%"
REM Replace it with the modified copy
ren "%tfile%" "%fname%"
You can replace a few lines at the top get the functionality you want.

you can't write to a specific line in a file with batch. Instead you have to rewrite the complete file.
Steps: a) read the file. b) change the desired line. c) (over)write the file with the new data.
#echo off
setlocal enabledelayedexpansion
if not exist reminder.txt call :InitFile :: create the file, if it doesn't exist
set /p "n=Enter slot # for reminder: "
set /p "a=type in the assignment name: "
set /p "c=type in the class: "
set /p "d=type in the date due: "
cls
call :ReadFile
set "_Line[%n%]=Reminder for %c% Homework: %a%,%d%"
call :WriteFile
type Reminder.txt
goto :eof
:ReadFile
set x=0
for /f "delims=" %%i in (reminder.txt) do (
set /a x+=1
set "_Line[!x!]=%%i"
)
goto :eof
:WriteFile
set x=0
(for /f "tokens=2 delims==" %%i in ('set _Line[') do echo %%i)>Reminder.txt
goto :eof
:InitFile
(for /l %%i in (1,1,6) do echo Reminder %%i [empty])>Reminder.txt
goto :eof
(Note: this would make trouble with more than 9 lines because of the alphabetical sorting with set _line[, but as you need only 6 lines, this should not be a problem for you)
Note: your input shouldn't contain !

Related

Batch if statements don't seem to work

I am so confused and all help is appreciated...
so I'm making a little game and this is the code:
#echo off
:menu
echo Welcome To My Game!!!
echo Play & echo.Exit
:: add more menu items above
set /p menInp=
if /i %menInp%==play goto initFile
if /i %menInp$==exit EXIT
:initFile
cls
if NOT exist \MyAdventureGame\AdventureGameSave.txt\ GOTO newGame
if exist \MyAdventureGame\AdventureGameSave.txt\ GOTO Load
:newGame
echo. 2>AdventureGameSave.txt
set Gold = 50
set Xp = 0
set Level = 1
GOTO Save
:Save
echo %Gold% >>AdventureGameSave.txt
echo %Xp% >>AdventureGameSave.txt
echo %Level% >>AdventureGameSave.txt
GOTO Town
:Load
:: add Load functionallity here
GOTO Town
:Town
echo Welcome to town!!!
PAUSE
Now for my question...
when i run it, no matter what I type for my menInp, my program goes to the next line, :initFile, and its almost like my if statements
(if /i %menInp%==play goto initFile
if /i %menInp$==exit EXIT)
are being ignored.
if /i %menInp$==exit EXIT
should be
if /i %menInp%==exit EXIT
or preferably
if /i "%menInp%"=="exit" EXIT
since you are unable to predict the user's input and it may contains spaces. The "s make the string a single token.
This is somewhat of a comment, but may forestall further problems.
Tip for game-generation:
If you reserve a character as a prefix for variables-you-want-to-save (eg all variables I want to save/reload start with #) then all you need to save a game is
set #>"mygamefile.txt"
and all you need to reload a game is
for /f "usebackqdelims=" %%a in ("mygamefile.txt") do set "%%a"
To zap all # variables (useful before reloading a game) use
for /f "delims==" %%a in ('set # 2^>nul') do set "%%a="
Oh and you may consider removing the terminal \ from the filename in the if exist statements (also enclose the entire filename in quotes to allow the name to contain spaces (and follow good practice regardless of whether you actually need to cater for spaces in filenames))

How to choose one of multiple actions based on file extension, in batch

I'm an amateur on the usage of the FOR command. I need a batch file that will run one of 5 file conversion tools based on a file's extension. I want to drop a file onto the batch file icon and have it converted.
Since my list is huge, I can't use nested IF's.
What I've tried so far:
#ECHO OFF
SET cadfile=.dwg .dxf .dwf
SET gsfile=.ps .eps .epi .epsp
SET xxxxxx=.xx .xx and goes on
FOR %%~x1 in (%cadfile%) do (
Do some action
FOR %%~x1 in (%gsfile%) do (
Do some other action
)
)
The %%~x1 variable is used for file extension of file, which dragged and dropped over the batch file.
(edited to make more clear)
FOR %%a in (%cadfile%) do (
if /i "%~x1"=="%%a" some_action "%~1"
)
... and follow the bouncing ball for the rest of the utilities/lists
I think this will work for you. It looks through all your groups of extensions in a single For loop and when the matching extension is found, calls a label where you can do the conversion and any related tasks. You'll need to finish the "groupN" variables and labels.
#echo off
SETLOCAL EnableDelayedExpansion
set file="%1"
set ext=%~x1
:: Set the 5 groups of extensions that have different converters
set group1=.dwg, .dxf, .dwf
set group2=.ps, .eps, .epi, .epsp
For %%A in (1 2 3 4 5) do (
set groupnum=group%%A
call set thisgroup=%%!groupnum!%%
:: Look for extension in this group
echo.!thisgroup!|findstr /i /C:"%ext%" >nul 2>&1
if not errorlevel 1 call :group%%A
:: else go loop next group
)
echo Extension not found in any group &pause &goto end
:group1
echo group1 file to convert is %file%
goto end
:group2
echo group2 file to convert is %file%
goto end
:end
pause
exit
The following method allows you to easily add and modify your list of extensions/applications. Please note that you just need to edit the values placed inside the first FOR command; the rest of the program is the solution you don't need to care of...
#echo off
setlocal EnableDelayedExpansion
rem Define the list of extensions per application:
rem (this is the only part that you must edit)
for %%a in ("cadfile=.dwg .dxf .dwf"
"gsfile=.ps .eps .epi .epsp"
"xxxxxx=.xx .xx1 .xx2") do (
rem The rest of the code is commented just to be clear,
rem but you may omit the reading of this part if you wish
rem Separate application from its extensions
rem and create a vector called "ext" with an element for each pair
for /F "tokens=1,2 delims==" %%b in (%%a) do (
rem For example: %%b=cadfile, %%c=.dwg .dxf .dwf
for %%d in (%%c) do set "ext[%%d]=%%b"
rem For example: set "ext[.dwg]=cadfile", set "ext[.dxf]=cadfile", set "ext[.dwf]=cadfile"
rem In the next line: set "ext[.ps]=gsfile", set "ext[.eps]=gsfile", etc...
)
)
rem Now process the extension of the file given in the parameter:
if defined ext[%~x1] goto !ext[%~x1]!
echo There is no registered conversion tool for %~x1 extension
goto :EOF
:cadfile
echo Execute cadfile on %1 file
rem cadfile %1
goto :EOF
:gsfile
echo Execute gsfile on %1 file
rem gsfile %1
goto :EOF
etc...
If each conversion tool is executed in the same way and don't require additional parameters (just the filename), then you may omit the individual sections and directly execute the conversion tools this way:
if defined ext[%~x1] !ext[%~x1]! %1
For further explanations on array concept, see this post.

Batch if else statement not working

I'm writing a little program but I have a problem with an if else statement.
#echo off
set /p points=
echo %points%
echo 1x5=
set /p 15=
if %15%==5 (
echo well done!
set /a points=%points%+1
) else ( echo wrong! )
pause
echo %points%
pause
Even if I fill in a wrong answer, it still ads 1 point to my points and says "Well done!"
(BTW: got some problems with inputting the code, don't now if you will be able to read it)
When an argument is included in the command line when calling the batch file, it is referenced from code as %1, %2, ... for the first, second, ... parameters.
In your code, when you write if %15%==5 ..., %15% is interpreted as first parameter to batch file (%1) followed by a 5 and a non escaped non paired percent sign that is discarded by the parser. So, if the batch file has no input arguments and %1 is empty, your if code is executed as if 5==5 ...
As a general recomendation, variables in batch files "should" never start with a number, as they will be considered as input arguments.
#echo off
setlocal enableDelayedExpansion
set /p points=
echo %points%
echo 1x5=
set /p 15=
if !15!==5 (
echo well done!
set /a points=!points!+1
) else ( echo wrong! )
pause
echo !points!
endlocal
pause
MC MD explained why your code does not work.Here's a way to make it work.

Batch files: ask for user input with "suggestions"

How can I prompt users for an answer, suggesting something they can edit?
I know how to make a q&a like:
1. The sky is:
The user put the answer and:
echo For you the sky is %var%
But i want something like:
1. The sky is: Blue
echo For you the sky is Blue
But the user can change it
1. The sky is: Green
echo For you the sky is Green
I don't know if i was clear, tell me if not.
Thanks
Initialize the variable before with a color:
#echo off&setlocal
set "color=Blue"
set/p "color=For you the sky is %color% (hit enter for o.k. or type in other color): "
echo For you the sky is %color%
You can use WScript.Shell's SendKeys method through JScript or VBScript to simulate keypresses. Give this a shot. I think it does what you're looking for. It will send uneditable 1. The sky is: then simulate the user keystrokes of Blue. The user is then free either just to hit Enter, or he can Backspace to erase "Blue" and replace it whatever he wishes.
Save this with a .bat extension and run it.
#if (#a==#b) #end /*
:: batch portion
#echo off
setlocal
set /p "=1. The sky is: "<NUL
call :sendkeys Blue
set /p "sky="
echo For you the sky is %sky%. Press any key to exit.
pause >NUL
exit /b
:sendkeys <string>
cscript /nologo /e:jscript "%~f0" "%~1"
goto :EOF
:: JScript portion */
var sh = new ActiveXObject("WScript.Shell");
sh.SendKeys(WSH.Arguments(0));
set x=blue
set y=
set /p "y=The sky is [%x%] "
if not defined y set y=%x%
echo For your the sky is %y%
If user input is empty, the predefined %x% is copied to the outputvariable %y%
As other answers said, you must need a third party utility to solve this problem. For example, I used my GetKey.exe and Show.exe auxiliary programs to write ReadLine.bat, a subroutine that simulate SET /P command, that is, it read characters until Enter key is pressed and delete the last character with BackSpace key. We may modify such routine in order to provide an initial value for the input variable:
#echo off
rem Read a variable with an initialized value
rem Antonio Perez Ayala
set Bell=7
set BackSpace=8
set Enter=13
set Space=32
:ReadInitVar var="prompt" ["initial value"]
rem %1 %2 %3
setlocal EnableDelayedExpansion
Show %2
set len=0
if "%~3" equ "" (
set %1=
) else (
Show %3
set "%1=%~3"
for /L %%i in (0,1,80) do if "!%1:~%%i,1!" neq "" set /A len+=1
)
:nextKey
GetKey
set key=%errorlevel%
if %key% geq %Space% (
rem Ascii character: insert it
Show %key%
for /F "delims=" %%a in ('Show %key%') do set "%1=!%1!%%a"
set /A len+=1
) else if %key% equ %BackSpace% (
rem Backspace: delete last character
if defined %1 (
Show %BackSpace% %Space% %BackSpace%
set "%1=!%1:~0,-1!"
set /A len-=1
) else (
rem Empty value
Show %Bell%
)
)
if %key% neq %Enter% goto nextKey
echo/
for /F "delims=" %%a in ("!%1!") do endlocal & set %1=%%a
exit /B
This way, you may use this line to solve your problem:
call :ReadInitVar color="For you the sky is " "Blue"
You may download Getkey.exe and Show.exe auxiliary programs, and review the original ReadLine.bat subroutine, from this site: Advanced Batch features via auxiliary .exe programs.
There is also another subroutine similar to ReadInitVar.bat above, but that display the user input in a different color field by using ColorShow.exe auxiliary program instead of Show.exe. You may review it here: http://www.dostips.com/forum/viewtopic.php?f=3&t=4198&p=23426#p23426

How do I exclude specific file names from an MS DOS dir list?

I am creating an MS DOS batch script that needs to list every .bat file in the current directory, but not show autoexec.bat or other utilities or systems .bat files that shouldn't be run by the user.
I currently have DIR "*.bat" /B /P
This lists all .bat files appropriately, but it shows autoexec.bat. How would I exclude that from the list? Also slightly important, how could I chop off the file extensions and show more than the 7-characters DOS limits files to?
Constraints: I am not able to use a DOS version above WinME. That is the version I am using.
Thanks for any help.
EDIT:
There is plenty of information on the internet about doing this, but it is all in the windows command processor, not MS DOS. Please understand that DOS and the Command Prompt are not the same thing.
#echo off
setlocal EnableDelayedExpansion
rem Add more names separated with slashes here:
set exclude=/autoexec/
for %%a in (*.bat) do (
if "!exclude:/%%~Na/=!" equ "%exclude%" (
echo %%~Na
)
)
EDIT: Some explanations added
Batch file processing is slow, so you should use techniques that allows a Batch file to run faster. For example:
Try to use the minimum lines/commands to achieve a certain result. Try to avoid external commands (*.exe files) like find, findstr, fc, etc. specially if they work on small amounts of data; use if command instead.
Use for %%a in (*.bat)... instead of for /F %%a in ('dir /B *.bat').... The second method requires to execute cmd.exe and store its output in a file before for command can process its lines.
Avoid pipes and use redirections instead. A pipe require the execution of two copies of cmd.exe to process the command at each side of the pipe.
A simple way to check if a variable contain a given string is trying to delete the string from the variable: if the result is different then the string exists in the variable: if "!variable:%string%=!" neq "%variable%" echo The string is in the variable.
Previous method may also be used to check if a variable have anyone of a list of values: set list=one two three, if "!list:%variable%=!" neq "%list%" echo The variable have one value from the list. If the values of the list may have spaces, they must be separated by another delimiter.
EDIT: New version added as answer to new comments
The easiest way to pause one page at a time is to use more filter this way:
theBatchFile | more
However, the program must reorder the output in order to show it in columns. The new version below achieve both things, so it does not require more filter; you just need to set the desired number of columns and rows per page.
#echo off
setlocal EnableDelayedExpansion
rem Add more names separated with slashes here:
set exclude=/autoexec/
rem Set the first two next variables as desired:
set /A columns=5, rows=41, wide=(80-columns)/columns, col=0, row=0
rem Create filling spaces to align columns
set spaces=
for /L %%a in (1,1,%wide%) do set spaces= !spaces!
set line=
for %%a in (*.bat) do (
if "!exclude:/%%~Na/=!" equ "%exclude%" (
rem If this column is less than the limit...
set /A col+=1
if !col! lss %columns% (
rem ... add it to current line
set name=%%~Na%spaces%
set "line=!line!!name:~0,%wide%! "
) else (
rem ... show current line and reset it
set name=%%~Na
echo !line!!name:~0,%wide%!
set line=
set /a col=0, row+=1
rem If this row is equal to the limit...
if !row! equ %rows% (
rem ...do a pause and reset row
pause
set row=0
)
)
)
)
rem Show last line, if any
if defined line echo %line%
Antonio
attrib +h autoexec.bat
should hide autoexec.bat and it should thus not appear in the list
DIR "*.bat" /B /P | find /v "autoexec" | for %i in (*.bat) do #echo %~ni
Using for to process each file name individually:
setlocal enabledelayedexpansion
for /f %%i in ('dir "*.bat" /b') do (
set system=0
if "%%i"=="autoexec.bat" set system=1
if "%%i"=="somesystem.bat" set system=1
if !system!==0 echo %%i
)
Another method without variables:
for /f %%i in ('dir "*.bat" /b') do call :test %%i
goto continue
:test
if "%1"=="autoexec.bat" goto :eof
if "%1"=="somesystem.bat" goto :eof
echo %1
goto :eof
:continue
For both, you can add new filenames to exclude from the list.

Resources