Windows batch script ends unexpectedly - batch-file

I am not familiar with windows batches.
I'm trying to write a script that will be included in a database migration script chain. It should check if SQL scripts have a hardcoded database name, and if it finds any, should warn the user, output file names, and stop the chain execution.
I reckon there are multiple problems with it..
set "lf=^"
setlocal EnableDelayedExpansion
for %%a in (functions sprocs up views) do (
cd %%a
for /f "tokens=*" %%b in ('findstr /lism "[MyDatabase]" *.sql') do (
if not [%%b] == [] (
call :append %%a\%%b
)
)
cd ..
)
if [%sinList%] == [] (
echo No hardcoded names in scripts found, continuing..
pause
) else (
echo Cannot continue database migration until you clear out hardcoded database name in files^%lf%%lf%
echo Files with hardcode^%lf%%lf% : %sinList%
pause
exit /b 1
)
goto :eof
:append
if defined sinList (
set sinList=%sinList%%1^%lf%%lf%
) else (
set sinList=%1^%lf%%lf%
)
goto :eof
Right now when I execute it I end up in /sprocs folder, doesn't output any results.
Turning on echo looks like this
C:\Portal\Database.Migration>dbname_hardcode_punisher.cmd
C:\Portal\Database.Migration>set "lf=^"
C:\Portal\Database.Migration>set "sinList="
C:\Portal\Database.Migration>setlocal EnableDelayedExpansion
C:\Portal\Database.Migration>for %a in (functions sprocs up views) do
( cd %a for /F "tokens=*" %b in ('findstr /lism "[MyDatabase]" .sql') do
(if not [%b] == [ ] (call :append %a\%b ) ) cd .. )
C:\Portal\Database.Migration>( cd functions for /F "tokens=" %b in
('findstr /lism "[MyDatabase]" .sql') do (if not [%b] == [ ] (call
:append functions\%b ) ) cd .. ) C:\Portal\Database.Migration>( cd
sprocs for /F "tokens=" %b in ('findstr /lism "[MyDatabase]" *.sql')
do (if not [%b] == [ ] (call :append sprocs\%b ) ) cd .. )
C:\Portal\Database.Migration\sprocs>(if not [Proc_name.sql] == []
(call :append sprocs\Another_proc.sql ) )
Also I'm afraid that if [%sinList%] == [] may not return the expected results since it could have one newline?

for the LineFeed trick to work, it needs delayed expansion (both with lf and the variable) and two empty lines:
setlocal enabledelayedexpansion
set lf=^
%= don't delete =%
%= these two lines =%
set new=hello!lf!world
echo !new!
echo Hello!lf!World
(Q:Where are the two required empty lines? A: %= anything =% is not defined and therefore gets parsed as nothing (empty). Just a reminder for the programmer not to delete them; You could also use just empty lines)

Related

Batch script programmimng: save line value from a txt file into a variable [duplicate]

