Avoiding Spaghetti Code in Batch Files

JBourne

New member
Local time
1:03 PM
Messages
10
I've been reading how to avoid spaghetti code in batch files(Batch files - GOTO, and How To Avoid "Spaghetti Code").

In the example of what spaghetti code is, I realized that the batch file that I use when I logon almost fits this example. Could someone please help me make my batch file more robust, and not have spaghetti code?

Code:
@ECHO OFF
CLS


:MENU
echo Welcome %USERNAME%

echo 1 - Start KeePass
echo 2 - Backup
echo 3 - FireFox
echo 4 - Exit

SET /P M=Please Enter Selection, then Press Enter:

IF %M%==1 GOTO StarKeePass
IF %M%==2 GOTO Backup
IF %M%==3 GOTO FireFox
IF %M%==4 GOTO :EOF
GOTO MENU


:StarKeePass
SET keePass="%USERPROFILE%\KeePass\KeePass-2.30\KeePass.exe"
SET kdb="%USERPROFILE%\KeePass\PasswordDatabase\PasswordDatabase.kdbx"

echo I'll start KeePass for You
START "" %keePass% %kdb% 

GOTO MENU

:Backup
SET backup="%USERPROFILE%\backup.bat"
call %backup%

GOTO MENU

:FireFox
cd "C:\Program Files (x86)\Mozilla Firefox\"
start firefox.exe

GOTO MENU
 

My Computer

Computer type
PC/Desktop
OS
Windows 7 64-Bit
That is not spaghetti code. It's very readable and easy to understand. No need to complicate it.
 

My Computer

Computer type
PC/Desktop
Computer Manufacturer/Model Number
Lenovo IdeaCenter 450
OS
Windows 10 Pro X64
CPU
Intel Quad Core i7-4770 @ 3.4Ghz
Memory
16.0GB PC3-12800 DDR3 SDRAM 1600 MHz
Graphics Card(s)
Intel Integrated HD Graphics
Sound Card
Realtek HD Audio
Monitor(s) Displays
HP 22" LCD
Screen Resolution
1680 x 1050
Hard Drives
250GB Samsung EVO SATA-3 SSD
2TB Seagate ST2000DM001 SATA-2
1.5TB Seagate ST3150041AS SATA
Keyboard
Dell USB
Mouse
Lenovo USB
Internet Speed
Cable via Road Runner 3MB Upload, 30MB Download
Antivirus
Windows Defender, MBAM Pro, MBAE
Browser
Seamonkey
Other Info
UEFI/GPT
PLDS DVD-RW DH16AERSH
Hi JBourne,

I'm impressed with your athirst for better looking code. But you should realise that batch was never designed to be pretty; it's probably the most ugliest language there is. Be as messy as you want. Certainly, no one is ever going to tell you off for spaghetti code in batch.

That being said, I do very much dislike Goto myself. Using Goto is generally considered bad practise because it is notorious for making code an unsightly mess: "spaghetti code". I'd never use a Goto statement in a batch if I could. Unfortunately, there are a couple of instances in batch where you are pretty much forced use Goto. For instance, if you need to instate a while loop (which batch doesn't provide a statement for), there's not much choice but to construct one out of labels and Gotos.

In most cases a label and its associated Goto can be replaced by a function. Functions neaten programs by giving them more 'structure'. Though batch files don't have real functions, a nice standard has been developed. Here's an example of using functions in batch, complete with parameters and return values:

FunctionsInBatch.bat
Code:
@echo off
goto :main

:add_two_numbers Num1 Num2
setlocal
	set /a result=%1 + %2
endlocal & set /a result=%RESULT%
goto :eof

:multiply_two_numbers Num1 Num2
setlocal
	set /a result=%1 * %2
endlocal & set /a result=%RESULT%
goto :eof

:evaluate_two_numbers Num1 Num2 Operator
setlocal
	set /a result=%1 %3 %2
endlocal & set /a result=%RESULT%
goto :eof

:main
set/p=12 plus 6 is <NUL
call :add_two_numbers 12 6
echo %RESULT%!

set/p=4 times 26 is <NUL
call :multiply_two_numbers 4 24
echo %RESULT%!

