Why am I getting a syntax error with this CMD script? - windows

I'm trying to write a simple CMD script to automate the build of my repo. Here is the script in its entirety:
#echo off
setlocal
goto main
:: Functions
:buildSubdir
pushd %1
for /f %%projectFile in ('dir /b /s project.json') do (
dnu restore "%%projectFile"
dnu build "%%projectFile"
dnu pack "%%projectFile"
)
popd
goto :EOF
:main
:: Check for dnu
where dnu > NUL 2>&1
if %ERRORLEVEL% NEQ 0 (
echo dnu wasn't found in your PATH! 1>&2
echo See http://docs.asp.net/en/latest/getting-started/installing-on-windows.html for instructions on installing the DNX toolchain on your PC. 1>&2
exit /b %ERRORLEVEL%
)
:: Do the actual work
cd %~dp0
call :buildSubdir src
call :buildSubdir test
Basically, what it does is try to find all the files named project.json in a few select directories (src and test), and execute dnu restore, dnu build, and dnu pack on them.
For some reason, I seem to be getting a syntax error on the line where I enter a for /f loop, saying something about %projectFile not being recognized. Here's a gist of the full output from my terminal when I remove the #echo off statement and re-run the script.
Can anyone tell me why this is happening, and what I can do to fix it? Thanks.
edit: Just changed it to this:
for /f %%p in ('dir /b /s project.json') do (
set projectFile=%%p
dnu restore "%projectFile%"
dnu build "%projectFile%"
dnu pack "%projectFile%"
)
Still doesn't seem to be working, although the error messages are different now. Here's a gist of the new output. (Note how %projectFile% is set to the empty string.)

for /f "delims=" %%p in ('dir /b /s /a-d project.json') do (
dnu restore "%%p"
dnu build "%%p"
dnu pack "%%p"
)
The directory name is assigned to %%p so since that's all you're using, you have no need to assign it further.
The delims= ensures that the entire line is assigned to %%p - otherwise, %%p will be assigned the first token using the default delimiter set, the practical outcome of which is to truncate the name at the first space.
See
for /?
from the prompt for docco.
The /a-d removes any directorynames from the dir output (just in case there is a directory name matching the mask supplied - small possibility though that might be)
If you'd wanted to manipulate the name in %%p rather than just use it as-is, you'd have needed to use either delayedexpansion or called another routine to do the manipulation. The basis of this characteristic is thedelayedexpansion trap- batch will substitute the *parse-time* value of any%var%it finds in a code-block (parenthesised series of statements) for%var% before executing, so since %projectFile% was undefined at the start of the for loop, batch will replace it with nothing which is its value at that time.
For documentation, see many, many articles here related to delayedexpansion or read the scant documentation in
set /?

With the For construct, you use letters as variables: a-z and A-Z (it is important to note that the letters are case-sensitive) in the For construct. There are a number of ways you can use FOR and I would start here - http://ss64.com/nt/for_f.html
You can also FOR /? to get help as needed.
I think you are looking for this:
#echo off
setlocal
goto main
:: Functions
:buildSubdir
pushd %1
for /f %%p in ('dir /b /s project.json') do (
dnu restore "%%p"
dnu build "%%p"
dnu pack "%%p"
)
popd
goto :EOF
:main
:: Check for dnu
where dnu > NUL 2>&1
if %ERRORLEVEL% NEQ 0 (
echo dnu wasn't found in your PATH! 1>&2
echo See http://docs.asp.net/en/latest/getting-started/installing-on-windows.html for instructions on installing the DNX toolchain on your PC. 1>&2
exit /b %ERRORLEVEL%
)
:: Do the actual work
cd %~dp0
call :buildSubdir src
call :buildSubdir test

Related

Return value of a function to a function in batch script

