Jump to content
Strawberry Orange Banana Lime Leaf Slate Sky Blueberry Grape Watermelon Chocolate Marble
Strawberry Orange Banana Lime Leaf Slate Sky Blueberry Grape Watermelon Chocolate Marble

MSFN is made available via donations, subscriptions and advertising revenue. The use of ad-blocking software hurts the site. Please disable ad-blocking software or set an exception for MSFN. Alternatively, register and become a site sponsor/subscriber and ads will be disabled automatically. 


Sign in to follow this  
bphlpt

Possible to do this Batch without using a temporary file?

Recommended Posts

I have a batch routine that works very well, but I thought it might be improved to not use a temporary file at all -- less wear and tear on SSDs etc.

What I'm doing is checking a directory for the existence of files, not folders, and if found then proceed. Here is what currently works:

...
(SET "_Dir1=%~1") & IF DEFINED _Dir1 (SET "_Dir2=!_Dir1!\") ELSE (SET "_Dir2=")
DIR /-B/O-N/A-D "!_Dir1!">md5.tmp 2>nul
SET /P _FirstLine=<md5.tmp
del md5.tmp >nul 2>&1
IF DEFINED _FirstLine ...
...

Any ideas on how to not need a temporary file? I don't care at all what the file is, I just need to know if a file, not folder, is present.

EDIT:
I still want the answer to the above, if there is one, but I will also admit that step might not even be strictly necessary. I'm sometimes guilty of the belt + suspenders approach to coding.

Here is the follow on code.:

...
IF DEFINED _FirstLine (FOR /F "tokens=*" %%G IN ('dir /-B/O-N/A-D "!_Dir1!"') DO IF /i NOT "%%G"=="App.md5" md5sum "!_Dir2!%%G">>App.md5)
...

I just wanted to make absolutely sure that the command [ md5sum "!_Dir2!%%G">>App.md5) ] was not run unless there was a file to run it against. This isn't the only time that command is run, but rather I recurse through the directory structure and I didn't want there to be any chance of garbage of any kind ending up in App.md5, not even empty lines.

To be thorough, here is the entire function:

:f_RecurseMd5 %_DirNames%
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
(SET "_Dir1=%~1") & IF DEFINED _Dir1 (SET "_Dir2=!_Dir1!\") ELSE (SET "_Dir2=")
(DIR /-B/O-N/A-D "!_Dir1!">md5.tmp 2>nul) & (SET /P _FirstLine=<md5.tmp) & (del md5.tmp >nul 2>&1)
IF DEFINED _FirstLine (FOR /F "tokens=*" %%G IN ('dir /-B/O-N/A-D "!_Dir1!"') DO IF /i NOT "%%G"=="App.md5" md5sum "!_Dir2!%%G">>App.md5)
FOR /F "tokens=*" %%G IN ('dir /-B /AD "!_Dir1!"') DO IF EXIST "!_Dir2!%%G" (CALL :f_RecurseMd5 "!_Dir2!%%G")
:f_CleanUp
FOR /F "tokens=1* delims==" %%G IN ('"SET "_" 2>nul"') DO (SET "%%G=") & ENDLOCAL&EXIT /B 0

...which can be called either while already in the directory you want to examine ( so no argument is passed ), or pointing to the directory you want to examine.

Thanks in advance.

Cheers and Regards

Edited by bphlpt
added further explanation

Share this post


Link to post
Share on other sites

you can look here, how to make an exitloop....

https://stackoverflow.com/questions/39026705/how-do-i-verify-if-a-file-exists-and-its-from-today

Share this post


Link to post
Share on other sites
29 minutes ago, bphlpt said:

No suggestions?

Cheers and Regards

Some. :dubbio:

 "md5sum" is not a built-in batch command, so it must be an external program (and as such it should be always invoked as md5sum.exe, possibly even including its path to avoid possible issues when the batch is moved.

This said, I would personally rather avoid the IF check and rather use a pipe to FIND,

Also - surely you have your reasons but the /o-n parameter to dir doesn't seem like *needed*.

As well, you surely have your reasons, but usually the setlocal is done globally on the whole batch and not within a function/subroutine, and without other information the EXIT /B seems superfluous.

For the record, a directory should always ends with \.

Anyway, provided that I got the whole idea behind correctly, I would personally write that stuff more *like*:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
CALL :f_RecurseMd5 "%~dp0"
GOTO :EOF


:f_RecurseMd5 %_DirNames%
SET "_Dir1=%~1"
IF NOT "%_Dir1:~-1,1%"=="\" SET _Dir1=%_Dir1%\

FOR /F  "tokens=* delims=" %%? IN ('DIR /-B/A-D "!_Dir1!"^|FIND /V /I "App.md5"') DO ECHO md5sum.exe "!_Dir1!%%?"
REM FOR /F "tokens=* delims=" %%? IN ('DIR /-B/A-D "!_Dir1!"^|FIND /V /I "App.md5"') DO md5sum.exe "!_Dir1!%%?">>App.md5

FOR /F "tokens=* delims=" %%? IN ('dir /-B /AD "!_Dir1!"') DO IF EXIST "!_Dir1!%%?\" (CALL :f_RecurseMd5 "!_Dir1!%%?\")


:f_CleanUp
SET _Dir1= & ENDLOCAL&EXIT /B 0

Of course it still needs to be tested/validated.

jaclaz

 

Share this post


Link to post
Share on other sites

 

Thanks as always my friend,

This shows how much I forget after I haven't been actively writing batch for a while.

The routine as a whole was/is its own separate program that is called by an external application, as is md5sum.exe, so your comments regarding it are appropriate. Separating the actual "loop" into a separate routine helped simplify things. I sometimes "forget" about piping to "FIND", along with " /o-n " not being needed, so thanks for the reminders. I tried FIND as you wrote and it worked as expected, But when I changed how the routine was called, and referenced the location of md5sum.exe as being in the same folder as the this and the calling application, I realized I could also pass in the location of the temporary App.md5 as external to the folder I'm processing and then not need to check for it at all. To get the output from md5sum,exe to be formatted the way I wanted it, the working location has to be the folder of interest, which my previous method assumed, but that was easily accomplished with a pushd/popd. And your reminder that directories should always end with a "\" also simplified things a bit in the "loop". When I was dealing with some commands that had a trailing back slash and some that didn't, I got hung up on trying to work without one in all cases, and you demonstrated that it is usually easier to just add them back where they are missing, such as in the second "FOR". I was also able to move the cleanup and the check for the trailing backslash to the "outer" part of the routine to make it just a little bit cleaner.

So this is how things turned out:

 

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET "_Dir=%~1"
IF NOT "!_Dir:~-1,1!"=="\" SET "_Dir=!_Dir!\"
SET "_Tmp=%~2"
SET "_Md5=%~dp0%md5sum.exe"
pushd "!Dir!"
CALL :f_RecurseMd5
popd
SET "_Dir=" & SET "_Tmp=" & SET "_Md5=" & ENDLOCAL&EXIT

:f_RecurseMd5 %_DirNames%
(SET "_Dir=%~1")
FOR /F "tokens=* delims=" %%G IN ('DIR /-B/A-D "!_Dir!"') DO !_Md5! "!_Dir!%%G">>"!_Tmp!"
FOR /F "tokens=* delims=" %%G IN ('DIR /-B /AD "!_Dir!"') DO IF EXIST "!_Dir!%%G\" (CALL :f_RecurseMd5 "!_Dir!%%G\")
ENDLOCAL&EXIT


See anything else I missed?

Thanks again for the response and the help.

Cheers and Regards
 

Edited by bphlpt
formatting

Share this post


Link to post
Share on other sites
1 hour ago, bphlpt said:

This shows how much I forget after I haven't been actively writing batch for a while.

Naah, most of the time it is just one's own "writing style", or - if you prefer - there is more than one way to skin a cat (though none that the cat would appreciate ;)).

 

1 hour ago, bphlpt said:

And your reminder that directories should always end with a "\" also simplified things a bit in the "loop". When I was dealing with some commands that had a trailing back slash and some that didn't, I got hung up on trying to work without one in all cases, and you demonstrated that it is usually easier to just add them back where they are missing, such as in the second "FOR".

Yep, adding them when missing AND removing duplicates when they are already there, and check (once and for all) if they are included in quotes (needed for paths with spaces) i.e. (example):

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET "_Dir=%~1"
:: _Dir is not enclosed in quotes, stripped by the ~
CALL :validate_dir _Dir
SET _Dir
GOTO :EOF


:validate_dir
:: make sure it ends with backslash
SET %1=!%1!\
::make sure it ends with only one backslash and enclose it in double quotes
SET %1="!%1:\\=\!"
GOTO :EOF

 

1 hour ago, bphlpt said:

See anything else I missed?

Thanks again for the response and the help.

Cheers and Regards
 

You are welcome :), nothing missed that I can see, what I see unneeded is the ENDLOCAL&EXIT :dubbio: (the first can - should - be replaced by a GOTO :EOF, the last can - as well should - be replaced by a GOTO :EOF or, if it is the end of the batch, it's function is automatically performed by the actual EOF).

But again it is probably a question of writing style, to me the "main" of the program must always end with a GOTO :EOF and as well each and every subroutine must begin with a :label and end with a GOTO :EOF.

jaclaz

Edited by jaclaz

Share this post


Link to post
Share on other sites
55 minutes ago, jaclaz said:

Naah, most of the time it is just one's own "writing style"...

.., what I see unneeded is the ENDLOCAL&EXIT :dubbio: (the first can - should - be replaced by a GOTO :EOF, the last can - as well should - be replaced by a GOTO :EOF or, if it is the end of the batch, it's function is automatically performed by the actual EOF).

But again it is probably a question of writing style, to me the "main" of the program must always end with a GOTO :EOF and as well each and every subroutine must begin with a :label and end with a GOTO :EOF.

I've written some code in the past where I made extensive use of passing back ERRORLEVEL to indicate which error occurred, and then just got in the habit of always ending my routines that way. I didn't see it as a problem since according to SS64:


EXIT /b has the option to set a specific errorlevel, EXIT /b 0 for success, EXIT /b 1 (or greater) for an error.
The exit code can be an integer of up to 10 digits in length (positive or negative).

EXIT without an ExitCode acts the same as goto:eof and will not alter the ERRORLEVEL

As to your validating directories and dealing with quotes, you might find these two routines interesting, though I'm sure you could improve/streamline them. (You would have to adapt them to be sure that a directory does indeed end in one and only one backslash)

:: #############################################################################

:: #############################################################################
:: :f_QualDir "%_DirName%" _DirVariable
::
:: Convert a name to a path.
::
:: Usage: CALL :f_QualDir "%_DirName%" _DirVariable
::
:: This function takes as input a directory name or complete or partial path,
:: surrounded by any number of MATCHED qoutes, (if any spaces are in the name AT
:: LEAST ONE set of quotes is REQUIRED), the directory location can be relative
:: or absolute, and can have an ending back slash or not.  The result - a fully
:: qualified directory path, without quotes, and without a backslash - is
:: assigned to the variable in the second argument.  To be sure that any
:: potential empty string is handled correctly, it is suggested to ALWAYS
:: enclose the %_DirName% string in qoutes ["], extra levels of quotes will be
:: safely removed.  An empty string [""] will evaluate to the current directory.
:: Periods or dots [.] are evaluated the same way they are if you use them on
:: the command line.  A single dot [.] evaluates to the current directory, two
:: dots [..] evaluate to the parent directory, [..\..] evaluates to the next
:: parent, [..\..\..] to the next parent, etc.  It will automatically "stop" at
:: the drive level, ie C:, or D:, or whatever.  [\], [\.], [\..], or anything
:: with a backslash [\] as the left-most character, evaluates to the drive
:: level.  Since a single dot evaluates to the current directory, [.\.\.\.\.\.\]
:: will also evaluate to the current directory.  [..\Temp] is at the same level
:: as the current directory.  ["..\..\Fred's files\Myjunk"] is a valid
:: %_DirName%.  The directory does NOT have to exist and is NOT created.  This
:: simply converts a name to a path.
::
:: Dependencies: :f_DeMultiQuote
::
:: Upon exit, a fully qualified path name is assigned to "_DirVariable".
:: ERRORLEVEL is set to 0.
:: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
:f_QualDir "%_DirName%" _DirVariable
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
(SET _QDir=%1)&(CALL :f_DeMultiQuote _QDir)
IF "."=="!_QDir:~-1!" SET _QDir=!_QDir:~0,-1!
FOR %%G IN ("%_QDir%") DO FOR %%H IN ("%%~G.\") DO (SET "_QDir=%%~fH")
ENDLOCAL&(SET "%2=%_QDir:~0,-1%")&EXIT /B 0
::
:: #############################################################################

:: #############################################################################
:: :f_DeMultiQuote _DQVar
::
:: Remove multiple levels of quotes from a variable.
::
:: Usage: CALL :f_DeMultiQuote _DQVar
::
:: This function removes any number of MATCHED quotes from the outside of a
:: string variable and reassigns the result back to the same variable.
::
:: Dependencies: None [_DQVar must be provided, but it can be empty]
::
:: Upon exit, ERRORLEVEL is set to 0.
:: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
:f_DeMultiQuote _DQVar
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
(CALL SET _String=%%%1%%)
IF [!_String:~0^,1!]==[^"] (
:s_dmqAgain
	IF [!_String:~-1!]==[^"] ((SET _String=!_String:~1,-1!)
		IF [!_String:~0^,1!]==[^"] GOTO s_dmqAgain)
)
ENDLOCAL&SET %1=%_String%
EXIT /B 0
::
:: #############################################################################


Cheers and Regards

Share this post


Link to post
Share on other sites

Well, the subroutines I write don't generate errors ;), as said it is a case of writing style.

Anyway, the ENDLOCAL is unneeded (there are very few cases when you need to NOT run the whole batch "LOCAL" and thus you need to exit the mode).

The subroutine about MATCHED quotes is not relevant speaking of Paths.

A path is EITHER enclosed in quotes OR it is not (you can't have a quote or any matched couple of quotes in a path), SO you can strip ALL quotes  and reenclose the path in quotes (and this covers BOTH matched and UNmatched quotes) i.e.:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET _Dir=%*
:: _Dir may be enclosed in quotes, or simply contain one or more quotes
CALL :validate_dir _Dir
SET _Dir
GOTO :EOF


:validate_dir
::make sure it contains no quotes
SET %1=!%1:^"=!

:: make sure it ends with backslash
SET %1=!%1!\


::make sure it ends with only one backslash and enclose it in double quotes
SET %1="!%1:\\=\!"
GOTO :EOF

It is much simpler (as it does a much simpler transformation).

jaclaz

 

  • Like 1

Share this post


Link to post
Share on other sites

Well, when you put it THAT way... :)

Point taken about ENDLOCAL. The matched quote routine was written to deal with other various variables, and was just reused for this application since it was handy, but your routine is MUCH more efficient when dealing specifically with paths.

The errors aren't MY errors, but because of the data dealt with or user input. But like I said at the beginning, I'm sometimes guilty of the belt + suspenders approach to coding.

Cheers and Regards

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...