set/p=the remainer of 99 divided by 4 is <NUL
call :evaluate_two_numbers 99 4 %%%%
echo %RESULT%!

If you wish to implement functions effectively in batch, I suggest studying RootOfTheNull's (channel now renamed to 'John Hammond') Basic Functions tutorial up to his tutorial #26. I highly recommend watching a hand full of that YouTuber's batch series if you wish to delve deeper into the anomalous language of batch.


JBourne, for your interest, I've rewritten your batch file the exact way I would write it.
Code:
@echo off
goto :main

:StarKeePass
	set keePass="%USERPROFILE%\KeePass\KeePass-2.30\KeePass.exe"
	set kdb="%USERPROFILE%\KeePass\PasswordDatabase\PasswordDatabase.kdbx"
	echo I'll start KeePass for You
	START "" %keePass% %kdb%
goto :eof

:Backup
	set backup="%USERPROFILE%\backup.bat"
	call %backup%
goto :eof

:FireFox
	start "" "C:\Program Files (x86)\Mozilla Firefox\firefox.exe"
goto :eof

:main
cls
echo.& echo Welcome %USERNAME%,& echo.
echo   1 - Start KeePass
echo   2 - Backup
echo   3 - FireFox
echo   4 - Exit
echo. 

choice /c "1234" /t 20 /d "4" /m "Please Type Selection, then Press Enter:"
echo.
if not errorlevel 2 call :StarKeePass
if not errorlevel 3 call :Backup
if not errorlevel 4 call :FireFox
if not errorlevel 5 goto :eof
goto :main

Note the "goto :main" near the top of the script can be found in all my batch files that use functions, which, aside from creating while loops (such as the functionality of the "goto :main" at the bottom), is the only time I would reckon to use a Goto.


If I must state a conclusion here, forget that Robvanderwoude article and just go with a style that you enjoy writing in, which is easy for you to read. A script will be better managed if you write it yourself.
 

My Computer

Computer type
PC/Desktop
OS
Windows 10, Windows 8.1 Pro, Windows 7 Professional, OS X El Capitan
You have 4 Gotos in your batch file, JBourne also had 4 :) :)

Just sayin ...
 

My Computer

Computer type
PC/Desktop
Computer Manufacturer/Model Number
Lenovo IdeaCenter 450
OS
Windows 10 Pro X64
CPU
Intel Quad Core i7-4770 @ 3.4Ghz
Memory
16.0GB PC3-12800 DDR3 SDRAM 1600 MHz
Graphics Card(s)
Intel Integrated HD Graphics
Sound Card
Realtek HD Audio
Monitor(s) Displays
HP 22" LCD
Screen Resolution
1680 x 1050
Hard Drives
250GB Samsung EVO SATA-3 SSD
2TB Seagate ST2000DM001 SATA-2
1.5TB Seagate ST3150041AS SATA
Keyboard
Dell USB
Mouse
Lenovo USB
Internet Speed
Cable via Road Runner 3MB Upload, 30MB Download
Antivirus
Windows Defender, MBAM Pro, MBAE
Browser
Seamonkey
Other Info
UEFI/GPT
PLDS DVD-RW DH16AERSH
Pyro has it right. Cmd batch language is lame. It traces its roots back to the early '70s with the CP/M operating system. It was garbage in the 20th century, and ought to be abolished in the 21st.

If you want better code, use a better language.
bash is better.
powershell is better.
 

My Computer

Computer type
PC/Desktop
Computer Manufacturer/Model Number
Dell
OS
Windows 10 x64
CPU
i7-7700K
Memory
16 GB 2400 MHz
Graphics Card(s)
GTX 1060
Sound Card
Integrated, plus external Presonus Audiobox USB
Monitor(s) Displays
2x AOC 27"
Screen Resolution
1920x1080
Hard Drives
512 GB M.2 SSD
2 TB 7200 RPM disk
Internet Speed
110 Mbps
Browser
Firefox
Batch is the easiest for novices though. It can do some pretty amazing things. Some of the batch scripts Pyprohly has posted in response to people looking for help have been excellent.
 

My Computer