This could possibly a duplicate of several questions out there in the SO, dostips and ss64. The research I've done point me to look out for _scope_ in functions. but my solutions is simple and straight forward but still problem exists
what is really behind the SETLOCAL and ENDLOCAL
How contextworkd in batch scripts ex (goto) 2>nul
Why (goto) wrapped in braces (explained in dostips)
Here is the code i've written so far to copy file from one place to another.
My Goals were:
Research the scope and contextof batch script
code reuse
#echo off
setlocal EnableDelayedExpansion
goto :main
:main
setlocal
set _app=test
set _base=C:/wamp64/www
set _destination=!_base!/test
set _source=%~dp0%/build
set /A _flag=0
echo *********************************************
echo Deploying in %~1 mode: %TIME%
echo Deploy path: !^_destination!
echo *********************************************
call :check !_base!, !_app!, _flag
if !_flag!==0 (
call :create !_base!, !_app!
)
xcopy "!_source!" "!_destination!" /D /C
exit /b 0
endlocal
:setbase
echo ::::: setting up base :::::
chdir /D C:
rem the base dir for app to exists=> %1
chdir %~1
exit /b 0
:check
echo ::::: checking for local web server destination :::::
call :setbase %~1
set %~3= dir /p|find /C "%~2"
exit /b 0
:create
echo ::::: creating the app folders :::::
rem setting the base to create app folder %1
call :setbase %~1
mkdir %~2
exit /b 0
endlocal
This is the output i get when i initiate deploy.bat
*********************************************
Deploying in production mode: 19:28:53.13
Deploy path: C:/wamp64/www/test
*********************************************
::::: checking for local web server destination :::::
::::: setting up base :::::
0
::::: creating the app folders :::::
::::: setting up base :::::
A subdirectory or file test already exists.
seems like the If !_flag!==0 which is checking whether the app folder exist in the server root and is not working at all. When i learnt the way to pass parameters to other functions; i thought it as passing a pointer like reference but it looks like it is deeply tied to scope.
So what's going on here in the code.
Checking for File/Dir Existence
The code you're employing to verify that a directory (?) exists is
set %~3= dir /p|find /C "%~2"
I don't think this is doing what you intend.
SET /P is often used like this in order to pipe the output of a command into an environment variable. (Though, you are giving /P to dir, which paginates the output—probably not what you intended.) The command you have won't do that, though. I'd guess that what this is accomplishing is setting the var to (literally) " dir /p" and then piping that through find-count. That results of find.exe /C never make it back into the envvar though. The output line "0" is the result of piping (nothing) through find.exe /C.
I propose a simpler test for existence:
IF EXIST "%~2" (SET /A %~3=1) ELSE (SET /A %~3=0)
This test worked on my Win10 machine.
I have seen some variance in how IF EXIST works. If you didn't want to use that, you could do it with a FOR loop.
FOR /F "tokens=*" %%e IN ('DIR /B') DO IF "%%~e"=="%~2" SET /A %~3=1
If you want to use the pipe in the FOR command, you'll have to escape it.
FOR /F "tokens=*" %%e IN ('DIR /B ^| find /C "%~2"') DO SET /A %~3=%%~e
All three of these methods worked for me.
SETLOCAL
I do not think there exists an official reference guide for cmd syntax other than the built-in help (SETLOCAL /?)
My experience with it is that it pushes all the envvars and working directories onto a "stack" and a corresponding ENDLOCAL or EXIT (though in scripts, you almost always want to use EXIT /B) will pop the environment off that "stack." In effect, it means that envvar/CWD changes you make in a SETLOCAL will only be temporary.

Why is displayed always the same file name on running a FOR loop which should find *.exe files and output their names?

