Lionel Blanc-guilhon
Lionel Blanc-guilhon

Reputation: 63

Hiding error messages if writing to a file fails in a Windows .BAT script

i just subscribe here.

I would want to hide the "Acces is denied." error message in case writing to a read-only file fails. Could someone tell me why the following code doesn't work as expected ? I'm using Windows 10.

:: This file is assumed to be read-only.
echo( >"MyReadOnlyFile.txt" 2>nul
( echo( ) >"MyReadOnlyFile.txt" 2>nul

In both case the "Access is denied." message is displayed, although the following "How-to: Redirection" page at SS64 says the contrary: https://ss64.com/nt/syntax-redirection.html

Redirect to NUL (hide errors)
command >filename 2> nul  Redirect output to file but suppress error
(command)>filename 2> nul  Redirect output to file but suppress CMD.exe errors

Thanks in advance for your help.

Lionel.

Upvotes: 0

Views: 1861

Answers (5)

Lionel Blanc-guilhon
Lionel Blanc-guilhon

Reputation: 63

I complete answers above by adding the following one that could be a fast workaround depending your needs.

setlocal disabledelayedexpansion
set "_mute=>nul & 2>&1"

rem * Set the backspace character (cf other threads in this forum).
for /F "usebackq" %%c in (`"prompt $H & for %%a in (1) do rem"`) do set "CHR8=%%c"

rem * Display a string containing redirection characters.
rem   - The leading+ending double quotes will be overriden, so output will be `Hello & | < > world`
rem   - WARNING: An ending space must be typed after the last `%CHR8%`, ie the typed command is `echo "%CHR8Hello & | < > world"%CHR8% `.
rem   - It's way faster than using `echo| set /p dummy="Hello & | < > world".
rem   - ERRORLEVEL is set properly, so conditional execution `&& (..) || (..)` can be used.
echo "%CHR8Hello & | < > world"%CHR8% 

rem * The string can be written to a file as well.
 (>> "%MyFile.path%" (
     echo BEG-OF-TEST
     echo "%CHR8Hello & | < > world"%CHR8% 
     echo END-OF-TEST
  )) %_mute% && (
    rem Tasks in case of successfull write.
  ) || (
    rem Tasks in case of write failure.
  )

Screenshots 1+2 in action (taken from my application), ampersands & are properly written to a file for the MAME game "HyperMan". CHR8 File content display

Initially each line was written piece by piece by using a slower <nul set /p =".." (or <nul set /p dummy="..") without the need to open a temporary local scope at each loop iteration, but these two commands set ERRORLEVEL to 1 in case of success (as discussed elsewhere on this forum) so can't be use in a reliable way. All in all i couldn't use my exception handler to detect a write failure efficiently in this (important) function, hence my initial post about hiding and capturing error with echo| set /p dummy="..".

If for example i change the attributes of the file in_mch_x.lst_file.path to read-only and i retry the same search, then the write failure is now properly captured thanks to the standard redirection of echo (screenshot 3 below) and the exception code properly propagated to highler calling levels. Screenshot 4 is the same in debug mode.

enter image description here enter image description here

Again thanks a lot guys, i should have subscribed here earlier for sure ! :p

Upvotes: 0

aschipfl
aschipfl

Reputation: 34979

You need to enclose the whole redirection expression in parantheses, otherwise you just redirect the output of the echo command:

(echo/> "MyReadOnlyFile.txt") 2> nul

Anyway, I'd use something that does not alter the file contents just in case it's not write-protected:

(rem/>>"MyReadOnlyFile.txt") 2> nul

Upvotes: 0

Lionel Blanc-guilhon
Lionel Blanc-guilhon

Reputation: 63

Thanks for your quick answers, i really appreciate.

Microsoft has been telling us for over a decade that PowerShell is the future of scripting.

I know indeed. I'm currently optimizing my own application in Batch+third party tools (a retrogame launcher, the main Batch script is about 41000 lines) by improving various stuffs here and there, so i guess Powershell will wait for a next project (smile). But anyway thanks for your advice, you're right.

Alternatively you could use the same method to reset the read only attribute, write to the file, then apply the read only attribute again:

This dir /A:-DR /B is nice and fast way to test if a file is read-only, indeed. There may be benefits to proceed in this way instead of 1. grabbing the file attribute with a more conventional for %%F in ("MyReadOnlyFile.txt") do set "file.attr=%%~aF" then 2. testing the second character by enabling delayed expansion. I will check where i can use it in my code to optimize some parts slightly, thank you for your suggestion Compo.

The error isn't written to STDERR, but to STDOUT (don't ask me, why), so echo( >"MyReadOnlyFile.txt" 1>nul suppresses the error message (sadly along with any possible useful output)

Thanks for your answer Stephan. Well, we know that Batch is quite insane often, but a such behavior would be more than insanity (smile). Redirecting stdout 1>nul doesn't hide the error message (as expected). However, surrounding the command and file redirection with brackets works as expected:

(echo( >"MyReadOnlyFile.txt") 2>nul

It might be a possible typing error on SS64 about the position of surrounding brackets, it should be (command >"filename") 2>nul. The error message is redidrected to stderr as expected, confirmed by the following if you open the "stderr.txt" file.

(echo( >"MyReadOnlyFile.txt") 2>"stderr.txt"

Again thank you to have replied, i did read a ton of threads on stackoverflow during the whole development of my application but i never subscribed. So that's a special feeling to have opened an account finally.

EDIT I also add these two in case it can help someone.

rem Merge stderr to stdout.
set "_mute=>nul 2>&1" & set "_mute1=1>nul" & set "_mute2=2>nul"

rem Force ERRORLEVEL to 0.
set "_errlev0=(call )"

rem If you're lazy to type it each time (i am !).
set "_ede=enabledelayedexpansion"

rem Existing file name.
set "file.path=MyTestFile.txt"

rem Paths to the SQLite executable and SQLite database (example).
set "sqlite_file.path=C:\SQLite\SQLite.exe"
set "db_file.path=C:\Database\MyBase.db"

setlocal %_ede%
rem --- Test1 ---
%_errlev0%
echo ERRORLEVEL=!ERRORLEVEL!
(echo| set /p dummy_name="Hello & world">>"!file.path!") %_mute% && (
  echo Test1 succeeded
) || (
  echo Test1 failed
)
echo %ERRORLEVEL=!ERRORLEVEL!


rem --- Test2 ---
:: Whatever SQLite query of your choice.
set "sql_query=SELECT arcade_t.description FROM arcade_t WHERE arcade_t.description LIKE '%%Capcom%%'"
set "sql_bad_query=_!sql_query!"

%_errlev0%
echo ERRORLEVEL=!ERRORLEVEL!
(>>"!file.path!" "!sqlite_file.path!" "!db_file.path!" "!sql_query!") %_mute% && (
  echo Test2 succeeded
) || (
  echo Test2 failed
)
echo ERRORLEVEL=!ERRORLEVEL!

endlocal
  • Surrounding the WHOLE command echo| set /p dummy_name="..." with brackets (as mentionned above, cf typing error in the SS64 page) and muting both stdout+stderr (or stderr only with %_mute2%) will hide the error message if writing the file failed.

  • Mentionning a variable name in the command set /p dummy_name="..." sets ERRORLEVEL as expected once the command echo| set /p="..." ends: 0 if writing the file succeeded, 1 if it failed. On the contrary, using a nameless command echo| set /p ="..." sets ERRORLEVEL to 1 even if writing the file succeeded, so should be avoided.

  • Again, surrounding the WHOLE SQLite command with brackets will hide the "Acces is denied." message but the SQLite "Error: near "_SELECT": syntax error" error message as well in case the SQLite query is syntaxically incorrect. This has has been tested and confirmed by replacing the proper query with the bad one sql_bad_query.

Upvotes: 1

Compo
Compo

Reputation: 38718

As stated already in the comments, the Access is denied message is output to stdOut, as opposed to the expected stdErr.

Since your task is related to a read only issue, (there are other reasons why that message may be output), I'd offer this relatively simple alternative:

@Dir "MyReadOnlyFile.txt" /A:-DR /B 1>NUL 2>&1 || (Echo() 1>>"MyReadOnlyFile.txt"

It simply determines if the existing file is read only, and only performs a write operation if it is not.

Alternatively you could use the same method to reset the read only attribute, write to the file, then apply the read only attribute again:

@Set "File=MyReadOnlyFile.txt"
@Dir "%File%" /A:-DR /B 1>NUL 2>&1 && (
    %SystemRoot%\System32\attrib.exe -R "%File%" 2>NUL
    If Not ErrorLevel 1 (
        (Echo() 1>>"%File%"
        %SystemRoot%\System32\attrib.exe +R "%File%"
    )
) || (Echo() 1>>"%File%"

Upvotes: 1

lit
lit

Reputation: 16266

This will work in a batch-file run by cmd. If you are on a supported Windows system, PowerShell is available. Microsoft has been telling us for over a decade that PowerShell is the future of scripting.

If the file is not read-only or does not exist, this will write to it.

powershell -NoLogo  -NoProfile -Command ^
    if (-not (Get-ChildItem -Path .\rofile.txt -ErrorAction SilentlyContinue).IsReadOnly) ^
        { Set-Content -Path '.\rofile.txt' -Value 'then' }

Of course, it is easier and less cryptic if it is written as a PowerShell .ps1 file script.

if (-not (Get-ChildItem -Path .\rofile.txt -ErrorAction SilentlyContinue).IsReadOnly)
    { Set-Content -Path '.\rofile.txt' -Value 'then' }

Upvotes: 0

Related Questions