Computer type
PC/Desktop
Computer Manufacturer/Model Number
Lenovo IdeaCenter 450
OS
Windows 10 Pro X64
CPU
Intel Quad Core i7-4770 @ 3.4Ghz
Memory
16.0GB PC3-12800 DDR3 SDRAM 1600 MHz
Graphics Card(s)
Intel Integrated HD Graphics
Sound Card
Realtek HD Audio
Monitor(s) Displays
HP 22" LCD
Screen Resolution
1680 x 1050
Hard Drives
250GB Samsung EVO SATA-3 SSD
2TB Seagate ST2000DM001 SATA-2
1.5TB Seagate ST3150041AS SATA
Keyboard
Dell USB
Mouse
Lenovo USB
Internet Speed
Cable via Road Runner 3MB Upload, 30MB Download
Antivirus
Windows Defender, MBAM Pro, MBAE
Browser
Seamonkey
Other Info
UEFI/GPT
PLDS DVD-RW DH16AERSH
You have 4 Gotos in your batch file, JBourne also had 4 :) :)

Just sayin ...
Well, as you know, "goto :eof" takes on a special meaning. Most of the "goto :eof" statements in my batch program, specifically the ones that conclude each function, may of course be replaced with "exit /b" which is functionally equivalent to "goto :eof". "Goto :eof" is just more commonly used by batch-ers than "exit /b" for some reason.

Furthermore, rearranging the structure of the program slightly, moving the ":main" section further to the top of the script will eliminate another Goto. But this would mean having functions below the main algorithm. It is good programming practise to declare functions and variables at the top of a program.

Here's the batch file I describe, which has as few Gotos as possible.
Code:
@echo off
:main
cls
echo.& echo Welcome %USERNAME%,& echo.
echo   1 - Start KeePass
echo   2 - Backup
echo   3 - FireFox
echo   4 - Exit
echo. 
 
choice /c "1234" /t 20 /d "4" /m "Please Type Selection, then Press Enter:"
echo.
if not errorlevel 2 call :StarKeePass
if not errorlevel 3 call :Backup
if not errorlevel 4 call :FireFox
if not errorlevel 5 exit /b
goto :main
 
:StarKeePass
	set keePass="%USERPROFILE%\KeePass\KeePass-2.30\KeePass.exe"
	set kdb="%USERPROFILE%\KeePass\PasswordDatabase\PasswordDatabase.kdbx"
	echo I'll start KeePass for You
	start "" %keePass% %kdb%
exit /b
 
:Backup
	set backup="%USERPROFILE%\backup.bat"
	call %backup%
exit /b
 
:FireFox
	start "" "C:\Program Files (x86)\Mozilla Firefox\firefox.exe"
exit /b
... The result is a grand total of one Goto statement. This last Goto cannot be avoided as it is part of a while loop construct—using Gotos and labels is the only stable way to replicate a while loop in the batch language.


Cmd batch language is lame. [...] It was garbage in the 20th century, and ought to be abolished in the 21st.
Unfortunately or fortunately, we won't be seeing batch files completely disappear from our Windows any time soon—only fade.

Here's what PowerShell expert Jeffrey Snover (one of the designers and developers of PowerShell) had to say about batch files when they were mentioned in an interview about PowerShell,
Erik Meijer: And so no more batch files now--?
Jeffrey Snover: Well, they'll still be there. They're like some... disease you die with but not of. Uh, and...
Erik Meijer & Jeffrey Snover: *chuckles*
Jeffrey Snover: Well, you know, you can't get rid of that stuff. But yeah, I think you'll see fewer and fewer usages of them.


Batch is the easiest for novices though.
Though that is up for debate. I'm sure many would agree that PowerShell is more intuitive and user-friendly than batch.
 

My Computer

Computer type
PC/Desktop
OS
Windows 10, Windows 8.1 Pro, Windows 7 Professional, OS X El Capitan
You could also use a Visual Basic script.
 

My Computer