I want that my batch script only shows the filename without any path or extension in a specific directory of *.exe files. My code so far is this:
for /R "%cd%" %%e in (*.exe) do (
set "EXENAME=%%~ne"
echo "%EXENAME%"
)
But this code does not work as expected. Let's assume, I have two files in that directory: tomcat7.exe and tomcat7w.exe. But when processing the script, I get as an answer this:
"tomcat7w"
"tomcat7w"
Why is that?
You ran into the delayed expansion trap as so many batch file coding newbies as Noodles hinted.
You could see the expected result by using echo %%~ne instead of echo "%EXENAME%".
By opening a command prompt window, running in this window set /? and reading the output help you get delayed environment variable expansion explained on an IF and a FOR example.
The batch file producing the expected output:
#echo off
setlocal EnableDelayedExpansion
for /R "%cd%" %%e in (*.exe) do (
set "EXENAME=%%~ne"
echo !EXENAME!
)
endlocal
If you want to see with extensions try like this way :
#echo off
setlocal EnableDelayedExpansion
for /R "%cd%" %%e in (*.exe) do (
set "EXENAME=%%~nxe"
echo !EXENAME!
)
endlocal
pause

Recursive directory processing in a BAT file with a twist

OK, I apologize ahead of time for a) using an old, crappy technology (BAT files) and b) asking what seems to be a redundant question. I'm limited in the technology I'm allowed to use in this particular case and after looking at dozens of posts on the subject I can't find anything I can adapt to what I need.
I have a directory structure that looks like this:
A
B
C
D
etc...
XYZ
more folders
My BAT file is located outside this files system. I need to inspect it starting at level "C" and need to find the "XYZ" directory. The folders between C and XYZ can have variable names depending on the environment in which the files were created. I need to end up with a string that consists of the directory names from C through XYZ (i.e. "C\D\E\F....\XYZ") that I can put into a variable so when my BAT file is completed I can reference the variable and run another command.
I've looked at posts using FIND and FOR but I can't seem to figure out how to a) limit the string to the starting directory (for example when I combine FOR with DIR I get "A\B\C...") and how to stop when I get to "XYZ"...
Any help is greatly appreciated.
This should work in most situations:
#echo off
setlocal enableDelayedExpansion
set "root=c:\a\b\c"
set "target=xyz"
for %%R in ("%root%") do for /f "delims=" %%F in (
'dir /b /s /ad "%root%\%target%"'
) do (
set "fullPath=%%F"
set "relpath=!fullPath:%%~dpR=!"
)
echo !relpath!
It can fail if any of your paths contain ! or =. There are solutions for this, but the code is significantly more complicated.
EDIT
Actually, there is a relatively simple solution using FORFILES that should work in all situations. (Assuming your version of Windows has FORFILES)
#echo off
setlocal disableDelayedExpansion
set "root=c:\a\b\c"
set "target=xyz"
for /f "delims=" %%F in (
'forfiles /p "%root%" /m "%target%" /s /c "cmd /c if #isdir==TRUE echo #relpath"'
) do set "relpath=%%~F"
for %%R in ("%root%") do set "relpath=%%~nxR%relpath:~1%"
echo %relpath%
The only restriction is the code has to change slightly if your result contains poison characters like &. In that case you need to add quotes to the final ECHO statement, or else enable delayed expansion at the end and use echo !relpath!
For a) question:
FOR /F "TOKENS=*" %%d IN ('DIR A\B\C\XYZ /S /AD /B') DO SET variable=%%d
For a) and b) question:
FOR /D /R "A\B\C" %%d IN (*.*) DO IF /I "%%~nxd"=="XYZ" (SET variable=%%d& GOTO :EOF)
but this will exit batch script, so you need:
... your batch code
CALL :GET_XYZ
... your batch code
GOTO :EOF
:GET_XYZ
FOR /D /R "A\B\C" %%d IN (*.*) DO IF /I "%%~nxd"=="XYZ" (SET variable=%%d& GOTO :EOF)
ECHO XYZ not found!
GOTO :EOF

Check if command is internal in CMD

