I am writing a batch script for the 6th assignment in my class and I have hit a snag when I am pretty much finished. (We usually focus on bash scripting, so I am new to batch)
The script functions as desired the first time running it; but behaves differently when running it for the second time.
Essentially, the script checks the value of an argument if it exists and runs specific code depending on the value. For example, if the argument is "1", then it checks the PATH variable for a directory and creates it if it doesn't exist, and if it does exist - nothing happens and it just continues the script.
The issue occurs when running the script for a second time after the PATH is modified. I receive the output "\Common was not expected at this time".
I had a similar issue when running the script the first time, but managed to fix it by including Quotation marks on both side of the evaluation on the IF statement, but now I am unsure of where to continue with this.
My code is as follows:
#echo on
IF "%1%" == "0" (
SET "VAR1=%path%"
echo.%VAR1%|findstr /C:"App0" >nul 2>&1
if errorlevel 1 SET "PATH=%PATH%%cd%\App0;"
if not errorlevel 1 echo Found
goto errorBypass
) ELSE IF "%1%" == "1" (
SET "VAR2=%path%"
ECHO %VAR2%
echo.%VAR2%|findstr /C:"App1" >nul 2>&1
if errorlevel 1 SET "PATH=%PATH%%cd%\App1;"
if not errorlevel 1 echo Found
goto errorBypass
) ELSE IF "%1%" == "" (
IF "%HUMBER_HOME%" == "" (
goto Error2
) ELSE (
CALL "HUMBER_HOME\bin\setEnv.bat"
goto errorBypass
)
)
echo HERE
:Error2
echo Error2
:errorBypass
call "run.bat"
Also, so I know for future reference - is there an effective way to debug by going line by line? or a command that can output the particular line where the error occurred? I find it somewhat difficult when one error can be caused by multiple issues in different places.
Referencing a batch file argument
Open a command prompt window and run call /?. The output help explains how to reference batch file arguments. %1 references the first argument as passed to the batch file. That can be for example 1 (not quoted argument string), but also "1" (quoted argument string). %~1 references first argument string with surrounding double quotes removed.
It is wrong to add one more % after the argument reference. The syntax %variable% is used to reference the string value of an environment variable. Batch file arguments are referenced only with percent sign and digit without or with a modifier between. There is no more percent sign after the digit. That is also the reason why 1, 2, 3, ... are not possible as names for environment variables.
So not good is IF "%1%" == "0" ( because of this can result on batch file called with "1" as first argument in execution of the command line:
IF ""1"" == "0" (
The much better syntax is IF "%~1" == "0" ( which results in execution of the command line:
IF "1" == "0" (
See my answer on difference between “…” and x“…” in batch for more details on how to evaluate batch file arguments.
Appending a folder path to local PATH
The environment variable PATH holds a comma-separated list of folder paths whereby the list separator is a semicolon instead of a comma.
Therefore a ; at end of PATH means there is one more folder path which is an empty folder path. It is possible to specify an empty folder path in the middle or at end of PATH, but it is bad practice to do so because of PATH should not contain empty folder paths.
For that reason the following command line in your code is not good:
if errorlevel 1 SET "PATH=%PATH%%cd%\App0;"
There is also missing ; in case of PATH does not already end with a semicolon which could be a reason for the error message on second execution of the batch file.
The better code can be seen below on completely revised batch file code.
Referencing current directory
It is possible to reference the current directory with %CD% which can differ from batch file directory referenced with %~dp0. %~dp0 references drive and path of argument 0 which is the batch file itself. The batch file path string referenced with %~dp0 always ends with a backslash. Therefore no additional backslash should be used after %~dp0 on concatenating it with a file/folder name.
The dynamic environment variable CD ends usually with no backslash at end. So in most cases %CD% must be concatenated with an additional \ with a file/folder name. But there is one exception which must be taken into account on using %CD% in a batch file: %CD% expands to a string with \ at end on current directory being the root directory of a drive, for example C:\ or D:\. So it is always necessary on using %CD% to check if the string already ends with a backslash before appending a file/folder name without or with an additional backslash.
Other recommendations
The usage of a command block starting with ( and ending with ) should be avoided on using environment variables defined/modified within a command block and referenced within the command block as this requires the usage of delayed expansion as explained by help output on running set /? in a command prompt window on an IF and a FOR example on which command blocks are used usually. The Windows command processor is designed primary for executing one command line after the other. The usage of command blocks can speed up the execution of a batch file in some cases, but in many cases it is better to avoid them.
See debugging a batch file with a short description how to debug a batch file. A single step execution is not really possible. But cmd.exe shows on which line or command block an error occurred resulting in exiting batch file execution and what is the error.
Revised batch file code
Here is the revised batch file code:
#echo off
goto Main
:AddPath
echo %PATH%;|%SystemRoot%\System32\findstr.exe /I /C:"\%~1;" >nul 2>&1
if not errorlevel 1 echo Found %~1 in PATH& goto :EOF
set "Separator=;"
if "%PATH:~-1%" == ";" set "Separator="
if "%CD:~-1%" == "\" (set "AppPath=%CD%%~1") else set "AppPath=%CD%\%~1"
set "PATH=%PATH%%Separator%%AppPath%"
set "AppPath="
set "Separator="
goto :EOF
:Main
if "%~1" == "0" call :AddPath App0 & goto errorBypass
if "%~1" == "1" call :AddPath App1 & goto errorBypass
if not "%~1" == "" goto RunApp
if "%HUMBER_HOME%" == "" goto Error2
if exist "%HUMBER_HOME%\bin\setEnv.bat" (
call "%HUMBER_HOME%\bin\setEnv.bat"
goto errorBypass
)
echo File "setEnv.bat" in subdirectory "bin" in directory
echo defined by environment variable HUMBER_HOME not found.
echo HUMBER_HOME directory: "%HUMBER_HOME%"
echo/
pause
goto :EOF
:RunApp
echo HERE
goto :EOF
:Error2
echo Error2
goto :EOF
:errorBypass
if exist "run.bat" call "run.bat"
There is defined the subroutine AddPath at top of the batch file which is a bit unusual. So the second line with goto Main results in jumping over code of the subroutine on starting the execution of the batch file.
The subroutine AddPath is called with either App0 or App1 on first argument being 0 or "0" or 1 or "1".
The first line in AddPath outputs current value of local environment variable PATH with a semicolon appended and redirects this output to FINDSTR which searches case-insensitive and literally for the first argument string passed to the subroutine after a backslash and ending with a semicolon. The additional \ and ; should avoid a false positive on any folder path in PATH containing by chance also App0 or App1 somewhere in the middle of the folder path. This small enhancement is not 100% fail safe, but should be good enough.
FINDSTR exits with 0 on searched string found in line. In this case just an information message is output and the subroutine is exited which results in batch file execution being continued on main code on which the subroutine was called before. Otherwise the passed application name must be added to local PATH.
See also:
Where does GOTO :EOF return to?
Single line with multiple commands using Windows batch file
So first the environment variable Separator is defined with ; as value. But if local PATH already ends with a backslash although it should not, the environment variable is deleted immediately. Please note that the command line comparing last character of PATH with ; can fail if PATH ends with ". So this simple version is not 100% fail safe.
Next the current directory path is concatenated with the passed application folder name without or with an additional backslash depending on current directory being root directory of a drive or a subdirectory of any directory.
Then the local PATH is extended with appending the application path according to passed argument without or with an additional semicolon before.
Finally the no longer needed environment variables Separator and AppPath are deleted before exiting the subroutine.
A main mistake in main code as posted in question are the missing percent signs around environment variable HUMBER_HOME on calling batch file setEnv.bat in subdirectory bin of the directory assigned to environment variable HUMBER_HOME. This could be another reason for the error message on second execution of the batch file.
The revised code first checks if each batch file to call really exists before calling it.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
echo /?
findstr /?
goto /?
if /?
pause /?
set /?
Related
This question is related to Test IF file exist, ELSE xcopy these two files.
Background: On occasion I choose to run aGallery-dl.bat in a given folder (just one in each of 100's of folders). It first deletes Folder.jpg then renames Folder2.jpg to Folder.jpg. This has the effect of a red X being replaced by a yellow ! when viewing the folder with parent folder selected in File Explorer. Secondly, it calls gallery-dl.exe. I use going from red X to yellow ! to let me know I've run aGallery-dl.bat at least once. If aGallery-dl.bat completes successfully, it finally deletes the Folder.jpg (currently yellow !), and now the representative contents of the folder (usually DeviantArt .jpg's) are visible. All is well.
rem #echo off
del .\Folder.jpg
ren .\Folder2.jpg Folder.jpg
FOR /F %%i IN ('cd') DO set FOLDER=%%~nxi
"C:\Program Files (x86)\gallery-dl\gallery-dl.exe" -d "U:\11Web\gallery-dl" --download-archive ".\aGDB.sqlite3" "https://www.deviantart.com/"%FOLDER%"/gallery/all"
del .\Folder.jpg
Problem: Restating, Gallery-dl.bat is in each of 100's of folders. On occasion, I run one of these from within it's local folder. Line 5, if the call to the web site is successful, gallery-dl.exe creates zzzGDB.sqlite3 within the local folder.
In the previous code, when aGallery-dl.bat completed, it would just delete the Folder.jpg. This assumes the call to the web page was successful. On rare occasion, the call to the web page will fail for any number of reasons, though at close (due to that final del .\Folder.jpg), it will still delete folder.jpg.
If zzzGDB.sqlite3 was not created/not present, I need the Folder.jpg (yellow !) to remain.
So, in the below code (line 6, now blank), I've lopped off the final del .\Folder.jpg and am trying to plug-in the provided code beginning at line 7, inserting a test for zzzGDB.sqlite. If found, del .\Folder.jpg. If not found, no action is taken against folder.jpg (it remains).
(The rem statement at the very bottom is just acting as a placeholder for my own knowledge.)
rem #echo off
del .\Folder.jpg
ren .\Folder2.jpg Folder.jpg
FOR /F %%i IN ('cd') DO set FOLDER=%%~nxi
"C:\Program Files (x86)\gallery-dl\gallery-dl.exe" -d "U:\11Web\gallery-dl" --download-archive ".\zzzGDB.sqlite3" "https://www.deviantart.com/"%FOLDER%"/gallery/all"
setlocal EnableExtensions DisableDelayedExpansion
for /D %%I in ("U:\11Web\gallery-dl\deviantart\*") do (
if exist "%%I\zzzGDB.sqlite3" (
del "%%I\Folder.jpg"
)
rem
)
endlocal
Note: Currently, the modified code goes back through every single folder within U:\11Web\gallery-dl\deviantart\*. This action should be reserved only to the local folder. I'm guessing the below is the issue.
for /D %%I in ("U:\11Web\gallery-dl\deviantart\*")
I don't know how to remove it and still implement everything after do?
do (
if exist ".\zzzGDB.sqlite3" (
del ".\Folder.jpg"
)
rem
)
I suggest to use following lines for aGallery-dl.bat.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
pushd "%~dp0" || exit /B
move /Y "Folder2.jpg" "Folder.jpg"
for %%I in (.) do set "FOLDER=%%~nxI"
"%ProgramFiles(x86)%\gallery-dl\gallery-dl.exe" -d "U:\11Web\gallery-dl" --download-archive "%~dp0zzzGDB.sqlite3" "https://www.deviantart.com/%FOLDER%/gallery/all"
if not errorlevel 1 if exist "zzzGDB.sqlite3" del "Folder.jpg"
popd
endlocal
The first two lines define the execution environment for the batch file.
The third line with command PUSHD pushes the current directory path on stack and sets the directory of the executed batch file as current directory. This works even on batch file being stored on a network resource accessed using a UNC path, except there is a network problem on execution of that command line.
The command exit /B is executed in case of an error to immediately exit processing of the batch file on batch file directory could not be set as current directory. The Windows command processor runs implicitly the command ENDLOCAL in this case.
See Single line with multiple commands using Windows batch file for an explanation of operator || which results in conditionally executing exit /B only if pushd exited with a non-zero exit code indicating an error.
The fourth command line with MOVE does not really move the data of file Folder2.jpg to file with name Folder.jpg. In real it just updates the file system in this case as done also by the two commands del .\Folder.jpg and ren .\Folder2.jpg Folder.jpg in your batch file. This is just a very little bit faster method to replace one file by another file with the advantage that Folder.jpg must not exist at all for success without displaying an error message as done by command DEL on file Folder.jpg not existing.
The FOR command line determines the name of the current folder without path and assigns it to environment variable FOLDER. This is a much faster and safer solution then using the command line:
FOR /F %%i IN ('cd') DO set FOLDER=%%~nxi
The command line above results in starting in background one more command process with %ComSpec% /c and the command line between ' appended as additional arguments. So there is executed in background with Windows installed to C:\Windows:
C:\Windows\System32\cmd.exe /c cd
The started cmd.exe executes internal command cd which outputs the full qualified folder name of current directory to handle STDOUT of background command process. This output is captured by cmd.exe processing the batch file and is processed by for after started cmd.exe closed itself after finishing execution of command CD.
The command FOR would split up the folder path into substrings (tokens) using normal space and horizontal tab as string delimiters, would look next if the first space/tab delimited string starts with a semicolon in which case the captured line would be ignored for further processing, and would assign otherwise just the first space/tab delimited string to loop variable i. So if the full qualified folder name of current directory would contain a space, this command line would fail to determine the folder name of current directory without path.
There is the dynamic variable CD of which value can be referenced with %CD%. The value is the full qualified folder name of current directory not ending with a backslash, except the root directory of a drive is the current directory. That would be identical to %~dp0 for batch file aGallery-dl.bat with the difference that %~dp0 expands to full qualified name of batch file folder always with a backslash at end.
However, neither dynamic variable CD nor %~dp0 nor execution of command CD in a separate command process in background are really useful to get name of current directory (= batch file directory) without path. The best method is using:
for %%I in (.) do set "FOLDER=%%~nxI"
This simple FOR does nothing else than getting name of current folder without path with a very fast executed file system query and assigning it to environment variable FOLDER.
Note: for %%I in ("%~dp0.") do set "FOLDER=%%~nxI" could be also used to get folder name without path of folder containing currently executed batch file if the current directory would not be the batch file directory.
The sixth command line executes gallery-dl.exe with various parameters (arguments).
Please note that "https://www.deviantart.com/"%FOLDER%"/gallery/all" is in real an invalid argument string. It is not valid to have " inside a URL. The character " is never valid inside an argument string which references a file or folder. This syntax error is detected and automatically fixed which is the reason why the command line in your batch file works at all. The correct argument string is "https://www.deviantart.com/%FOLDER%/gallery/all" with one " at beginning and one " at end and no double quote inside the argument string enclosed in double quotes.
There is standard for console applications to exit with value 0 on success and a greater value like 1 on an error. I don't know if this is true also for gallery-dl.exe, but I assume that with the command line:
if not errorlevel 1 if exist "zzzGDB.sqlite3" del "Folder.jpg"
if not errorlevel 1 checks if exit code of gallery-dl.exe assigned to dynamic variable errorlevel is NOT greater or equal 1 which means less than 1 which means equal 0 (on executable never exiting with a negative value which is not recommended to do by a program on any operating system) which means no error occurred during execution of gallery-dl.exe. Please read the documentation of gallery-dl.exe regarding to exit codes of this program or find it out with several executions in a command prompt window and using echo Exit code is: %errorlevel% after each execution of gallery-dl.exe with success or an error (like a wrong URL).
That condition should be already enough. But there is used one more condition to check for existence of the file zzzGDB.sqlite3 in current directory only before deleting the file Folder.jpg in the current directory which is the batch file directory.
Please read issue 2 in this answer for the reason writing in batch file
if not errorlevel 1 if exist "zzzGDB.sqlite3" del "Folder.jpg"
instead of
if not errorlevel 1 (
if exist "zzzGDB.sqlite3" (
del "Folder.jpg"
)
)
The code above works also, but requires more CPU cycles for being processed by Windows command processor than the single line with two IF commands and one DEL command on one command line.
The last but one line pops the full qualified folder name of initial current directory from stack and sets it again as current directory and the last line restores initial execution environment on starting the batch file.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?... explains %~dp0 ... drive and path of argument 0 ... full batch file path.
del /?
echo /?
endlocal /?
exit /?
for /?
move /?
popd /?
pushd /?
set /?
setlocal /?
In my code I'm searching for only files in folder and all subfolders. When the name of subfolder has one blank (space) between the words this subfolder is recognized as a file, too. This is not correct behavior. The parameter /a-d doesn't help here.
#echo on
Setlocal EnableDelayedExpansion
set "input=C:\Users\NekhayenkoO\test\"**
set "output=C:\Users\NekhayenkoO\outputxml\"**
set string1=Well-Formed and valid
set string2=Well-Formed, but not valid
set string3=Not well-formed
set /a loop=0
set /a loop1=0
set /a loop2=0
set /a loop3=0
for /f %%a in ('dir /b /a-d /s %input%') do (
CALL jhove -m PDF-hul -h xml -o %output%\%%~na.xml %%a
if !ERRORLEVEL! EQU 0 (echo Errorlevel equals !errorlevel! )
if !ERRORLEVEL! GEQ 1 (Errorlevel equals !errorlevel! )
set /a loop3+=1
)
The output of the script on running in directory C:\Users\NekhayenkoO\jhove-beta:
Setlocal EnableDelayedExpansion
set "input=C:\Users\NekhayenkoO\test\"**
set "output=C:\Users\NekhayenkoO\outputxml\"**
set string1=Well-Formed and valid
set string2=Well-Formed, but not valid
set string3=Not well-formed
set /a loop=0
set /a loop1=0
set /a loop2=0
set /a loop3=0
for /F %a in ('dir /b /a-d /s "C:\Users\NekhayenkoO\test\"') do (
echo Verarbeite %~na
CALL jhove -m PDF-hul -h xml -o "C:\Users\NekhayenkoO\outputxml\\%~na.xml" "%a"
if !ERRORLEVEL! EQU 0 (echo Errorlevel equals !errorlevel! )
if !ERRORLEVEL! GEQ 1 (Errorlevel equals !errorlevel! )
set /a loop3+=1
)
(
echo Verarbeite 757419577
CALL jhove -m PDF-hul -h xml -o "C:\Users\NekhayenkoO\outputxml\\757419577.xml" "C:\Users\NekhayenkoO\test\757419577.pdf"
if !ERRORLEVEL! EQU 0 (echo Errorlevel equals !errorlevel! )
if !ERRORLEVEL! GEQ 1 (Errorlevel equals !errorlevel! )
set /a loop3+=1
)
Verarbeite 757419577
Errorlevel equals 0
Verarbeite GBV58575165X
Errorlevel equals 0
Verarbeite GBV85882115X
java.lang.ClassCastException: edu.harvard.hul.ois.jhove.module.pdf.PdfSimpleObject cannot be cast to edu.harvard.hul.ois.jhove.module.pdf.PdfDictiona
at edu.harvard.hul.ois.jhove.module.PdfModule.readDocCatalogDict(PdfModule.java:1344)
at edu.harvard.hul.ois.jhove.module.PdfModule.parse(PdfModule.java:521)
at edu.harvard.hul.ois.jhove.JhoveBase.processFile(JhoveBase.java:803)
at edu.harvard.hul.ois.jhove.JhoveBase.process(JhoveBase.java:588)
at edu.harvard.hul.ois.jhove.JhoveBase.dispatch(JhoveBase.java:455)
at Jhove.main(Jhove.java:292)
Errorlevel equals 0
Verarbeite GBV858852357
Errorlevel equals 0
Verarbeite nicht_valide_PDF
Errorlevel equals 0
Verarbeite not_Wellformed_intern
Errorlevel equals 0
Verarbeite pp1788_text
Errorlevel equals 0
Verarbeite Rosetta_Testdatei
Errorlevel equals 0
Verarbeite script
Errorlevel equals 0
Verarbeite java
Errorlevel equals 0
Verarbeite java
Errorlevel equals 0
Verarbeite java
Errorlevel equals 0
Verarbeite java
Errorlevel equals 0
Verarbeite GBV58525785X
Errorlevel equals 0
Verarbeite GBV58574517X
Errorlevel equals 0
Drücken Sie eine beliebige Taste . . .
What is jhove?
Oleg Nekhayenko, you have asked several jhove related questions in the last days, but you have always forgotten to explain what jhove is which is important to know for all of your questions.
Therefore I searched in world wide web for jhove, found very quickly the homepage
JHOVE | JSTOR/Harvard Object Validation Environment, read quickly its documentation and command-line interface description and finally downloaded also jhove-1_11.zip from SourceForge project page of JHOVE.
All this was done by me to find out that jhove is a Java application which is executed on Linux and perhaps also on Mac using the shell script jhove and on Windows the batch file jhove.bat for making it easier to use by users.
You could have saved yourself and all readers of your questions a lot of time if you would have written jhove.bat instead of just jhove in your code snippets or at least mentioned anywhere that jhove is a batch file.
Assigning a value/string to an environment variable
I suggest to read first the answer on
Why is no string output with 'echo %var%' after using 'set var = text' on command line?
and next look on these two lines:
set "input=C:\Users\NekhayenkoO\test\"**
set "output=C:\Users\NekhayenkoO\outputxml\"**
I don't know why two asterisks are at end of those 2 command lines. But that does not really matter as both asterisk are ignored on assigning the two paths to the two environment variables.
This can be seen on posted output of the batch file as there is no asterisk output on the lines:
for /F %a in ('dir /b /a-d /s "C:\Users\NekhayenkoO\test\"') do (
CALL jhove -m PDF-hul -h xml -o "C:\Users\NekhayenkoO\outputxml\\757419577.xml" "C:\Users\NekhayenkoO\test\757419577.pdf"
There is no asterisk anywhere. So the environment variables input and output are obviously defined without the asterisks at end which is even good here.
Enclosing directory and file names in double quotes
The help output on running cmd /? in a command prompt window explains in last paragraph on last help page on which characters in a directory or file name double quotes must be used around complete directory/file name.
The space character is the string delimiting character on command line and therefore a directory or file name with a space must be always enclosed in double quotes.
Predefined environment variables on Windows
Opening a command prompt window and running set results in output of all environment variables defined for the the current user account including PATH and PATHEXT as also USERNAME and USERPROFILE.
The Wikipedia article about Windows Environment Variables explains the environment variables predefined by Windows. It is advisable to make use of them in batch files.
Execution of applications and scripts on Windows
If in a command prompt window or in a batch file just the file name of an application or script without file extension and without path is specified, the Windows command interpreter is searching first in current directory and next in all directories of environment variable PATH for a file with specified name having a file extension listed in environment variable PATHEXT. In this case Windows command interpreter is searching for jhove.*.
The values of the environment variables PATH and PATHEXT can be seen on opening a command prompt window and running in this window set path which results in output of all environment variables starting with the case-insensitive interpreted string PATH with their current values.
Next to know is that when Windows command interpreter searches for jhove.*, the NTFS file system returns the file names matching this search pattern sorted alphabetically. So if current directory or any of the directories listed in PATH have for example jhove.bat and jhove.exe, the NTFS file system returns first jhove.bat. This batch file is used by Windows command interpreter as file extension BAT is listed by default in PATHEXT.
But if the file system of the drive with jhove.* files is FAT, FAT32 or ExFat, the file system returns the file names matching the search pattern in order as stored in the file allocation table and therefore unsorted. So in case of a directory contains jhove.bat and jhove.exe on a drive with any FAT file system, it is unpredictable which file is executed by Windows command interpreter on specifying just jhove in a batch file.
For that reason it is always advisable to specify the application or script with file name and at least also with the file extension. And if possible the entire path to the application to run or the script to call should be also specified.
The Windows command interpreter does not need to search around by specifying the name of an application or script file with file extension and with complete path.
See also answer on Where is "START" searching for executables?
Calling a batch file versus running an application
A batch file is a script (text file) interpreted by Windows command interpreter line by line whereby a command block starting with ( and ending with matching ) is interpreted like a subroutine defined on one line.
An application is an executable (binary file) compiled with a compiler for a specific processor or processor family and therefore does not need to be interpreted anymore on execution. It contains already processor instructions (machine code).
Why the command call must be used to run another batch file from within a batch file is explained in detail by the answers on
How to call a batch file that is one level up from the current directory?
In a Windows batch file, can you chain-execute something that is not another batch file?
For that reason it is very important to know what jhove is. It is a batch file and must be therefore called with command call which answers the question How to process 2 for loops after each other in batch?
For help on command call open a command prompt window and run call /?. The output help explains also which placeholders exist to reference arguments of the batch file whereby argument 0 is the name of the batch file.
Which command lines contains jhove.bat?
On unexpected behavior on calling a batch file from another batch file it is important to know the code of the called batch file as well because the error could be in code of called batch file.
Code of jhove.bat as stored in jhove-1_11.zip without instruction comments:
#ECHO OFF
SET JHOVE_HOME=%~dp0
SET EXTRA_JARS=
REM NOTE: Nothing below this line should be edited
REM #########################################################################
SET CP=%JHOVE_HOME%\bin\JhoveApp.jar
IF "%EXTRA_JARS%"=="" GOTO FI
SET CP=%CP%:%EXTRA_JARS
:FI
REM Retrieve a copy of all command line arguments to pass to the application
SET ARGS=
:WHILE
IF %1x==x GOTO LOOP
SET ARGS=%ARGS% %1
SHIFT
GOTO WHILE
:LOOP
REM Set the CLASSPATH and invoke the Java loader
java -classpath %CP% Jhove %ARGS%
Well, this is a not good written batch code for following reasons:
The commands setlocal and endlocal are not used in batch file to control the life time of variables used by this batch file. See answer on change directory command cd ..not working in batch file after npm install for more details. npm.bat is also a not good coded batch file like jhove.bat as it turned out.
The command line SET JHOVE_HOME=%~dp0 defines the environment variable JHOVE_HOME with drive and path of storage location of jhove.bat. The path returned by %~dp0 ends always with a backslash. If jhove*.zip was extracted into a directory with 1 or more space in complete path, care must be taken wherever JHOVE_HOME is finally used to enclose the final string in double quotes.
The command line SET CP=%JHOVE_HOME%\bin\JhoveApp.jar defines the environment variable CP by concatenating path to batch file jhove.bat with a fixed path and name of the Java package. Here is already a small mistake as %~dp0 is a path always ending with a backlash concatenated with a string starting with a backslash. So there are two backslashes finally in path to the Java package file. But Windows kernel handles this error in path and therefore it does not really matter.
The environment variable CP is referenced unmodified with no EXTRA_JARS defined by the user finally on command line java -classpath %CP% Jhove %ARGS%. The error here is %CP% is specified without being enclosed in double quotes which results in unexpected behavior if jhove*.zip was extracted indeed by the user into a directory with 1 or more spaces in complete path.
A percent sign is missing at end of command line SET CP=%CP%:%EXTRA_JARS.
The writer of jhove.bat did not know obviously anything about %* which on usage of last command line instead of %ARGS% makes the WHILE loop above completely useless.
Much better for jhove.bat would be:
#echo off
setlocal EnableExtensions
set "JHOVE_HOME=%~dp0"
set "EXTRA_JARS="
REM NOTE: Nothing below this line should be edited
REM #########################################################################
set "CP=%JHOVE_HOME%bin\JhoveApp.jar"
if not "%EXTRA_JARS%"=="" set "CP=%CP%:%EXTRA_JARS%"
rem Set the CLASSPATH and invoke the Java loader
java.exe -classpath "%CP%" Jhove %*
endlocal
The executable java.exe must be found via environment variable PATH by Windows command interpreter.
Final batch code for usage
I suggest to use the following code for this task in case of jhove.bat should not be modified to above working code:
#echo off
setlocal EnableExtensions
set "InputFolder=%USERPROFILE%\test"
set "OutputFolder=%USERPROFILE%\outputxml"
echo Searching for bin\JhoveApp.jar in:
echo.
set "SearchPath=%CD%;%PATH%"
set "SearchPath=%SearchPath:)=^)%"
for /F "delims=" %%I in ('echo %SearchPath:;=^&ECHO %') do (
echo %%I
if exist "%%~I\bin\JhoveApp.jar" (
set "JHOVE_HOME=%%~I"
goto RunJHOVE
)
)
echo.
echo Error reported by %~f0:
echo.
echo Could not find bin\JhoveApp.jar in current directory and folders of PATH.
echo.
endlocal
pause
goto :EOF
:RunJHOVE
if "%JHOVE_HOME:~-1%" == "\" (
set "CP=%JHOVE_HOME%bin\JhoveApp.jar"
) else (
set "CP=%JHOVE_HOME%\bin\JhoveApp.jar"
)
echo.
echo Using %CP%
md "%OutputFolder%" 2>nul
rem for /F %%I in ('dir /A-D /B /S "%InputFolder%\*" 2^>nul') do (
rem java.exe -classpath "%CP%" Jhove -m PDF-hul -h xml -o "%OutputFolder%\%%~nI.xml" "%%I"
rem )
for /R "%InputFolder%" %%I in (*) do (
java.exe -classpath "%CP%" Jhove -m PDF-hul -h xml -o "%OutputFolder%\%%~nI.xml" "%%I"
)
endlocal
The input and output folder paths are defined without backslash at end and without asterisk using predefined environment variable USERPROFILE.
A slightly modified code written by Magoo in his answer on Find the path used by the command line when calling an executable is used to find Java package of JHOVE. The batch file prints the folders it is searching for in case of the file could not be found which results in an error message and halting batch execution until the user presses any key.
The class path variable CP is created with taking into account if folder path ends with a backslash or not. Folder paths in PATH should be defined without backslash at end, but there are always installers which add folder paths not 100% correct to PATH. However, it does not really matter if the result would be \\ anywhere within a path as Windows kernel handles this. That's the reason why if exist "%%~I\bin\JhoveApp.jar" also always works although this file existence test could be also done with two backslashes in path depending on folder path in PATH.
Next the output folder is created without checking first if the folder is already existing and without checking if folder creation was successful at all.
The batch code contains two solutions for running jhove on each file found recursively in input folder path. The first one is commented out. It would have the advantage to work also for hidden and system files. The second solution does not work for hidden and system files, but this is most likely not necessary here. The second solution is therefore the preferred one.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
pause /?
set /?
setlocal /?
And read also the Microsoft articles:
Microsoft's command-line reference
Using command redirection operators
Testing for a Specific Error Level in Batch Files
I have a project which has following steps:
Create list of jpg files in a folder (initially there is none).
Go to step 1 if the created text file is empty.
Start another program (this program needs the received jpg file as input) if the created list file in step 1 is not empty.
JPG file will be sent to this folder by another process.
I am new to using batch script and used the following code from input.
But this program is not starting another process as required in step 3 even after receiving JPG file.
What is wrong with my code?
#echo off
set "dir=E:\test"
set "file=%dir%\a.txt"
:start
dir/b *.jpg>a.txt
if "%~z1" == "" (
goto start
)
if "%~z1" == "0" (
goto start
)
if "%~z1" == "1" (
Start "" "C:\Users\vamsidhar muthireddy\Documents\Visual Studio 2010\Projects\database_test0\Debug\database_test0.exe"
)
Don't name a variable like an internal command. It is possible to name a variable dir although there is also the command DIR, but it is not advisable.
Don't name a label like an internal command. It is possible to name a label start although there is also the command START, but it is not advisable.
Why is it not advisable to name a variable or label like a command?
Well, for example if in future somebody wants to find where variable dir is used and the batch file contains also command DIR, or wants to rename label start by running a replace and batch file contains also command START, these actions become difficult as it must be analyzed in which context dir and start are used on each found occurrence.
Also syntax highlighting of batch file code is definitely not correct with commands DIR and START as the variable dir and the label start would be most likely also highlighted as commands.
The main coding mistake is %~z1 as this is replaced by file size of the file specified with its file name as first argument on calling the batch file if the batch file was called at all with a file name of an existing file. But this is not the case here. The intention here was getting size of the list file. Also if "%~z1" == "1" will be nearly never true. This condition becomes only true if the file specified as parameter has a file size of exactly 1 byte.
Here is a commented batch code which I think is more useful for the task:
#echo off
set "SourceDirectory=E:\test"
rem This loop is executed with a delay of 5 seconds between each loop run
rem until at least 1 file with extension JPG is found in the defined source
rem directory. Then the JPG file is processed and batch processing ends.
:Loop
echo Checking for a *.jpg file in %SourceDirectory% ...
if exist "%SourceDirectory%\*.jpg" goto ProcessFile
%SystemRoot%\System32\ping.exe 127.0.0.1 -n 6 >nul
goto Loop
:ProcessFile
for %%I in ("%SourceDirectory%\*.jpg") do (
echo Processing %%I ...
start "" "%USERPROFILE%\Documents\Visual Studio 2010\Projects\database_test0\Debug\database_test0.exe" "%%I"
)
rem Delete the created variable before exiting batch processing.
set "SourceDirectory="
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
for /?
goto /?
if /?
ping /?
rem /?
set /?
start /?
I am trying to make a batch file that will make a folder on the desktop from user input. Here is my code:
echo What do you want the folder to be called?
SET /p folderName=
md C:\Users\%username%\desktop\%folderName%
However, whenever I try this, It gives me an error saying:
A subdirectory or file C:\Users\Razi\desktop\ already exists.
It doesn't seem to be noticing the %folderName% at the end.
Can somebody tell me what's wrong with my code and give an alternative? Thank you!
Oviously, %folderName% appears to be empty, as you can see in the error message which includes the path argument the md command receives. So md tries to create the already existing directory Desktop.
If the error appears even when you enter a valid directory name, I am pretty sure that the code fragment is part of a block in between parentheses, in which case you need to enable and apply delayed expansion. Otherwise you read the folderName value present before the entire block is executed.
For instance, the following block is seen as a single command line by the command interpreter; %folderName% is expanded (replaced by its value) as soon as the whole line/block is parsed:
(
echo What do you want the folder to be called?
set /P folderName=
md "%USERPROFILE%\desktop\%folderName%"
)
In the following, delayed expansion is enabled (by setlocal); to actually use it, the % expansion has been changed to !; so !folderName! is expanded (read) later, as soon as it is executed:
setlocal EnableDelayedExpansion
rem ...
(
echo What do you want the folder to be called?
set /P folderName=
md "%USERPROFILE%\desktop\!folderName!"
)
rem ...
endlocal
Note that the changed value of folderName is no longer available as soon as endlocal has been executed, or the batch file terminates (where an implicit endlocal happens).
In addition to the above, I put quotation marks around the path at the md command to avoid trouble with white-spaces or special characters, and I changed C:\Users\%USERNAME% to %USERPROFILE% as recommended in Stephan's comment.
I need to verify if any files exist in a folder and if so present the user with a message.
Currently I have this:
IF EXIST C:\PLUS\ADMIN\BATCH\*.* (
start "" cmd/c "echo Files in the directory! &echo (&pause
)
Exit
I've spent hours reading the things I've dug up on variables and piping results to things but I'm a complete batch file newbie so I'm really hoping someone can just tell me what I'm doing wrong.
Currently the batch file runs just fine but it's throwing up the message on the screen regardless of whether there are files in the directory or not. Those files tend to be named 20141010.570, 20141011.571, etc. with variable file extensions based upon an ever increasing number (so it'll stretch into 4 digits once it's done with *.999)
The problem with your code is that in windows all folders contain at least two folders (. and ..) and the test if exist c:\somewhere\* will always be true.
One easy solution is to use dir command asking to only show the files, without directories, and see if it raises an error
dir /a-d "C:\PLUS\ADMIN\BATCH\*" >nul 2>nul && (
start "" cmd /c "#echo Files in the directory! &#echo(&#pause
) || (
echo there are no files
)
The /a-d will exclude folders. If there are files, errorlevel is not set and the code after && is executed. Else, if there are no files, dir command fails, errorlevel is set and the code after || is executed.
for /f %A in ('dir /b^|findstr /i /r "[0-9][0-9][0-9][0-9]*\.[0-9][0-9][0-9]*') do echo %A
or
dir /b|findstr /i /r "\<[0-9][0-9][0-9][0-9]*\.[0-9][0-9][0-9]*\>"&&Echo File Found||Echo File Not Found
The pattern is three or more numeric characters, a dot, then three or more numeric characters, ch. It must be the entire string (so a22222.222 won't match).
Type findstr /? for help. Dos's 6.22 Help files lists return codes as 0 found, 1 not found, and 2 error.
& seperates commands on a line.
&& executes this command only if previous command's errorlevel is 0.
|| (not used above) executes this command only if previous command's errorlevel is NOT 0
> output to a file
>> append output to a file
< input from a file
| output of one command into the input of another command
^ escapes any of the above, including itself, if needed to be passed to a program
" parameters with spaces must be enclosed in quotes
+ used with copy to concatinate files. E.G. copy file1+file2 newfile
, used with copy to indicate missing parameters. This updates the files modified date. E.G. copy /b file1,,
%variablename% a inbuilt or user set environmental variable
!variablename! a user set environmental variable expanded at execution time, turned with SelLocal EnableDelayedExpansion command
%<number> (%1) the nth command line parameter passed to a batch file. %0 is the batchfile's name.
%* (%*) the entire command line.
%<a letter> or %%<a letter> (%A or %%A) the variable in a for loop. Single % sign at command prompt and double % sign in a batch file.
.
--
As all the files start with 2014 then you can use this:
IF EXIST "C:\PLUS\ADMIN\BATCH\2*.*" (
echo Files are in the directory!
echo(
pause
)
Exit