Computer type
PC/Desktop
Computer Manufacturer/Model Number
n/a
OS
W7 Ultimate SP1, LM19.2 MATE, W10 Home 1703, W10 Pro 1703 VM, #All 64 bit
CPU
AMD Phenom II x6 1100T, 3.3 GHz
Motherboard
ASUS M4A88T-M/USB3 (AM3)
Memory
12GB DDR3 1333 G-Skill (4GB x 2), G-Skill (2GB x 2)
Graphics Card(s)
NVIDIA GeForce GTX 660
Sound Card
Realtek?
Monitor(s) Displays
Samsung S23B350
Screen Resolution
1920x1080
Hard Drives
WD Green 2TB (SATA), WD Green 3TB (SATA), WD Blue 4TB (SATA), WD Blue 6TB (SATA)
PSU
Cooler Master
Case
Antec GX300 Tower
Cooling
3x Antec TRICOOL 120mm Fans
Mouse
Wired Optical
Internet Speed
DSL
Antivirus
Avast
Browser
Pale Moon (64 bit)
Other Info
2018-12-27 Upgraded HDDs
2015-12-10 Upgraded case, graphics card, storage
2015-08-15 Upgraded motherboard & RAM
2015-07-15 Upgraded LM17.1 to LM17.2
If I need more than a batch file, I use Open Object Rexx. I started using Rexx on IBM mainframes in the 70s and have used it in one form or another on every PC I've owned since.

Sample of the beginning of one of the Rexx programs I use every day to synchronize Seamonkey mail between my Desktop and laptop.

Code:
/****************************************************************************/
/* Copy Mozilla Mail data between Laptop and Desktop. Direction based       */
/* on where program is run.                                                 */
/****************************************************************************/
'@Echo off'
ver='v1.0'
Parse source whoami
Parse value whoami with . . whoami
Say Filespec('NAME',whoami) ver

curdir=Directory()                                 /* remember where we are*/

/***********************************/
/* Register all REXXUTIL functions */
/***********************************/
rc=RxFuncAdd('SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs')
If rc>1 then
  Do
    Say 'Unable to register REXXUTIL functions.'
    Signal Done
  End
Call SysLoadFuncs

/*********************/
/* See if push wanted */
/*********************/

Arg p1 p2 p3 .
If p1='/?' Then
  Signal Tell
If p1 \= '' & Wordpos(p1,'DOIT PUSH FORCE TEST RICH MAUREEN') == 0 Then
  Do
    Say 'Invalid parameter: 'p1
    Signal Done
  End
If p2 \= '' & Wordpos(p2,'DOIT PUSH FORCE TEST RICH MAUREEN') == 0 Then
  Do
    Say 'Invalid parameter: 'p2
    Signal Done
  End
If p3 \= '' & Wordpos(p3,'DOIT PUSH FORCE TEST RICH MAUREEN') == 0 Then
  Do
    Say 'Invalid parameter: 'p3
    Signal Done
  End
 

My Computer

Computer type
PC/Desktop
Computer Manufacturer/Model Number
Lenovo IdeaCenter 450
OS
Windows 10 Pro X64
CPU
Intel Quad Core i7-4770 @ 3.4Ghz
Memory
16.0GB PC3-12800 DDR3 SDRAM 1600 MHz
Graphics Card(s)
Intel Integrated HD Graphics
Sound Card
Realtek HD Audio
Monitor(s) Displays
HP 22" LCD
Screen Resolution
1680 x 1050
Hard Drives
250GB Samsung EVO SATA-3 SSD
2TB Seagate ST2000DM001 SATA-2
1.5TB Seagate ST3150041AS SATA
Keyboard
Dell USB
Mouse
Lenovo USB
Internet Speed
Cable via Road Runner 3MB Upload, 30MB Download
Antivirus
Windows Defender, MBAM Pro, MBAE
Browser
Seamonkey
Other Info
UEFI/GPT
PLDS DVD-RW DH16AERSH
The more sophisticated programmers avoid "goto" statements[1] by using the "comefrom"[2]:

1: E. W. Dijkstra, "GOTO Statement Considered Harmful," Letter of the Editor, Communications of the ACM, March 1968
2: Clarke, Lawrence, "We don't know where to GOTO if we don't know where we've COME FROM. This linguistic innovation lives up to all expectations.", Datamation, 1973
 

My Computer

Computer type
Laptop
Computer Manufacturer/Model Number
Lenovo
OS
Win 7 Pro 64
CPU
Core i5 M540
Memory
4Gb
Hard Drives
Seagate 1Tb SSHD
Back
Top