I have a command's name and I need to check if this command is internal. How can I do it in a batch script?
So after a lot of tweaking, and thanks to the help of #Andriy M, it finally works.
#ECHO off
CALL :isInternalCommand dir dirInternal
ECHO is dir internal: %dirInternal%
CALL :isInternalCommand find findInternal
ECHO is find internal: %findInternal%
exit /b 0
:isInternalCommand
SETLOCAL
MKDIR %TEMP%\EMPTY_DIR_FOR_TEST > NUL 2>& 1
CD /D %TEMP%\EMPTY_DIR_FOR_TEST
SET PATH=
%~1 /? > NUL 2>&1
IF ERRORLEVEL 9009 (ENDLOCAL
SET "%~2=no"
) ELSE (ENDLOCAL
SET "%~2=yes"
)
GOTO :EOF
OLD SOLUTION
You can use where. If it fails, the command is probably internal. If it succeeds, you get the executable path that proves it's not internal.
C:\Users\user>where path
INFO: Could not find files for the given pattern(s).
C:\Users\user>where find
C:\Windows\System32\find.exe
EDIT: As the comments suggest, this might not be the best solution if you're looking for portability and not just research. So here's another possible solution.
Set %PATH% to nothing so HELP can't find anything and then run HELP on the command you're trying to check.
C:\Users\user>set PATH=
C:\Users\user>path
PATH=(null)
C:\Users\user>%WINDIR%\System32\help del
Deletes one or more files.
DEL [/P] [/F] [/S] [/Q] [/A[[:]attributes]] names
[...]
C:\Users\user>%WINDIR%\System32\help find
'find' is not recognized as an internal or external command,
operable program or batch file.
This might still fail if the command doesn't have help.
EDIT 2: Never mind, this won't work either. Both cases return %ERRORLEVEL%=1.
kichik has a good answer. However, it can give a false positive if there happens to be an executable or batch script within the current directory that matches the supplied command name.
The only way I can think of to avoid that problem is to create a folder that is known to be empty within the %TEMP% directory, and then run the test from that folder.
Here is a modified version of kichik's solution that should work.
#echo off
setlocal
::Print the result to the screen
call :isInternal find
call :isInternal dir
::Save the result to a variable
call :isInternal find resultFind
call :isInternal dir resultDir
set result
exit /b
:isInternal command [rtnVar]
setlocal
set "empty=%temp%\empty%random%"
md "%empty%"
pushd "%empty%"
set path=
>nul 2>nul %1 /?
if errorlevel 9009 (set rtn=not internal) else (set rtn=internal)
popd
rd "%empty%"
(
endlocal
if "%~2" neq "" (set %~2=%rtn%) else echo %1 is %rtn%
)
exit /b 0
Here is a script that will simply list all internal commands, assuming that HELP includes a complete list of internal commands.
Update: Both FOR and IF have special parsing rules that prevent those commands from working if executed via a FOR variable or delayed expansion. I had to rewrite this script to use a CALL and execute the command via a CALL argument instead.
#echo off
setlocal enableDelayedExpansion
set "empty=%temp%\empty%random%"
md "%empty%"
pushd "%empty%"
for /f "delims= " %%A in ('help^|findstr /rc:"^[^ ][^ ]* "') do call :test %%A
popd
rd "%empty%"
exit /b
:test
setlocal
set path=
%1 /? >nul 2>nul
if not errorlevel 9009 echo %1
exit /b 0

Skip when error occurs

