Exist, Rmdir and %errorlevel% in batch Windows [duplicate] - windows

I'm writing a batch (.bat) script and I need to handle the case in which the deletion of a folder fails. I'm using %errorlevel% to catch the exit code, but in the case of the rd command it seems not to work:
C:\Users\edo\Desktop>rd testdir
Directory is not empty
C:\Users\edo\Desktop>echo %errorlevel%
0
Why? What do you suggest?

Wow, this is the 2nd case I've seen where ERRORLEVEL is not set properly! See File redirection in Windows and %errorlevel%.
The solution is the same as for detecting redirection failure. Use the || operator to take action upon failure.
rd testdir || echo The command failed!
The bizarre thing is, when you use the || operator, the ERRORLEVEL is then set properly to 145 if the folder was not empty, or 2 if the folder did not exist. So you don't even need to do anything. You could conditionally "execute" a remark, and the errorlevel will then be set properly.
rd testdir || rem
echo %errorlevel%
I thought the above gave a complete picture. But then a series of comments below demonstrated there are still potential problems when /RD /S is used.
If a file or subfolder under the parent folder is locked (at any level under parent) then RD /S /Q PARENT && echo removed || echo failed will print out an error message, but the && branch fires instead of the || branch. Very unfortunate. If the command fails because the parent folder itself is locked, then || will properly fire and set the ERRORLEVEL.
It is possible to detect failure in all cases by swapping stderr with stdout and piping the result to FINDSTR "^". If a match is found, then there must have been an error.
3>&2 2>&1 1>&3 rd /s test | findstr "^" && echo FAILED
The swap of stderr and stdout is important when /q is missing because it allows the "Are you sure (Y/N)?" prompt to be visible on stderr, separate from the error message on stdout.

rd does not set errorlevel to zero - it leaves errorlevel intact: f.e. if previous operation ends in positive errorlevel and rd finishes successfully it leaves errorlevel unchanged. Example: error levels of robocopy below 4 are warnings and not errors and can be ignored so the following code may end with error even when the directory was deleted successfully:
robocopy ...
if errorlevel 4 goto :error
rd somedir
if errorlevel 1 goto :error
Solution: ignore the error and check if the directory still exists after rd:
rd somedir
if exist somedir goto :error

Related

Trying to convert for /f "tokens=*" %%a in ... to SHELL

