A working script that moves various files based on file name runs successfully. After the script is done it will check two directories for any lingering files using IF EXIST *.txt. This works great except I've noticed some files with no extension. These were not an issue before and since that cannot be helped due to processes out of my control, I need to amend my script.
My only idea is the following code. Bear with as there are two conditions:
:check1
PUSHD "\\UNC\path1" &&(
DIR /A-D *.
IF %errorlevel% NEQ 0 GOTO check2
) & POPD
:add1
ECHO Add note to the log file
:check2
PUSHD "\\UNC\path2" &&(
DIR /A-D *.
IF %errorlevel% NEQ 0 GOTO laststep
) & POPD
:add2
ECHO Add note to the log file
:laststep
Some other code before exiting
This should run DIR on the path and if files without extensions exist, it will have %errorlevel% zero and move on to the next check. If there are no files present, it will have %errorlevel% not zero (likely 1) and it will append some text to the log before the next check. Check two will do the same.
This seems to be awfully complicated and I am not able to find a "one-liner" solution that is as easy as IF EXIST. I realize I can use *. but that returns directories as well and may result in an incorrect %errorlevel%.
Updated Code
Where I normally set my variables, I also SET the two paths to run DIR against. This way they can be used more easily elsewhere and I bypass the UNC Path error I normally get - reasons for that are unknown to me. The updated file check, used only for files without an extension, is:
DIR %p1% /b /a-d|FIND /v "." && ECHO Found 1 >> %log%
DIR %p2% /b /a-d|FIND /v "." && ECHO Found 2 >> %log%
FINDSTR /I "Found" %log%
IF %errorlevel% EQU 0 GOTO stillthere
:nofiles
Some code
GOTO domore
:stillthere
Some code
:domore
Other code before exit
Thank you for the responses, I've learned from this.
Is this what you want to find?
dir /b /a-d |find /v "."
#ECHO OFF
SETLOCAL
:check1
PUSHD "u:\path1"
DIR /A-D *. >NUL 2>NUL
IF %errorlevel% EQU 0 ECHO Add note \path1 to the log file
POPD
PUSHD "u:\path2"
DIR /A-D *. >NUL 2>NUL
IF %errorlevel% EQU 0 ECHO Add note \path2 to the log file
POPD
:laststep
:: Some other code before exiting
GOTO :EOF
Your problems include:
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
and you are potentially jumping out of a PUSHD/POPD bracket which would mean your POPD won't necessarily restore your starting directory.
(Note that I used u:\ rather than a server to suit my system)
You are already using && to verify PUSHD worked. You can do the same with your DIR /-D. I find it makes life easier. Also, you probably want to hide any error message if *. is not found, especially since that is the expected condition. I also hid the display of any files that might be found, but you can certainly get rid of that redirection. You might also want to hide error message if PUSHD fails, but I didn't implement that.
PUSHD "\\UNC\path1" && (
DIR /A-D *. 1>nul 2>nul && ECHO Add note to the log file
POPD
)
PUSHD "\\UNC\path2" && (
DIR /A-D *. 1>nul 2>nul && ECHO Add note to the log file
POPD
)
ECHO Some other code before exiting
Related
I am trying to rename every image in a directory to add the date that each file was created, however, I keep either getting "invalid syntax" or "A duplicate file name exists, or the file cannot be found"
I am running Windows 10, and accessing the images off a flash drive (hence the short file path). I tried having all the code in one for-loop, when that didn't work I tried using batch functions, no dice. I did see someone mention on another thread to use delayed expansion, I would be up for using this if someone could give a better explanation than the /? command.
#echo off
REM batch file is placed in top of F drive, same as "images 2017+"
cd "F:\images 2017+"
FOR /R "F:\images 2017+" %%F in (*.jpg) do call :renER "%%~nF" "%%~tF"
goto :eof
:renER
cd "F:\images 2017+"
pause
echo %1
echo %2
rename %1.jpg %1_%2.jpg
pause
goto :eof
:end
For every .jpg file in "images 2017+", the date which that file was created would be stuck onto the end after a space.
thisIsMyFile.jpg made at 5-13-2017, would become thisIsMyFile 5-13-2017.jpg
Current output
EDIT:
I am CDing into the same directory as the images are, then using the passed variables to locate the correct image (The date is one of the passed variables, and shows up in the echo command).
I notice that you only want the date, not the time so you can do that as follows using your existing Call to a label, There is also no need to use FOR /R in this case so I'll use a normal for loop:
#echo off
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
CALL :RenER "%%~fA" %%~tA
)
GOTO :eof
:RenER
PAUSE
ECHO %1
ECHO %2
SET "_tmp=%~2"
SET "_tmp=%tmp:/=-"
REN "%~1" "%~n1_%_tmp%%~x1"
PAUSE
GOTO :eof
Notice how above we are dropping the Time off immediately by not wrapping it in quotes since you don't want that to be part of the file name.
You can also forgo the call to a label entirely without needing delayed expansion by using a second loop, as a matter of preference I think this is quite a bit cleaner!
#echo off
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
FOR /F "Tokens=1-3 Delims=/ " %%a IN ('echo.%%~tA') DO (
PAUSE
ECHO.%%~fA
ECHO.%%~tA
REN "%%~fA" "%%~nA_%%a-%%b-%%c%%~xA"
PAUSE
)
)
this is nice and clean and with a minor edit we can paste it directly into the CMD Prompt which is nicer still This is because we are not using DelayedExpansion, Calling a Label, or using Temp variables so by changing the %%s to %s, we can then Paste this directly into the CMD Line which is often more convenient when doing these sorts of operations:
This Multi-line will do just fine to be pasted into CMD directly:
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
FOR /F "Tokens=1-3 Delims=/ " %a IN ('echo.%~tA') DO #(
PAUSE
ECHO.%~fA
ECHO.%~tA
REN "%~fA" "%~nA_%a-%b-%c%~xA"
PAUSE
)
)
or, as a single line to paste into CMD if you prefer:
FOR %A IN ("F:\images 2017+\*.jpg") DO #( FOR /F "Tokens=1-3 Delims=/ " %a IN ('echo.%~tA') DO #( PAUSE& ECHO.%~fA& ECHO.%~tA& REN "%~fA" "%~nA_%a-%b-%c%~xA"& PAUSE ) )
no need to cd anywhere. ren takes a full path/filename for source - just the destination must be a filename only. So ... do call :renER "%%~fF" "%%~tF" is fine (no need to snip the extension and add it again later). In the subroutine reformat the time to a valid string and reassemble the destination file name:
#echo off
FOR /R "F:\images 2017+" %%F in (*.jpg) do call :renER "%%~fF" "%%~tF"
goto :eof
:renER
pause
echo %1
echo %2
set "string=%~2"
set "string=%string::=-%"
set "string=%string:/=-"
ECHO rename "%~1" "%~n1_%string%%~x1"
pause
goto :eof
:end
NOTE: I disarmed the rename command. Remove the ECHO after troubleshooting, if it works as intended.
#Stephan's answer is probably the best approach. But if you want to change directories ...
The windows shell has a working drive/volume, and on each drive/volume a current working folder. cd changes the working folder on a disk; to change the working folder on a drive (which is not the working drive) and to make that drive the working drive, you need to use cd /d, in this case cd /d "F:\images 2017+".
(A plain cd in this instance changes the working folder on F:\, but if your working folder is on C: -- as I'm guessing is the case -- it will not be changed.)
Assuming command extensions are enabled, you should also be able to use pushd and popd. pushd behaves like cd /d but also saves your previous location; popd returns you to that previous location. (And IIRC pushd will accept UNC paths.)
So at the beginning of your script, pushd "F:\images 2017+", and at the end popd.
I tend to favor pushd/popd over cd because invocations can be nested. So you can do things like
(assume working directory is C:\Users\IoCalisto):
pushd "F:\images 2017+"
(working directory is now F:\images 2017+)
pushd "Z:\images 2015-2016"
(working directory is now Z:\images 2015-2016)
popd
(working directory is now F:\images 2017+)
popd
(working directory is now C:\Users\IoCalisto)
... with this approach, your scripts will have fewer "side effects" and be more modular, or at least modularizable.
I am a complete novice when it comes to scripting, but am attempting to write a batch script which runs a command to output a png file to a printer. The script I have works fine for one file, but when there are multiple files it does not.
Can anyone point me in the right direction please?
#echo off
REM ___Change Directory to where Label Is Stored___
pushd C:\AFP\to
REM ___Create Variable to capture filename of any png file___
for /F %%a in ('dir /b *.png') do set FileName=%%~na.png
REM ___Now we have the filename as a variable, send it to printer using Zebra SSDAL___
\\172.16.100.2\nDrive\Prime_DPD_Label_Print\ssdal.exe /p "TSC DA200" send %FileName% >> C:\AFP\Log\Label_Printing_Log.txt
REM ___Copy PNG File to Backup Folder___
XCOPY /y /q /c C:\AFP\to\*.png C:\AFP\backup\
REM ___Delete PNF File from To Folder___
DEL C:\AFP\to\*.png
When the script runs, the first file prints fine. The subsequent files then do not print, I get "File does not exist" back from the ssdal.exe command. Why would the first one work but not the subsequent prints? I would have expected the for to loop through.
#ECHO Off
SETLOCAL
REM ___Change Directory to where Label Is Stored___
pushd C:\AFP\to
REM ___Process all png files___
for /F "delims=" %%a in ('dir /b *.png') do (
REM ___Now we have the filename as "%%a", send it to printer using Zebra SSDAL___
\\172.16.100.2\nDrive\Prime_DPD_Label_Print\ssdal.exe /p "TSC DA200" send "%%a" >> C:\AFP\Log\Label_Printing_Log.txt
IF ERRORLEVEL 1 (
CALL ECHO SSDAL returned ERRORLEVEL %%errorlevel%% FOR "%%a"
) ELSE (
REM ___Move PNG File to Backup Folder___
IF EXIST "c:\afp\backup\%%a" (
ECHO MOVE "%%a" to backup skipped as file already exists IN backup
) ELSE (
MOVE "%%a" C:\AFP\backup\
)
)
REM Two-second delay
TIMEOUT /t 2 >nul 2>nul
)
POPD
GOTO :EOF
Ah! using Zebra printers. Sensible lad!
This replacement script should do what you want.
The setlocal command is used to ensure that any variation made by this batch to the cmd environment is discarded when the batch ends.
The delims= sets "no delimiters" so for/f will set %%a to the entire filename, even if it contains spaces or other delimiters. Quoting %%a ensures such filenames are kept together as a single unit, not interpreted as separate tokens.
I'm assuming that ssdal acts responsibly and returns errorlevel non-zero in the case of errors. The if errorlevel 1 means if the errorlevel is currently 1 or greater than 1 and in that case, the error message is generated. We need to call echo ... %%varname%% ... in order to display the current value of the variable, if we're not using delayed expansion (many SO articles explain this)
Otherwise, if ssdal was successful, check for the existence of the filename in the backup directory, and either move it there or report that it already exists.
Of course, there are many ways in which this could be manipulated if features I've added are not desired. I'm happy to adjust this script to comply.
timeout is a standard utility to wait for a keypress. The redirection takes care of its prompting (it will provide a countdown unless gagged).
I am writing a batch script where I am writing a set of instruction inside function as I want to call it many time so want to reuse it.For me it works when i write outside function but inside function it never works . Below the code which I have used .
#echo off
set _prefs="%APPDATA%\test\test\BrowserProfile\prefs.js"
set _prefs_notes="%ProgramFiles(x86)%\test\tset1\Data\workspace\BrowserProfile\prefs.js"
#rem it works
copy /y %_prefs_notes% %_prefs_notes%.copy1 > nul
CALL :AMEND_PREFJS %_prefs_notes%
EXIT /B
:AMEND_PREFJS
rem make copy of prefs file
#rem it does not work
copy /y %~1 %~1.copy > nul
findstr /v "layers.acceleration.disabled" "%~1" > "%~1.tmp"
echo end
set %~1=
EXIT /B 0
goto end
:prefs_not_found
rem set error level?
echo "file does not exist -- %_prefs_notes%"
:end
set _prefs=
I am going to tell you what most of us do as best practices for writing batch files.
Never assign quotes to variables. You can use quotes though to protect the assignment of the variable. This helps with protecting special characters within the assignment and also keeps you from assigning trailing spaces.
Get into the habit of always using quotes to surround your file paths when using them with another command.
This is how I would write your batch file.
#echo off
set "_prefs=%APPDATA%\test\test\BrowserProfile\prefs.js"
set "_prefs_notes=%ProgramFiles(x86)%\test\tset1\Data\workspace\BrowserProfile\prefs.js"
#rem it works
copy /y "%_prefs_notes%" "%_prefs_notes%.copy1" > nul
CALL :AMEND_PREFJS "%_prefs_notes%"
EXIT /B
:AMEND_PREFJS
rem make copy of prefs file
#rem it does not work
copy /y "%~1" "%~1.copy" > nul
findstr /v "layers.acceleration.disabled" "%~1" > "%~1.tmp"
echo end
EXIT /B 0
I am creating a script as a .cmd and have a command that renames a folder from "test" to "test.old" on some occasions there may already be a "test.old" that exists creating a duplicate error so I implemented a very redimentry workaround but am trying to figure out how to loop this command so it adds a variable number if a duplicate exists.
E.g If test1.old it renames to test2.old and if that exists then it renames to test3.old etc..
REN "%userprofile%\AppData\Local\test" "test.old"
if %errorlevel% == 0 goto :TESTNOERRORS if %errorlevel% == 1 goto :TESTDUP
:TESTDUP
REN "%userprofile%\AppData\Local\test" "test1.old"
REN "%userprofile%\AppData\Local\test" "test2.old"
:TESTNOERRORS
What would the most efficient way to do this in a .cmd script?
P.s I am very new to scripting in general as well as programming,
Thank you
You can use it :
REN "%userprofile%\AppData\Local\test" "test.old"
if %errorlevel% == 1 goto :TESTDUP
goto :END
:TESTDUP
REN "%userprofile%\AppData\Local\test" "test%it%.old"
if %errorlevel% == 0 goto :END
set /A it+=1
goto :TESTDUP
:END
If the first REN not work the loop try to rename the folder until it find an available name
Retrying the rename operation based on errorlevel is not a good idea because the rename can also be failed for variety of reasons other than duplicate name conflicts. for example when the source does not exist or when you have not sufficient permissions to rename the object or when the target is in use,...
A more general approach would be something like this
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FSObjToRename=PathToFileOrFolder"
set /a "start#=1, maxRetries=10000, end#=start#+maxRetries"
set "q=""
for %%F in ("%FSObjToRename%") do (
set "SpawnCommand=for /L %%# in (%start#%,1,%end#%) do #if not exist %%q%%%FSObjToRename%.%%#.old%%q%% (ren %%q%%%FSObjToRename%%%q%% %%q%%%%~nxF.%%#.old%%q%% & exit)"
REM This is a more readable form of the SpawnCommand:
REM for /L %%# in (%start#%,1,%end#%) do #if not exist "%FSObjToRename%.%%#.old" (ren "%FSObjToRename%" "%%~nxF.%%#.old" & exit)
)
if exist "%FSObjToRename%" cmd /e:on /v:off /d /s /c "%SpawnCommand%"
The above code avoids :Labels and GOTOs completely by executing the rename loop in new instance of CMD.EXE so it can break the loop by exit command but if the string for SpawnCommand looks weird and confusing (mainly because of %%q%% to hide quotation marks from batch file parser) then for small number of retries, the loop can be rewritten like this
if exist "%FSObjToRename%" for %%F in ("%FSObjToRename%") do (
for /L %%# in (%start#%,1,%end#%) do (
if not exist "%FSObjToRename%.%%#.old" (ren "%FSObjToRename%" "%%~nxF.%%#.old" & goto :break)
)
)
:break
Using goto inside of FOR loop will prevent the execution of the loop body but does not break to loop iterator (It will blindly count to %end#%) so there would be a delay between executing goto :break and actually going to :break, by the amount which depends on the number of iterations
What is considered small for number of retries, completely depends on the performance of the computer. For today computers you may not notice a delay even for 100000 iterations. for small enough retries the latter will have slightly better performance than to launching new instance of cmd but the former is more robust in handling arbitrary retries.
Using backward GOTOs (when target Label is above the GOTO command) should be avoided whenever possible specially when the number of iterations have the potential be large and size of the batch script is not small.
I have the following line in a batch script
for %%a in (*.rmt) do (findstr /C:" model='" %%a)>tmp.par
When I run this on an empty folder, the errorlevel is still 0.
However, if I replace *.rmt with a filename, say x.rmt, which doesnt exist in the folder either, the errorlevel becomes 1.
Ideally, if there are no RMT files in the folder, shouldnt the errorlevel!=0?
I require this For loop to work on *.rmt, as there might be 0 to multiple RMT files in a folder. Please help.
Thanks.
Note: If the string " model='" exists in one RMT file, it will compulsorily be present in all the other RMT files(if any) in the folder.
The findstr is never executed if there are no matches to the *.rmt, hence the errorlevel remains unchanged.
When you use x.rmt, FOR changes behaviour - it's no longer looking for a filename matching, it's looking at a particular string - which may or may not be a filename, which may or may not exist.
You could deliberately set errorlevel before the for
#ECHO OFF
SETLOCAL
ECHO y|FIND "x">nul
for %%a in (*.rmt) do (findstr /C:" model='" %%a)
ECHO errorlevel=%errorlevel%
GOTO :EOF
which will return errorlevel 1 unless the match is found.
Try this:
#echo off
for /F "delims=" %%i in ('dir /b "path_to_dir\*.rmt"') do (
:: echo Folder is NON empty
findstr /C:"model='" %%i >> C:\testlog.log
goto :EOF
)
No, the FOR command never sets the ERRORLEVEL <> 0 if there are no iterations.
Yes, the following command reports ERRORLEVEL=1:
for %%a in (notExists.rmt) do (findstr /C:" model='" %%a)>tmp.par
But that is because the simple FOR simply lists the string(s) within the IN() clause if they do not contain wildcards. It doesn't bother checking to see if the file exists. So your FINDSTR command is actually raising the error because it cannot find the file, not the FOR statement.
Your command is flawed in that each iteration overwrites the previous tmp.par. That can be easily fixed by adding an extra level of parentheses. This also will create an empty tmp.par if no files were found or if none of the files contained the search string. The ERRORLEVEL cannot be relied upon because its value will not have been set if no files were found, or it may be 0 or 1 depending on if the last file contained the search string.
(for %%a in (*.rmt) do (findstr /C:" model='" %%a))>tmp.par
If you don't mind having a filename: prefix on each line of output, then you can simplify your code to:
findstr /C:" model='" *.rmt >tmp.par 2>nul
This also will create an empty tmp.par file if no files were found, or if none of the files contain the search string. But now the ERRORLEVEL will be reliable. The ERRORLEVEL is 1 if no files are found or if no files contain the search string. Otherwise the ERRORLEVEL will be 0.