I like to have a final PAUSE in my *.bat scripts so I can just double click on them in Windows explorer and have the chance to read the output. However, the final PAUSE is an annoyance when I run the same script from the command line.
Is there any way to detect whether we are running the script from a command prompt (or not) and insert the PAUSE (or not) accordingly?
(Target environment is Windows XP and greater.)
Update
I've managed to compose this from Anders's answer:
(((echo.%cmdcmdline%)|find /I "%~0")>nul)
if %errorlevel% equ 0 (
set GUI=1
) else (
set CLI=1
)
Then, I can do stuff like this:
if defined GUI pause
#echo off
echo.Hello World
(((echo.%cmdcmdline%)|find /I "%~0")>nul)&&pause
...NT+ only, no %cmdcmdline% in Win9x probably.
As pointed out by E M in the comments, putting all of this on one line opens you up to some edge cases where %cmdcmdline% will escape out of the parenthesis. The workaround is to use two lines:
#echo off
echo.Hello World
echo.%cmdcmdline% | find /I "%~0" >nul
if not errorlevel 1 pause
I doubt that there's a distinction, because I think it just starts a command prompt and then runs the bat when you double click on it.
However, if you make shortcuts to the bat files and go to Properties and add in an extra argument (something like "/p") in the "Target" field, then you could check for the presence of that argument at the end of the script and pause if it is set. Then, running from the shortcut would cause it to end in a pause and running from command line wouldn't.
I was hoping the answer by #Anders would work in its own .bat file. Unfortunately, it does not for me. Based on #DarinH's comment, perhaps it does for some. The script below should work for all, but requires an extra parameter.
The key lies in the %CmdCmdLine% environment variable, which I imagine might be a bit different for a few edge cases.
PauseIfGui.bat
#echo off
if "%~1" == "" ((echo.%CmdCmdLine%)|"%WinDir%\System32\find.exe" /I "%~0")>nul && pause & exit /b
((echo.%CmdCmdLine%)|"%WinDir%\System32\find.exe" /I "%~1")>nul && pause
This accepts one optional parameter: the full path of calling script. If no params are passed, it runs the same as #Anders script.
AnyOtherFile.bat
#echo off
call PauseIfGui.bat %~f0
If opened from Explorer (i.e. double-clicking) , AnyOtherFile.bat will pause. If called from a command prompt, it will not.
Related
I am coding a batch file and it needs some more files. But they files should only be able to run using the call function from another batch file. My code looks like this:
call compileData.bat
pause
I want the compilerData.bat just starts when it's called from this one, not if its just started from Explorer or something other.
Can you please help me?
I have tried to find a solution on this problem in a whole hour!
You can use a parameter.
compileData.bat:
if "%1" neq "somestring" exit /b
REM rest of your code
Another.bat:
call compileData.bat somestring
pause
I cannot think of any way that would prevent the bare "run" of the called script. Possibly that might only be done using NTFS permissions.
What you can do quickly is something like this:
MOTHERBATCH.bat
call compileData.bat SomePASSPHRASE
compileData.bat
#echo off
if not "%1"=="SomePASSPHRASE" (
echo "You can not run this script directly, please run MOTHERSCRIPT.bat."
exit /B 1
)
echo "Passphrase is correct, code is executed..."
Set an environment variable in the parent script, then if that variable is not set or doesn't have the correct value in the children, they just exit with an error message explaining they aren't intended for standalone use. You really can't prevent someone from reverse engineering the code and forcing it to run.
You could put the children in a password protected zip file and have the parent unpack it just before calling them. Then when the parent is done, it deletes the unpacked scripts.
Do all of the above.
You can use a not so well known system variable named cmdcmdline.
I will explain a brief usage for you.
For brevity's sake we will have two very simple batch files.
Parent.bat
#echo off
call compiledata.bat
And compiledata.bat
#echo off
echo %cmdcmdline%
pause
When compiledata.bat is executed on its own this variable's value is the batch file itself.
C:\WINDOWS\system32\cmd.exe /c ""C:\Batch\CALL\compiledata.bat" "
But when compiledata.bat is called from parent.bat the variable's value is that of the calling parent.bat.
C:\WINDOWS\system32\cmd.exe /c ""C:\Batch\CALL\parent.bat" "
My suggestion is putting all your batch code into a single batch file and use subroutines. Open a command prompt window and run call /? for help on how to use subroutines which is nothing else than calling a batch file being embedded in current batch file.
A simple example:
#echo off
echo Running %~f0 %*
call :compileData %*
call :WaitForUser
rem The next line results in exiting processing of this batch file
goto :EOF
:compileData
echo/
echo Running subroutine compileData with the arguments: %*
rem Exit processing subroutine compileData and continue above
rem after the command line calling the subroutine compileData.
goto :EOF
:WaitForUser
echo/
pause
rem Exit processing subroutine WaitForUser and continue above
rem after the command line calling the subroutine WaitForUser.
goto :EOF
See also Where does GOTO :EOF return to? And take a look on DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ for the explanation on using echo/ to output an empty line.
Here's my solution:
when launched from the command line, %cmdcmdline% inherits the name from the base calling program, so it wouldn't be the name of the "middle man" calling your batch file
this is what I came up with. I had to use the "subroutine" method to get the variables properly expanded
Note: Edge Case: if you use complex paths with the batch files having the same name in different folders, you could run into an "Edge Case". If that is important to you, then you might have to further parse the file names. I'm not totally sure, it wasn't my use case so I didn't go further.
#echo OFF
setlocal EnableDelayedExpansion
call :myGetFileName "%CmdCmdLine%"
if /I "%sRet%"=="%~nx0" (
echo ************** Pause
) else (
echo ************** NO Pause
)
echo finished test
pause
exit
:myGetFileName
set "sRet=%~nx1"
exit /b
I have a little program and when i run "if exist" it closes, can somebody tell me why? I have checked for syntax errors and none (i think), i will paste my code here and see what you can do.
echo What drive would you like to launch MCEdit on?
set /p DRIVE="Drive: "
if exist "%DRIVE%:\MC\DATA\mcedit\mcedit.exe" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
) else (
echo You do not have MCEdit installed on this drive, would you like to install it?
set /p YesNoIn="[y/n]: "
if "%YesNoIn%"=="y" goto:yes
goto:menu
:yes
echo Choose the "mcedit.exe" file from a current installation on your pc.
pause
call "%DRIVE%\MC\DATA\FileViewer.bat"
xcopy "%OutCD%" "%DRIVE%:\MC\DATA\mcedit" /E
echo Done!
set /p yesnol="Would you like to launch MCEdit now? [y/n]: "
if "%yesnol%"="y" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
)
)
Right after you type the drive it closes, any help?
The issue that causes the script to exit early is a syntax error in the last if-statement of your else branch. If you run the script from the command line (not by double clicking on it) you'll get the error message:
="y" was unexpected at this time.
Indeed your last if-statement is:
if "%yesnol%"="y" (
but the cmd parser expects a double "=" sign (==) for comparisons, so you should have:
if "%yesnol%"=="y" (
The reason why it will see it even if it won't take the else branch is because an if-block (actually, all block of codes delimited by ( ... )) is parsed as if it was one single command written on one line (with each "subcommand" in your block separated with &&). As the parser will process that whole "line" in one go, it will detect whatever syntax error present inside the whole block.
Besides that syntax error, there are some little mistakes you've made in your script. The first one is that it is actually not good to declare labels inside a block of code. You've declared the :yes label inside the if-block in the else branch. A goto inside an if-block will "destroy" the if-context. You maybe won't notice in your code, but consider this example:
#echo off
set var=Let's brake the if statement
IF DEFINED var (
echo The variable var exists
IF "%var%"=="Let's brake the if statement" goto :expected
echo The variable var is not what I would expect though.
echo You have to change it to something else...
goto :outside
:expected
echo I'm happy with its value. It is at least something I would expect
echo This is the end of the if-branch
) ELSE (
echo Strange ... the variable var is not defined.
echo Indeed: var="%var%"
)
:outside
rem just to get outside
You would expect an output like
The variable var exists
I'm happy with its value. It is at least something I would expect
This is the end of the if-branch
but the output will be
The variable var exists
I'm happy with its value. It is at least something I would expect
This is the end of the if-branch
Strange ... the variable var is not defined.
Indeed: var="Let's brake the if statement"
The goto destroyed the if-context. As said earlier, the cmd parser will parse the whole if-block as one command. See it this way: you're asking the parser to abandon the command it was processing (the whole if block with the condition it just checked) and jump to somewhere else. That somewhere else is after the if condition, so it won't even evaluate that one again. So once you're inside a block (espacially if-blocks and for-loops) don't use goto to ignore some piece of code inside that block, use if-statements and put the code to ignore inside the if-block. A goto to jump outside a block of code is not a problem but from the moment the label is inside an if block for example, it can result in unexpected results.
The second mistake is about the variables YesNoIn and yesnol. As I said the whole if block is parsed in one go as one single command. It is not possible to give a variable a new value, and read that new value in the same command with the simple variable expansion %YesNoIn% or %yesnol%. A solution to this problem is delayed expansion (the link also has an example). As I was writing this answer, I saw #Josefz already posted an answer with delayed expansion so I won't repeat it here. But what I would recommend is that you take a look at the choice command. Using the choice command, you won't need delayed expansion. It just sets the errorlevel and there exists a way to check the errorlevel without worrying about delayed expansion: IF ERRORLEVEL n will check if the errorlevel is greater or equal to n. On top of that, choice automatically verifies if the user entered a correct value!
Your script with choice instead of set /p will look like this:
echo What drive would you like to launch MCEdit on?
set /p DRIVE="Drive: "
if exist "%DRIVE%:\MC\DATA\mcedit\mcedit.exe" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
) else (
echo You do not have MCEdit installed on this drive, would you like to install it?
choice /c YN
REM Y ==> errorlevel = 1 ; N ==> errorlevel = 2
if ERRORLEVEL 2 goto:menu
echo Choose the "mcedit.exe" file from a current installation on your pc.
pause
call "%DRIVE%\MC\DATA\FileViewer.bat"
xcopy "%OutCD%" "%DRIVE%:\MC\DATA\mcedit" /E
echo Done!
choice /c YN /m "Would you like to launch MCEdit now? "
if NOT ERRORLEVEL 2 (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
)
)
PS: No Enter key needs to be pressed when choice is used.
EDIT: A third mistake is the choice of the variable APPDATA, it is already used by your windows OS in a user's context as you can see here.
You have simple typo in comparison operator in the latter if. Correct to if "%yesnol%"=="y"
I'm not sure whether changing APPDATA variable is a good idea.
Missing :menu label in your code.
Important: read and apply http://ss64.com/nt/delayedexpansion.html.
Never use :label nor :: label-like comment inside a command block enclosed in () parentheses
SETLOCAL EnableExtensions EnableDelayedExpansion
:::
:menu
:::
echo What drive would you like to launch MCEdit on?
set /p DRIVE="Drive: "
if exist "%DRIVE%:\MC\DATA\mcedit\mcedit.exe" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
) else (
echo You do not have MCEdit installed on this drive, would you like to install it?
set /p YesNoIn="[y/n]: "
if /I "!YesNoIn!"=="y" (
echo Choose the "mcedit.exe" file from a current installation on your pc.
pause
call "%DRIVE%\MC\DATA\FileViewer.bat"
xcopy "%OutCD%" "%DRIVE%:\MC\DATA\mcedit" /E
echo Done^!
set /p yesnol="Would you like to launch MCEdit now? [y/n]: "
if /I "!yesnol!"="y" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
)
) else goto:menu
)
I worked with 4DOS a lot decades ago, and bash more recently, but don't have experience with plain Windows batch. I'm trying to make something to conveniently kill the firefox.exe processes that sometimes misfire and never show Firefox but persist and eat my resources.
Trying to make a query-to-user that defaults to kill the processes.
The first problem is the "if %REPLy%==SOMETHING (goto SOMEWHERE)" statements.
The explicit ones work fine but I want to kill the firefoxes if none of them are true. I thought I'd just put the code after the "if...goto"s but that didn't work. So I tried an additional if based on the variable REPLy equaling nothing. That didn't work. So I thought maybe a variable equaling nothing ("") might not be the same as being undeclared and maybe the reply stuff was simply removing the variable rather than giving the value "" and added an if for that. That didn't work either. So I thought maybe I had to put the kill code under a label and send execution there with a goto like the I did in the statements that work, but that doesn't work either. If I enter SOMETHING other than the explicit variations of NO or no, full or truncated, it works, kinda. The taskkill command reports success but it still fails in reality. But I'll work on that bridge when I get there. The immediate problem is how to get NO entry (in other words, just hit the enter key) to goto the kill code just like a non-no string does. What am I doing wrong here?
#echo off
REM All this stuff with the path is because I can't reboot this system right now (long story) and I can not seem to make the amended path stick. So for now, I set it each time. I presume I just need to reboot to make the path setting I changed under computer properties, etc, stick.
echo "This is the path:"
path
PATH=%PATH%;C:\Program Files\GnuWin32\bin
echo "This is the path now:"
path
echo "All these path setting and testing commands and remarks can be cleaned up after I figure out if the new path becomes permanent after reboot."
REM Here ends the stuff I expect to delete after I can reboot.
REM Here begins the part I do not expect to change and that works fine.
tasklist | findstr /B firefox.exe | wc -l > kill_firefox.bat_var.tmp
set /p NUMBER_OF_PROCESSEs=<kill_firefox.bat_var.tmp
del kill_firefox.bat_var.tmp
IF NOT DEFINED NUMBER_OF_PROCESSEs (goto ERROR - NUMBER_OF_PROCESSEs not set)
if %NUMBER_OF_PROCESSEs%==0 (goto NO_PROCESSES)
REM Since the contrary conditions lead to gotos, if processing gets to here, there are 1 or more firefox.exe processes.
echo The number of firefox.exe processes running is:
echo .
echo %NUMBER_OF_PROCESSEs%
echo .
tasklist | findstr /B firefox.exe
set /p REPLy= "Kill these? Y/n"
echo "REPLy is %REPLy%"
pause
if %REPLy%==n (goto USER DECLINED TO KILL)
if %REPLy%==N (goto USER DECLINED TO KILL)
if %REPLy%==no (goto USER DECLINED TO KILL)
if %REPLy%==NO (goto USER DECLINED TO KILL)
REM Here is where the problems start. By my reasoning, I shouldn't need any if/then here, nor even a goto, just the code that is now in the part labeled KILL. The ifs and the goto and putting the code in the labeled section are the result of many attempts to get that code to run with various constructions.
if [%REPLy%] == [] goto KILL
IF NOT DEFINED REPLy (goto KILL)
goto KILL
:NO_PROCESSES
echo There are no firefox.exe processes running.
pause
exit
:ERROR - NUMBER_OF_PROCESSEs not set
echo Logic error - The variable is not defined. This script must be repaired.
pause
exit
:USER DECLINED TO KILL
echo User declined to kill processes.
pause
exit
:KILL
REM I am not sure if ANY of this is running because the pause command is not working and the terminal disappears to fast to see. What am I doing wrong here?
echo killing . . .
taskkill /IM firefox.exe
pause
exit
Added by edit:
OK, I musta confused my smart pills with my dumb pills. Here is how I fixed the part that I was stuck on:
I changed
set /p REPLy= "Kill these? Y/n"
to
set /p REPLy= "Kill these? Y/n" || set REPLy=Y
and that did the trick. I see why that works, but I don't quite see why the ways I tried before don't. Apparently Batch treats variables set (unset? cleared? nulled?) by "set /p somemessage" with just a plain enter key as a response in some way I don't understand. But anyway, I don't have to understand it, just accept it. The construction with the "||" above works. Anyway this was the part of the problem I asked about and it's solved. If I can't get the rest of it working I'll post again after cleaning this batch file up a bit.
use setx to set a permanent variable (see setx/?, the syntax is different from set).
set /p leaves the variable unchanged, if input is empty. So you can predefine a variable:
set "REPLy=Y"
set /p "REPLy=Kill these? Y/n"
echo %REPLy%
echo first letter of REPLy is %REPLy:~0,1%
but instead of set /p, I would use choice.
There is no "empty" variable. If it has no value, the variable is not defined.
if has a /i switch to ignore capitalization.
To get the number of processes I would use (no need for an external utility):
for /f %%i in ('tasklist ^| find /c "firefox.exe"') do set NUMBER_OF_PROCESSEs=%%i
and as SomethingDark already mentioned:
run the script from the command prompt instead of double clicking it (and use exit /b instead of exit)
don't use spaces in labels.
ECHO OFF
setlocal enabledelayedexpansion
CD C:\Work\
FOR /D /r %%G in ("t*") DO (
START C:\prg.exe %%G\input_1 %%G\input_2 %%G\cpp_output
FOR /D /r %%H in (%%G\cpp_output\*.txt) DO (
FOR /F %%i in ("%%H") DO (
#set FN=%%~nxi DO (
START C:\Work\compareResults.m %%G\matlab_output\FN.txt %%G\cpp_output\FN.txt
)
)
)
)
PAUSE
I have been trying to get this .bat code to run but it doesn't work - a window opens and closes quickly - i.e prg is not run. However, when I run:
ECHO OFF
setlocal enabledelayedexpansion
CD C:\Work\
FOR /D /r %%G in ("t*") DO (
START C:\prg.exe %%G\input_1 %%G\input_2 %%G\cpp_output
)
PAUSE
prg is run. I guess there seems to be a problem with the extra commands but I just don't know what the problem is.
According to http://ss64.com/nt/for.html one can have a few commands in a single for and they can be in different lines. However that didn't work so I tried using & to have multiple commands and I've also tried using the caret (^) for breaking lines. None of these seem to work and I can't even really debug anything since the batch window disappears too fast. Any ideas for what I am doing wrong? Or even how to debug the .bat program?
...
#set FN=%%~nxi DO(
START C:\Work\compareResults.m %%G\matlab_output\FN.txt %%G\cpp_output\FN.txt))))
This won't work. This is bad syntax. DO can't stand behind a SET command. In your case DO goes only with FOR:
FOR something DO (
codeline 1
codeline 2
...
codeline n
)
If you want several command lines to be executed within a FOR block you just have to write them between the brackets (), each command in a new line.
However, there is also a way to combine several commands in only one line (don't use it here): command1&command2&...comandn will execute commands 1 to n one after the other. They will be executed even if one of the commands throws an error. If you use && instead (command1&&command2&&...comandn), each command will only be executed if the previous command was proceeded without errors.
I'm really new in this forum so I hope to respect all your rules, if not please forgive me!
I've just started studying something about batch files and I'm trying to execute a simple program from batch passing a parameter (the last aim is to submit a SAS program passing a date parameter).
Is it possible to activate a sort of list where I can choose some between pre-defined parameters?
--> This is the real aim of my work
I'm trying to "play" with this code:
#echo off
title Setting up execution period
echo Insert your date in the format GGMMMAAAA (es: '31DEC2003'D).
SET /p data_par=Insert the date to filter datas:
SET first_byte=%data_par:~1,1%
if "%first_byte%"=="" (
GOTO tag1
) else (
GOTO tag2
)
:tag1
msg * Missing value
:tag2
msg * Well done!
pause
I've tried in a lot of ways but it looks like the IF statement is not executed, I don't know where am I wrong.
Another question: why the prompt closes after i press "Enter" (afte the set/p command is executed)?
--> this has been resolved putting the "pause" command at the end of the script.
Thank you all for the attention,
Best regards!
Squotty
Put a pause at the end of your code to see the errormessages.
correct syntax for if when using else is:
if "a"=="b" (dosomething) else (dosemethingelse)
You can write it in several lines, but there are rules, where to set the paratheses:
if "a"=="b" (
echo this is code for something
rem more lines possible
) else (
echo this is code for something else
rem more lines possible
)
The first ( has to be on the same line than if.
) else ( have to be on one line.
If you press just enter with set /p, the variable remains unchanged (propably empty), so your code will go on with the code and hits the line else. Here it will tell you "else is not recognized as a command..."
at your tagx you should tell batch, where to stop execution. Use goto :eof to stop execution or goto somewhere to continue somewhere else. If you don't, it will just continue with the next lines.
Example:
:tag1
msg * Missing value
goto :eof
:tag2
msg * Well done!
goto :continue
pause
:continue
REM go on with the program...
(note: the pause will never be reached. I let it there to show you, how things work)
EDIT instead of just checking for some input you can check for the correct format:
echo %data_par%|findstr /r "[0-3][0-9][A-Z][A-Z][A-Z][1-2]0[0-9][0-9]">nul && (
echo correct format
goto continue
) || (
echo wrong format
goto startover
)
It's not bullet proof (eg. 38ABC2019 would be considered "correct"), but at least it checks for the correct format (e.g. 15.12.2019 or 12/15/2014 would be "not correct")
#ECHO OFF
SETLOCAL
SET "item1=date1"
SET "item2=date2"
SET "item3=date3"
SET "item4=date4"
FOR /l %%a IN (1,1,4) DO CALL ECHO(%%a. %%item%%a%%
ECHO(U. User-specified
choice /c 1234u
CALL SET selection=%%item%errorlevel%%%
IF NOT DEFINED selection (
SET /p selection="Your date-selection ? "
)
IF NOT DEFINED selection ECHO No selection made&GOTO :EOF
ECHO selection is %selection%
GOTO :EOF
This code may be of assistance.
It's normal to develop batch code using a batch window. Simply set up a shortcut to command prompt (Start>Programs>Accessories) which would allow you to run the script over and over and retain the results on-screen without using 'pause'. Editing can be accomplished by using notepad batchfilename.bat from the prompt (if you are using notepad for an editor - if using something better, then substitute that program's name). You can exit from the batch window by executing an exit command.
You can also get help on batch commands by using commandname /? - it's often cryptic and there are plenty of quirks. Extensive help available here on SO.