I am trying to convert a whole BATCH script to SHELL script with the help of this sort of converter manual.
I have almost finished, but I am struggling to convert this FOR LOOP:
for /f "tokens=*" %%a in ('%adb% shell mkdir /usr/ui/^|find /i "File exists"') do (
if not errorlevel 1 goto :cannot_patch
)
I know that for /f is
Loop command: against a set of files - conditionally perform a command against each item.
However, as I am a noob to SHELL SCRIPT (and BASH as well), my best try was:
for -f "tokens=*" a in ( '$ADB shell mkdir /usr/ui/^|find /i "File exists"' ); do
if [ $? -nq 1 ]
then
cannot_patch
fi
done
which does not work, resulting in a Syntax error: Bad for loop variable.
Any hint, link, or suggestion would be very much appreciated.
EDIT
I am trying to understand what exactly ('%adb% shell mkdir /usr/ui/^|find /i "File exists"') is doing.
I thought those were sh commands, but it turns out I was wrong and that find /i is trying to
Search(ing) for a text string in a file & display all the lines where it is found.
(https://ss64.com/nt/find.html)
| is the pipe operator and "File exists" should be the error thrown by mkdir in case the command tries to create a directory that already exists.
So I think I could probably write this easier, but still, what does the ^ symbol in /usr/ui/^ do? Is it a regex?
EDIT2
It seems indeed that #glenn_jackman is right: probably I'd better understand what the code is trying to do.
So to give a better context, here is a bit more code of the original batch:
for /f "tokens=*" %%a in ('%adb% shell mkdir /usr/ui/^|find /i "File exists"') do (
if not errorlevel 1 goto :cannot_patch
)
:cannot_patch
echo Error: Cannot create directory!
echo Patch is already installed or system files exist and might be overwritten.
choice /m "Do you want to continue"
if errorlevel 2 goto :END
goto :continue_patch
To my understanding, the code is trying to run the adb shell mkdir command and, if it fails (throwing the "File exists" error), it will ask to the user if he/she wants to continue regardless.
So in this case, I guess the real problem is trying to write a code that does the same in SH, probably without the need of a for loop.
Still, I am finding it out...
for /f "tokens=*" %%a in ('%adb% shell mkdir /usr/ui/^|find /i "File exists"') do (
if not errorlevel 1 goto :cannot_patch
)
runs the command %adb% shell mkdir /usr/ui/ then searches that output for the string File exists. if not errorlevel 1 means "if %errorlevel% is not greater than or equal to 1," which is a weird and awkward way of just saying if "%errorlevel%"=="0", which would mean that the string was found. If it's found, the script then goes to the label :cannot_patch.
In bash, the way to process the output of a command is to use $() instead of for /f, so
$(${adb} shell mkdir /usr/ui/ | grep "File exists") && cannot_patch
This assumes that the variable adb is set and you've got a function in your shell script somewhere called cannot_patch.
Note that the only difference between my answer and Paul Hodge's answer is that I'm assuming that you still need ADB to be called.
The general syntax of your loop matches https://ss64.com/nt/for.html
You should also look over https://ss64.com/nt/errorlevel.html and this SO reference that lists/explains error codes.
From the SO article, FOR /F │ 1 = No data was processed.
But the errorlevel page says
IF ERRORLEVEL 1 will return TRUE whether the errorlevel is 1 or 5 or 64
and the SO article also says
if %errorlevel% equ 5 (
echo Can not access the directory, check rights
)
Apparently ^ is a CMD escape character, used here to escape the pipe character being passed as part of a compound command that (I'm guessing...) creates a directory and checks for a failure, so I'm parsing this as an attempt to make the directory, detect if it already exists, and respond accordingly.
I think in bash this would be written this way:
mkdir /usr/ui/ || cannot_patch
Sorry, but I have got bad news for you — the batch code you want to transcript is wrong:
for /f "tokens=*" %%a in ('%adb% shell mkdir /usr/ui/^|find /i "File exists"') do (
if not errorlevel 1 goto :cannot_patch
)
This is not going to do what you probably think it does. The following steps actually happen:
It executes the command %adb% shell mkdir /usr/ui/ and pipes (|) its output into the command find /i "File exists", but all this happens in a new cmd.exe instance created by the for /F loop.
The for /F loop, well, loops over the lines of the output of the aforesaid commands after they have completed; the new cmd.exe instance is destroyed before starting to iterate.
BUT: The ErrorLevel value resulting from the %adb% …|find … command line is not available in the body of the for /F loop, because this executes in the cmd.exe instance the batch script itself runs in too. This means, that the condition if not errorlevel 1 in the loop body actually reacts on a preceding command, unintentionally.
Since, as I believe, you simply want to test whether the output of %adb% shell mkdir /usr/ui/ contains the sub-string File exists in a case-insensitive manner, you actually just need this:
rem /* Just using `echo` for demonstration purposes, to be replaced;
rem moreover, you may also omit the `&&` or the `||` part: */
%adb% shell mkdir /usr/ui/ | find /i "File exists" && (
echo ErrorLevel 0: sub-string found.
) || (
echo ErrorLevel 1: sub-string NOT found!
)
Unfortunately, I cannot tell you exactly how to translate this, but find might probably have to be substituted by grep, and the operators && and || could be replaced by checking the exit code returned in $?, unless there are similar operators…

Parse the output of a command only when it's an error, and only with native batch commands

I'm writing a batch file that is calling a command, and I want to handle any errors that might result. I have as a requirement that I use only the native CLI commands; in other words, I know I could easily accomplish this if I were using PowerShell or whatever, but I'm not.
What I want is if an error occurs, check the output, and if the error is expected, continue processing, otherwise abort. However, I'm having trouble parsing the output in a useful way.
This seems to be as close as I can get:
command | (find /i "valid" >NUL && goto :okay || goto :stop)
Here, I'm piping the output of the command to find. The trouble is, this occurs regardless of the exit status of the command - it sends both the success and failure messages. One option would be:
command 1>NUL 2>(find /i "valid" >NUL && goto :okay || goto :stop)
However, this doesn't work, because it's attempting to redirect to a file; the find command doesn't receive the input.
(For testing purposes, command is a cmd file that echos a message, and exit /b 1 for an error)
Any ideas?
Based on what you list as known
On success, the errorlevel = 0
On error, the errorlevel = 1.
On error, an error message is written to the STDERR (2) stream.
On an "EXPECTED" error, the word "valid" is output.
Try this:
command 2>&1 1>nul | ( find /i "valid" >nul && goto okay || goto stop )
NOTE: the order in which the output re-directions take place is important!
CMD parses the redirects from left to right. This is directing STDERR to STDOUT and then directing STDOUT to NUL. Think of them like pointers. STDERR is getting the address of STDOUT before we set STDOUT to NULL.
Update
On error, an error message is written to the STDOUT (1) stream.
Option A:
call command > "%Temp%\out.txt" && goto okay || ( find /i "valid" "%Temp%\out.txt" >nul && goto okay || goto stop )
Option B:
( call command && echo valid ) | find /i "valid" >nul && goto okay || goto stop

File redirection in Windows and %errorlevel%

Lets say we want to create an empty file in windows with the following command:
type nul > C:\does\not\exist\file.txt
the directory does not exist, so we get the error:
The system cannot find the path specified
If you print out the %errorlevel% the output is:
echo %errorlevel%
0
Yet the command was not successful!
I noticed, that windows does not set the %errorlevel% of the last command if you use redirection..
Is there a way around this?
You can use the following:
C:\>type nul > C:\does\not\exist\file.txt && echo ok || echo fail
The system cannot find the path specified.
fail
C:\>echo %errorlevel%
1
I always assumed the && and || operators used ERRORLEVEL, but apparently not.
Very curious that ERRORLEVEL is set after redirection error only if you use the || operator. I never would have guessed. Nor would I ever have bothered to test if not for your excellent question.
If all you want to do is set the ERRORLEVEL upon redirection failure, then of course you can simply do:
type nul > C:\does\not\exist\file.txt || rem
The command
type nul > C:\does\not\exist\file.txt
invoked with a non-existent path is terminated at redirection failure and type is not invoked at all. It therefore has no chance of setting ERRORLEVEL. The redirection, being performed by the shell, does not set ERRORLEVEL.
One solution is to pre-initalise ERRORLEVEL with a non-zero value. It will remain unchanged upon failure and will be reset to zero (by type) upon success:
#echo off
::pre-initialise ERRORLEVEL with a value of 1:
call :SETERROR 1
type NUL > NOSUCHDIR\test.txt
IF ERRORLEVEL 1 goto ERROR
echo All is well.
goto END
:ERROR
echo Error detected.
:END
goto :eof
:SETERROR
exit /b %1
The shorft form
type NUL > NOSUCHDIR\test.txt && goto OK || goto ERROR
works because it analyses exit code, which is not the same as error level:
An exit code can be detected directly with redirection operators (Success/Failure ignoring the ERRORLEVEL) this can often be more reliable than trusting the ERRORLEVEL, which may or may not have been set correctly.
Herbert Kleebauer explained this to me in the Usenet group alt.msdos.batch.
Update:
An anonymous user suggested an alternative solution based on the COPY command:
COPY NUL: C:\does\not\exist\file.txt
This command does set ERRORLEVEL, which may be analysed by the next command in the script. Very convenient, so I thank him for the proposed edit.

FindStr isn't work correct

I made a piece of batch-code, and I thought this will work. What I'm thinking that this code is doing? I have some plugins and I want to test if the deploy correct. So I get the pluginlink from the plugins.txt. Then I get the plugin from SVN with the java sentence. I deploy the plugin and get the feedback in test1.txt. Then I do a findStr in that file and searchs for "BUILD SUCCESSFUL" if it is there I want to add the sentence Build Gelukt and if it fails I want to add Build Fout. But I get always the answer Build Gelukt, while as you can see in the image he sends back that the build is Failed.
Whats wrong with this piece of code?
for /f "tokens=* delims= " %%a in (plugins.txt) do (
echo %%a
cd "C:\dotCMS Automatic Install"
java -cp .;"C:\dotCMS Automatic Install\svnkit.jar" Test %%a
cd %dotcms_home%
call ant deploy-plugins > test1.txt
FindStr "SUCCESSFUL" test1.txt
if %ERRORLEVEL% ==1 (echo ^<tr BGCOLOR=\"#FFFFFF\"^>^<td^>%%a^</td^>^<td^>Build Fout^</td^>^</tr^> >> C:\dotCMSResults\goedje.html ) else (echo ^<tr BGCOLOR=\"#00FF00\"^>^<td^>%%a^</td^>^<td^>Build Gelukt^</td^>^</tr^> >> C:\dotCMSResults\goedje.html)
del test1.txt
rem call ant undeploy-plugins >> test.txt
)
Classic batch problem - you are setting your ERRORLEVEL and attempting to access it using %ERRORLEVEL% within the same DO() clause. %VAR% expansion happens at parse time, and the entire FOR ... DO() statement is parsed once, so you are seeing the value of ERRORLEVEL before the statement was executed. Obviously that won't work.
jeb alluded to the answer in his comment regarding disappearing quotes. Your problem will be fixed if you setlocal enableDelayedExpansion at the top, and then use !ERRORLEVEL! instead of %ERRORLEVEL%. Also, GregHNZ is correct in that the ERRORLEVEL test should occur immediately after your FINDSTR statement.
There are other ways to handle ERRORLEVEL within parentheses that don't require delayed expansion:
The following tests if ERRORLEVEL is greater than or equal 1
IF ERRORLEVEL 1 (...) ELSE (...)
And below conditionally executes commands based on the outcome of the prior command
FindStr "SUCCESSFUL" test1.txt && (
commands to execute if FindStr succeeded
) || (
commands to execute if prior command failed.
)
The %ErrorLevel% variable applies to the immediately previous command only.
So when you do this:
echo Errorlevel: %ERRORLEVEL%
With your current code, you are getting the error level of the CD command above
Try putting your if %ERRORLEVEL% ==1 line immediately after the FindStr command, and then do the del and the cd afterward. Obviously you'll need to put the full path to the html file in your echo statement.

How to conditionally take action if FINDSTR fails to find a string

I have a batch file as follows;
CD C:\MyFolder
findstr /c:"stringToCheck" fileToCheck.bat
IF NOT XCOPY "C:\OtherFolder\fileToCheck.bat" "C:\MyFolder" /s /y
I am getting an error ("C:\OtherFolder\fileToCheck.bat" was unexpected at this time.) when trying to execute this.
Please let me know what I am doing wrong.
I presume you want to copy C:\OtherFolder\fileToCheck.bat to C:\MyFolder if the existing file in C:\MyFolder is either missing entirely, or if it is missing "stringToCheck".
FINDSTR sets ERRORLEVEL to 0 if the string is found, to 1 if it is not. It also sets errorlevel to 1 if the file is missing. It also prints out each line that matches. Since you are trying to use it as a condition, I presume you don't need or want to see any of the output. The 1st thing I would suggest is to redirect both the normal and error output to nul using >nul 2>&1.
Solution 1 (mostly the same as previous answers)
You can use IF ERRORRLEVEL N to check if the errorlevel is >= N. Or you can use IF NOT ERRORLEVEL N to check if errorlevel is < N. In your case you want the former.
findstr /c:"stringToCheck" "c:\MyFolder\fileToCheck.bat" >nul 2>&1
if errorlevel 1 xcopy "C:\OtherFolder\fileToCheck.bat" "c:\MyFolder"
Solution 2
You can test for a specific value of errorlevel by using %ERRORLEVEL%. You can probably check if the value is equal to 1, but it might be safer to check if the value is not equal to 0, since it is only set to 0 if the file exists and it contains the string.
findstr /c:"stringToCheck" "c:\MyFolder\fileToCheck.bat" >nul 2>&1
if not %errorlevel% == 0 xcopy "C:\OtherFolder\fileToCheck.bat" "c:\MyFolder"
or
findstr /c:"stringToCheck" "c:\MyFolder\fileToCheck.bat" >nul 2>&1
if %errorlevel% neq 0 xcopy "C:\OtherFolder\fileToCheck.bat" "c:\MyFolder"
Solution 3
There is a very compact syntax to conditionally execute a command based on the success or failure of the previous command: cmd1 && cmd2 || cmd3 which means execute cmd2 if cmd1 was successful (errorlevel=0), else execute cmd3 if cmd1 failed (errorlevel<>0). You can use && alone, or || alone. All the commands need to be on the same line. If you need to conditionally execute multiple commands you can use multiple lines by adding parentheses
cmd1 && (
cmd2
cmd3
) || (
cmd4
cmd5
)
So for your case, all you need is
findstr /c:"stringToCheck" "c:\MyFolder\fileToCheck.bat" >nul 2>&1 || xcopy "C:\OtherFolder\fileToCheck.bat" "c:\MyFolder"
But beware - the || will respond to the return code of the last command executed. In my earlier pseudo code the || will obviously fire if cmd1 fails, but it will also fire if cmd1 succeeds but then cmd3 fails.
So if your success block ends with a command that may fail, then you should append a harmless command that is guaranteed to succeed. I like to use (CALL ), which is harmless, and always succeeds. It also is handy that it sets the ERRORLEVEL to 0. There is a corollary (CALL) that always fails and sets ERRORLEVEL to 1.
You are not evaluating a condition for the IF. I am guessing you want to not copy if you find stringToCheck in fileToCheck. You need to do something like (code untested but you get the idea):
CD C:\MyFolder
findstr /c:"stringToCheck" fileToCheck.bat
IF NOT ERRORLEVEL 0 XCOPY "C:\OtherFolder\fileToCheck.bat" "C:\MyFolder" /s /y
EDIT by dbenham
The above test is WRONG, it always evaluates to FALSE.
The correct test is IF ERRORLEVEL 1 XCOPY ...
Update: I can't test the code, but I am not sure what return value findstr actually returns if it doesn't find anything. You might have to do something like:
CD C:\MyFolder
findstr /c:"stringToCheck" fileToCheck.bat > tempfindoutput.txt
set /p FINDOUTPUT= < tempfindoutput.txt
IF "%FINDOUTPUT%"=="" XCOPY "C:\OtherFolder\fileToCheck.bat" "C:\MyFolder" /s /y
del tempfindoutput.txt
In DOS/Windows Batch most commands return an exitCode, called "errorlevel", that is a value that customarily is equal to zero if the command ends correctly, or a number greater than zero if ends because an error, with greater numbers for greater errors (hence the name).
There are a couple methods to check that value, but the original one is:
IF ERRORLEVEL value command
Previous IF test if the errorlevel returned by the previous command was GREATER THAN OR EQUAL the given value and, if this is true, execute the command. For example:
verify bad-param
if errorlevel 1 echo Errorlevel is greater than or equal 1
echo The value of errorlevel is: %ERRORLEVEL%
Findstr command return 0 if the string was found and 1 if not:
CD C:\MyFolder
findstr /c:"stringToCheck" fileToCheck.bat
IF ERRORLEVEL 1 XCOPY "C:\OtherFolder\fileToCheck.bat" "C:\MyFolder" /s /y
Previous code will copy the file if the string was NOT found in the file.
CD C:\MyFolder
findstr /c:"stringToCheck" fileToCheck.bat
IF NOT ERRORLEVEL 1 XCOPY "C:\OtherFolder\fileToCheck.bat" "C:\MyFolder" /s /y
Previous code copy the file if the string was found. Try this:
findstr "string" file
if errorlevel 1 (
echo String NOT found...
) else (
echo String found
)
I tried to get this working using FINDSTR, but for some reason my "debugging" command always output an error level of 0:
ECHO %ERRORLEVEL%
My workaround is to use Grep from Cygwin, which outputs the right errorlevel (it will give an errorlevel greater than 0) if a string is not found:
dir c:\*.tib >out 2>>&1
grep "1 File(s)" out
IF %ERRORLEVEL% NEQ 0 "Run other commands" ELSE "Run Errorlevel 0 commands"
Cygwin's grep will also output errorlevel 2 if the file is not found. Here's the hash from my version:
C:\temp\temp>grep --version
grep (GNU grep) 2.4.2
C:\cygwin64\bin>md5sum grep.exe
c0a50e9c731955628ab66235d10cea23 *grep.exe
C:\cygwin64\bin>sha1sum grep.exe
ff43a335bbec71cfe99ce8d5cb4e7c1ecdb3db5c *grep.exe

Resources