i have the following code in batch (cmd):
for /f "delims=" %%f in ('dir /b /s Example') do (
command
if %errorlevel%==1 (
command
SKIP
)
command
)
EDIT:
To make things more clear:
for /f... searches for a directory called 'Example' and loops to search for more directories than one.
the first command is a delete command, it deletes all files in the directory.
the command that happens when an error occurs, is a echo command which writes some info about the error to a text file.
now the hole skip thing; sometimes, the files can't be deleted because of access denied or this file is in use by.... Normally, what would happen if there weren't a skip thing, it would just stop the command and hang. So, what i want to do, is prevent this from happening. Alternatively, i want to use something like skip, so it can skip the file and continue anyways. So i think this command needs to be piped in the delete command.
I hope it's clear now.
Thanks in advance.
Like this?
for /f "delims=" %%f in ('dir /b /s Example') do (
command
if not errorlevel 1 (
command-for-success
) else (
command-for-error
)
)
Create the two command files and run delex.cmd. The files and directories that are not deleted will be logged to delex.txt. The ones that hang, will have a minimized cmd window open that gets killed after a delay by using ping (thanks to Doc Brown's suggestion).
delex2.cmd
----------
#echo off
del /q %1
if exist %1 echo %1 not deleted!>>delex.txt
exit
delex.cmd
---------
#echo off
if exist delex.txt del delex.txt
for /f "delims=" %%f in ('dir /s /b example') do start "delextaskkill" /min delex2.cmd "%%f"
ping 127.0.0.1 -n 3 -w 1000> nul
taskkill /fi "Windowtitle eq delextaskkill"> nul
Tested with:
\example
| file1
| file2
| file3
| file4
| file5
|
\---example
file1
file2
file3
file4
file5
When one uses del, and "access denied" or "this file is in use by..." occurs, %errorlevel% is 0, so testing %errolevel% is useless. Perhaps the following simple solution works in your case:
for /f "delims=" %%f in ('dir /b /s Example') do (
del %%f
if exist %%f (
echo "file not deleted"
) else (
echo "file deleted"
)
)
I believe that this is what you want
for /f "delims=" %%f in ('dir /b /s Example') do (
command
if not errorlevel 1 (
command
) else (
command
goto :eof
)
)
Perhaps this is more advanced than can be accomplished using just built-in cmd processing. Why not consider using the Windows Scripting Host (vbscript/jscript) or even PowerShell? Both will likely provide you the level of control you are requesting.
Try this batch file:
#echo off
REM For each directory named 'Example' ...
for /f "delims=" %%f in ('dir /b /s Example') do (
REM .. enter the directory and delete all the files found there.
pushd .
cd %%f
del /q *
for /f "delims=" %%z in ('dir /b') do (
REM Any files that still exist must have been inaccessable. Log an error.
echo Unable to delete file %%z > C:\logfile.txt
)
popd
)
Tested with the following directory structure:
folder\
|------Example\
| |-------file1 (write-protected)
| |-------file2
| |-------file3
|------deeper\
|-------Example\
|-------file4
|-------file5 (write-protected)
|-------file6
After running the batch file, only file1 and file5 were left. An "Access is denied" message was printed for each write-protected file encountered; if that gets annoying you re-direct the output of the batch file like script.bat > NUL to hide it.
So after all the existing answers didn't satisfy you and you absolutely need a skip within your batch file, i will make another try:
#echo off
setlocal
for /f "delims=" %%f in ('dir /b /s batch') do call :DoSomething %%f
goto end
:DoSomething
echo Here i am: %1
if %errorlevel%==1 goto :eof
echo No error occured
goto :eof
:end
endlocal
The trick is, that the for loop calls a sub function within the same file and gives the needed parameter to it. This new call runs in a new context and can only access the variables which are defined after the do call :DoSomething in the given order their.
So you have to access the variables here with %1, %2, etc. If you want to leave this context you have to make a goto :eof to jump to the end of the file (this marker is predefined in batch mode and should not occur within your file), what leaves the context and returns to the for loop.
After running through the whole loop we just jump to the :end marker, make a little clean up and are finished.
The following looks for the file extentions you want recursively under the "dirname" directory tree and executes commandstuff.bat against that file name:
for /r %i in (c:\dirname\*.ext) do
commandstuff "%i"
Commandstuff.bat looks like this:
#ECHO OFF del %1 IF (%ERRORLEVEL% ==
0) goto END
:ERROR Echo Error deleting %1
:END Echo end
This would run commandstuff.bat for you to delete the files you want. When there is an error it will simply echo the file details and continue processing the next file.

Resources