This question already has an answer here:
Variables are not behaving as expected
(1 answer)
Closed 3 years ago.
I want to read a text file where every line has a number. Negative numbers need to be replaced by 0 and written to a new file along with the rest of the positive numbers.
The problem is that I want to save the value of a line, i.e. %%a into a new variable. Then I'll check if the first character of that variable is '-' if so, I'll set the value to 0 for that line in the final file if not, it will remain as is.
But I cannot save the line value into anything. Below is my code.
3.txt is the original file, tempfile.txt is the final file.
set filem=3.txt
set tempfile=tempfile.txt
for /f "tokens=*" %%a in (%filem%) do (
set linevalue=%%a
IF %linevalue:~0,1% EQU - (
echo 0>>%tempfile%
) ELSE (
echo %%a>>%tempfile%
)
)
pause
If your numbers are really integers, you could give the following method a try:
#Echo Off
Set "filem=3.txt"
Set "tempfile=tempfile.txt"
If Exist "%filem%" (
( For /F "Tokens=1* Delims=]" %%G In (
'""%__AppDir__%find.exe" /N /V ""<"%filem%""'
) Do If "%%H" == "" (
Echo=%%H
) Else (
If %%H Lss 0 (
Echo 0
) Else Echo %%H
)
)>"%tempfile%"
)
Pause
If they're floating point numbers, you'd be both setting and using variables within the same code block, so you would need to use delayed expansion, (which was missing from your code):
#Echo Off
SetLocal DisableDelayedExpansion
Set "filem=3.txt"
Set "tempfile=tempfile.txt"
If Exist "%filem%" (
( For /F "Tokens=1* Delims=]" %%G In (
'""%__AppDir__%find.exe" /N /V ""<"%filem%""'
) Do If "%%H" == "" (
Echo=%%H
) Else (
Set "LineVal=%%H"
SetLocal EnableDelayedExpansion
If "!LineVal:~,1!" == "-" (
Echo 0
) Else Echo %%H
EndLocal
)
)>"%tempfile%"
)
Pause
I am assuming that you will run the bat/cmd in the same directory/folder as your code files, otherwise, edit the cd /d "%~dp0" adding the full/complete folder of your files.
#echo off
cd /d "%~dp0"
set "filem=.\3.txt"
type nul >".\tempfile.txt"
set "tempfile=.\tempfile.txt"
for /f "tokens=1*" %%a in ('type "%filem%"')do (
echo["%%~a"|%__APPDIR__%findstr.exe \-[0-9] >nul && (
echo[0>>"%tempfile%" ) || ( echo/%%~a%%b>>"%tempfile%" )
)
goto :EOF

creating new files with different names in Batch

In the office we have a extense and well defined file structure , and i have to create a hundreds of directories with the same name but differing in the last numbers.
directory0001
directory0002
...
directory0324
this is what i have done:
SET B=0001
SET C=0324
:while1
IF NOT %B%==%C%
(
echo "first loop"
SET COUNTER=0
IF NOT %COUNTER%=1
(
echo "Secoond loop"
mkdir "C:\pathfile\directory00"%B%
SET COUNTER==1
)
else()
SET B=%B%+1
goto :while1
)
else
(
)
I'm not sure whether i'm using properly the operators or not, i'm using what i've found in different posts.
-i'm using windows terminal to debugg the code, there is a better way?
There is a FOR loop for exactly this, incrementing a count from one value to the end value:
for /L %%i in (1,1,324) do if %%i LEQ 9 ( md "C:\pathfile\directory000%i" ) else if %%i LEQ 99 ( md "C:\pathfile\directory00%i" ) else ( md "C:\pathfile\directory0%%i" )
This will start at 1, increment by 1, until 324 is reached. The IF statement is only needed for formatting leading zeroes.
Edit:
this is the complete code with proper indentation so that you (as a novice) can understand the flow more easily:
#echo off
SETLOCAL ENABLEEXTENSIONS
REM enable cmd extensions so that mkdir/md will create all intermediate folders
SET first=1
SET last=324
REM numeric extension will be appended with 4 places to this foldername
SET folder=C:\users\goofy\manydirs\directory
FOR /L %%i in (%first%,1,%last%) DO (
IF %%i LEQ 9 (
mkdir %folder%000%%i
) ELSE IF %%i LEQ 99 (
mkdir %folder%00%%i
) ELSE IF %%i LEQ 999 (
mkdir %folder%0%%i
) ELSE (
mkdir %folder%%%i
)
)

Split string in batch file

I am new to using batch files so could someone please help me split a string i am getting from a file.
I am using %USERPROFILE% to get my string.
The string is: "C:\Users\nicholas"
I would like to keep the C:\ part, but get rid of the \Users\nicholas part.
for /f "tokens=2 delims=\" %A in ('set userprofile') do echo %A\
See for /?
Also
echo %userprofile:~0,3%
If you carefully read the output of set /?, you'll find the answer:
May also specify substrings for an expansion.
%PATH:~10,5%
would expand the PATH environment variable, and then use only the 5
characters that begin at the 11th (offset 10) character of the expanded
result.
So, you can use something like this to get the first 3 characters of your string:
> echo %userprofile:~0,3%
C:\
I As you need the drive where where the users are located you can use directly
%systemdrive% variable - this is the drive where the windows is installed
II the easiest way to get a drive from path:
for %%a in ("%userprofile%") do echo %%~da\
%~da - expands a path to its drive only
III over-complicated but powerful way (split function that can be used for a different things):
#echo off
call :split "%userprofile%" "\" 1 drive
echo %drive%\
goto :eof
:split [%1 - string to be splitted;%2 - split by;%3 - possition to get; %4 - if defined will store the result in variable with same name]
::http://ss64.org/viewtopic.php?id=1687
setlocal EnableDelayedExpansion
set "string=%~2%~1"
set "splitter=%~2"
set /a position=%~3
set LF=^
rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
for /f "delims=" %%R in ("!splitter!") do (
set "var=!string:%%~R%%~R=%%~L!"
set "var=!var:%%~R=%%~L!"
if "!var!" EQU "!string!" (
echo "%~1" does not contain "!splitter!" >&2
exit /B 1
)
)
)
if "!var!" equ "" (
endlocal & if "%~4" NEQ "" ( set "%~4=")
)
if !position! LEQ 0 ( set "_skip=" ) else (set "_skip=skip=%position%")
for /f "eol= %_skip% delims=" %%P in (""!var!"") DO (
if "%%~P" neq "" (
set "part=%%~P"
goto :end_for
)
)
set "part="
:end_for
if not defined part (
endlocal
echo Index Out Of Bound >&2
exit /B 2
)
endlocal & if "%~4" NEQ "" (set %~4=%part%) else echo %part%
exit /b 0

Printing empty lines and lines that end in a zero to a file

I have this batch file I am developing. It is supposed to change two values in a config file, let's say ValueA and ValueB. The config file is in this format:
[Section]
ValueX=5
ValueA=6
ValueY=True
[Section2]
ValueB=9
ValueZ=4
I loop through the original file and write an updated one to %temp%.
for /F "tokens=* delims= usebackq" %%F in ("C:\config.txt") do call :doline "%%F"
goto :eof
:doline
set line=%~1
if "%line:~0,7%" equ "ValueA=" (
echo.Width=%valuea%>> %temp%\tempcfg
) else (
if "%line:~0,7%" equ "ValueB=" (
echo.Height=%valueb%>> %temp%\tempcfg
) else (
echo.%line%>> %temp%\tempcfg
)
)
This has two flaws, mainly at the line echo.%line%>> %temp%\tempcfg.
Empty lines are not printed.
Lines ending with =0 are printed to standard output, not the file. I tried changing the line.
echo.%line% >> %temp%\tempcfg prints a space in the end of each line.
echo.%line% 1>> %temp%\tempcfg does the same as above.
echo.%line%1>> %temp%\tempcfg prints an 1 in the end of each line.
Do you have any suggestions how to fix these?
Why the empty lines are not sent to output? Because they are not processed by the for command. By dessign, empty lines are skipped. We need to ensure the line is processed adding content to it. Usual way is using findstr to number lines and then removing them
for /F "tokens=1,* delims=:" %%F in ('findstr /n "^" "C:\config.txt"'
) do call :doline "%%G"
for executes findstr to number the lines, then each line is splitted using the colon as delimiter, %%F gets the line number and %%G the rest of the line.
Now, inside your :doline subroutine, you have to deal with the problem of the numeric ended lines. This can be solved changing the way to write the commands. Fisrt indicate where to send the data and then echo the data
:doline
set "line=%~1"
if "%line:~0,7%" equ "ValueA=" (
>> %temp%\tempcfg echo(Width=!line:~7!
) else if "%line:~0,7%" equ "ValueB=" (
>> %temp%\tempcfg echo(Height=!line:~7!
) else (
>> %temp%\tempcfg echo(!line!
)
Or, still better, don't redirect inside your subroutine and redirect the full output of your for command
#echo off
setlocal
(
for /F "tokens=1,* delims=:" %%F in (
'findstr /n "^" "C:\config.txt"'
) do call :doline "%%G"
) > %temp%\tempcfg
endlocal
goto :EOF
:doline
set "line=%~1"
if "%line:~0,7%" equ "ValueA=" (
echo(Width=!line:~7!
) else if "%line:~0,7%" equ "ValueB=" (
echo(Height=!line:~7!
) else (
echo(!line!
)
The problem is that the %line% contents are evaluated before the line is parsed. So anything that contains a numeric followed by >> is understood to mean: redirect output to the specified numeric output handle, rather than the default output.
See: Using command redirection operators
To solve this, use delayed variable expansion instead; this way, the meaning of the line is parsed before the variable is actually expanded in-place.
The following works for me on the Windows 7 command line.
setlocal ENABLEDELAYEDEXPANSION
for /F "tokens=* delims= usebackq" %%F in ("C:\config.txt") do call :doline "%%F"
endlocal
goto :eof
:doline
set line=%~1
if "%line:~0,7%" equ "ValueA=" (
echo.Width=!line:~7!>> %temp%\tempcfg
) else (
if "%line:~0,7%" equ "ValueB=" (
echo.Height=!line:~7!>> %temp%\tempcfg
) else (
echo.!line!>> %temp%\tempcfg
)
)
Edit: regarding empty lines, apparently it is expected behaviour for the FOR command to gobble these up. See DOS batch FOR loop with FIND.exe is stripping out blank lines? for a potential solution.

How to receive even the strangest command line parameters?

as discussed in an other thread How to avoid cmd.exe interpreting shell special characters like < > ^
it is not easy to get all parameters from the command line.
A simple
set var=%1
set "var=%~1"
are not enough, if you have a request like
myBatch.bat abc"&"^&def
I have one solution, but it needs a temporary file, and it is also not bullet proof.
#echo off
setlocal DisableDelayedExpansion
set "prompt=X"
(
#echo on
for %%a in (4) do (
rem #%1#
)
) > XY.txt
#echo off
for /F "delims=" %%a in (xy.txt) DO (
set "param=%%a"
)
setlocal EnableDelayedExpansion
set param=!param:~7,-4!
echo param='!param!'
It fails with something like myBatch.bat %a, it display 4 not the %a
in this situation a simple echo %1 would work.
It's obviously the for-loop but I don't know how to change this.
Perhaps there exists another simple solution.
I don't need this to solve an actual problem, but I like solutions that are bullet proof in each situation, not only in the most cases.
I don't think anyone found any holes in this, except for the inability to read newlines in the parameters:
#echo off
setlocal enableDelayedExpansion
set argCnt=1
:getArgs
>"%temp%\getArg.txt" <"%temp%\getArg.txt" (
setlocal disableExtensions
set prompt=#
echo on
for %%a in (%%a) do rem . %1.
echo off
endlocal
set /p "arg%argCnt%="
set /p "arg%argCnt%="
set "arg%argCnt%=!arg%argCnt%:~7,-2!"
if defined arg%argCnt% (
set /a argCnt+=1
shift /1
goto :getArgs
) else set /a argCnt-=1
)
del "%temp%\getArg.txt"
set arg
The above comes from a lively DosTips discussion - http://www.dostips.com/forum/viewtopic.php?p=13002#p13002. DosTips user Liviu came up with the critical SETLOCAL DisableExtensions piece.
The code below is based on the rambling Foolproof Counting of Arguments topic on DosTips and this answer by jeb:
#echo off & setLocal enableExtensions disableDelayedExpansion
(call;) %= sets errorLevel to 0 =%
:: initialise variables
set "paramC=0" & set "pFile=%tmp%\param.tmp"
:loop - the main loop
:: inc param counter and reset var storing nth param
set /a paramC+=1 & set "pN="
:: ECHO is turned on, %1 is expanded inside REM, GOTO jumps over REM,
:: and the output is redirected to param file
for %%A in (%%A) do (
setLocal disableExtensions
set prompt=#
echo on
for %%B in (%%B) do (
#goto skip
rem # %1 #
) %= for B =%
:skip - do not re-use this label
#echo off
endLocal
) >"%pFile%" %= for A =%
:: count lines in param file
for /f %%A in ('
find /c /v "" ^<"%pFile%"
') do if %%A neq 5 (
>&2 echo(multiline parameter values not supported & goto die
) %= if =%
:: extract and trim param value
for /f "useBack skip=3 delims=" %%A in ("%pFile%") do (
if not defined pN set "pN=%%A"
) %= for /f =%
set "pN=%pN:~7,-3%"
:: die if param value is " or "", else trim leading/trailing quotes
if defined pN (
setLocal enableDelayedExpansion
(call) %= OR emulation =%
if !pN!==^" (call;)
if !pN!=="" (call;)
if errorLevel 1 (
for /f delims^=^ eol^= %%A in ("!pN!") do (
endLocal & set "pN=%%~A"
) %= for /f =%
) else (
>&2 echo(empty parameter values (""^) not supported & goto die
) %= if errorLevel =%
) else (
:: no more params on cmd line
set /a paramC-=1 & goto last
) %= if defined =%
:: die if param value contains "
if not "%pN:"=""%"=="%pN:"=%" (
>&2 echo(quotes (^"^) in parameter values not supported & goto die
) %= if =%
:: assign nth param, shift params, and return to start of loop
set "param%paramC%=%pN%" & shift /1 & goto loop
:last - reached end of params
:: no param values on cmd line
if %paramC% equ 0 (
>&2 echo(no parameter values found & goto die
) %= if =%
:: list params
set param
goto end
:die
(call) %= sets errorLevel to 1 =%
:end
:: exit with appropriate errorLevel
endLocal & goto :EOF
The following conditions will terminate the program immediately:
no parameters found
multiline parameter
empty parameter (""", or " is permitted for the last parameter)
one or more quotes (") in a parameter value
To ease these restrictions, simply comment out the relevant lines. Read the inline comments for more information. Do not attempt to turn off the multiline parameter trap!
I invented the syntax-error-technic to solve the problem (partially).
With this solution it's even possible to receive multiline parameters and also carriage return characters.
There is no known parameter which fails!
BUT the drawback of this solution, the main process exits and only a child process continues.
That is a consequence of the capture trick, a syntax error is created by using an invalid parenthesis block ( Prepare ) PARAMS....
But the syntax error itself outputs the complete block, including the expanded value of %*.
The output is redirected to a file by the permanent redirect technic.
And the child process can retrieve the complete parameter from the file.
This solution can be useful, when the batch file only handles the parameter and always exit afterwards.
#echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Clear params.tmp
break > params.tmp
start "" /b cmd /k "%~d0\:StayAlive:\..\%~pnx0 params.tmp"
(set LF=^
%=empty=%
)
REM *** Change prompt for better recognition
prompt #PROMPT#
REM *** Change streams permanently
REM *** stream1 redirects to params.tmp
REM *** stream2 redirects to nul
echo on >nul 2>nul 0>nul 3>params.tmp 4>nul 5>&3
#REM *** This is the magic part, it forces a syntax error, the error message itself shows the expanded %asterix without ANY modification
( Prepare ) PARAMS:%LF%%*%LF%
echo Works
exit /b
REM *** Second thread to fetch and show the parameters
:StayAlive
:__WaitForParams
if %~z1 EQU 0 (
goto :__WaitForParams
)
REM *** Show the result
findstr /n "^" %1
It's up to the user who types the command to escape any special characters. Your program cannot do anything about what the shell does before your program even runs. There is no other "bullet proof" solution to this.

Resources