Command works on command line, but operates unexpectedly in batch file - batch-file

I have this simple batch code to check the Date Modified on a sub-folder (specifically the Recycle Bin.)
This search works flawlessly when manually input in Command Prompt, but not batch.
And using the same exact code to check other folders it works just fine. Help?
Code:
if exist C:\$Recycle.Bin (
pushd "C:\$Recycle.Bin"
for /F "delims=" %%a in ('dir /S /b S-1-*-1001 /AD') do set {file}=%%a
for %%a in ("%{file}%") do echo Recycle Bin: %%~ta
popd
)

The reason this is not working in batch is for one annoying feature of IF statements with the SET command. As stated by This Post - "cmd expands variables when commands are parsed, not when they are run. It so happens that an if or for statement with a block ( ... ) (or actually any block) counds as a single command in that case. So when you set variables inside a block and try using them in the same block there are no variables anymore – they were replaced by the values the variables had before the block even executed." - Joey
To fix this you can simply not put your code block inside the IF statement but rather use an ELSE and have it goto an :EOF
Option 1: - Avoid IF Statement W/H Code Block
#ECHO OFF
Rem | Check If Directory Exists & pushd It.
if exist "C:\$Recycle.Bin" (pushd "C:\$Recycle.Bin") ELSE (goto :EOF)
Rem | Grab data on folders
for /F "delims=" %%a in ('dir /S /b S-1-*-1001 /AD') do (set "{File}=%%a")
Rem | Display data on folders
for %%a in ("%{file}%") do (echo Recycle Bin: %%~ta)
Rem | Un-pushd
popd
pause
goto :EOF
If you do however wish to use a block inside the IF statment you will need to use setlocal enabledelayedexpansion at the top of your script. Furthermore, to echo or read brackets you will have to use !{File}! over %{File}%.
Option 2: - Properly expand IF Statement W/H Code Block
#ECHO OFF
#setlocal enabledelayedexpansion
if exist "C:\$Recycle.Bin" (
pushd "C:\$Recycle.Bin"
for /F "delims=" %%a in ('dir /S /b S-1-*-1001 /AD') do (set "{File}=%%a")
for %%a in ("!{file}!") do (
Set "data=%%~ta"
echo Recycle Bin: !data!
)
popd
) Else (Goto :EOF)
pause
goto :EOF

Related

How do I replace spaces and symbols in all files and FOLDERS with a batch file

I'm very close I think. I have a folder where-in I'm trying to rename all the sub folders and files such that caps are stripped, symbols replaced with applicable words, and spaces changed to hyphens in both the files AND their parent sub-folders.
Here's the batch file I have so far:
cd d:\scripts\testing\
for /r %%D in (.) do #for /f "eol=: delims=" %%F in ('dir /l/b "%%D"') do #ren "%%D\%%F" "%%F"
SET location=d:\scripts\testing\
for /R %location% %%A in (*.*) do call :replspace "%%A"
for /R %location% %%A in (*.*) do call :repland "%%A"
goto :eof
:replspace
set "_fn=%~nx1"
ren %1 "%_fn: =-%"
:repland
set "_fn=%~nx1"
ren %1 "%_fn:&=and%"
As you might be able to see, first it goes through and renames everything (files and folders) in d:\scripts\testing\ to lower-case. Next it renames all files in that same directory to replace spaces with hyphens and "&" with the word "and". This all works... except I need to do the same symbol and space changes to the folders and I'm not finding any real info on how to do that.
Anyone have any suggestions?
BTW, this runs on server 2012 r2, however for interoperability issues, the scripts have to be old fashioned batch scripts.
You can perform the entire replacement task with one for loop., by using dir /s to recursively search the directory.
#echo off
setlocal enabledelayedexpansion
pushd "d:\scripts\testing\" || goto :EOF
for /f "delims=" %%i in ('dir /b /l /s') do (
set "item=%%~i"
set "item=!item:%%~dpi=!"
set "item=!item: =-!"
ren "%%~fi" "!item:&=and!"
)
popd
I did not set the & replacement as a variable seeing as there are only two replacements done, so simply using the substitution on the last item makes sense without the need to set again. If you have more replacements to add, add them before the ren line:
Note that this example will merely echo the results for testing purpose. Only remove echo once you're convinced the results are as expected.
Then, take note of || goto :EOF in the pushd statement. This is critical for good reason. Should it fail to cd or pushd, generally the script will continue with the rename, from the working directory it was started in, or a previous cd pushd etc. In this case, if it fails to find the directory, or it does not have permission, it will skip the remainder of the script entirely.
Final Note. Should your files or folders contain ! this will need to change. You can then simply revert to moving the set and ren to a label, then call the label as delayedexpansion will cause the ! to be lost.
While Gerhard's script is clean and should work, it trips on itself under certain circumstances like mine. I wound up using this script:
rem #### Step 1: move to working directory ####
cd "d:\scripts\testing\"
rem #### Step 2: change case on everything ####
for /r %%D in (.) do #for /f "eol=: delims=" %%F in ('dir /l/b "%%D"') do #ren "%%D\%%F" "%%F"
rem #### Step 3: set location ####
SET location=d:\scripts\testing\
for /R %location% %%A in (*.*) do call :replspace "%%A"
for /R %location% %%A in (*.*) do call :repland "%%A"
rem #### Step 4: replace spaces/symbols on directories ####
setlocal enabledelayedexpansion
pushd "D:\scripts\testing\" || goto :EOF
for /f "delims=" %%i in ('dir /l /b /s /ad') do (
set "item=%%~i"
set "item=!item: =-!"
move "%%~fi" "!item:&=and!"
)
popd
rem #### variables ####
:replspace
set "_fn=%~nx1"
ren %1 "%_fn: =-%"
:repland
set "_fn=%~nx1"
ren %1 "%_fn:&=and%"
Its a combination of my own script and a slight modification of Gerhard's script. Bassically I ended up stepping through the modifications.
change everything to lower case.
replace spaces and symbols in the file names.
replace spaces and symbols in the directory names.
I know it's repetitive and I'd like to do it with less lines, but it works.

Batch file to append several text documents to one document

I have created a batch file which should do several things, including appending all text documents of a certain format into one text file provided there are more than one text files of this format in the directory. This section of the code is below:
:multiple
SET /a count=0
ECHO.> "%location%\UAV_camera_coords_all.txt"
FOR /r "%location%\Output" %%G in ("UAV_camera_coords_*.txt") do set /a count+=1
IF %count% GTR 1 (
FOR /r "%location%\Output" %%G in ("*.txt") DO (
SET file=%%~G
TYPE "%file%">>"%location%\UAV_camera_coords_all"
)
GOTO :end
It seems that the code is crashing upon reaching the if statement even though the count variable is greater than one. None of the code in the if statement is executed and indeed none of the code which should come after the if statement is executed either. Is there any syntax or other error which may be causing this?
Besides my comment. I assume that you are not really planning on typing the output of *.txt but instead only UAV_camera_coords_*.txt.. if not, feel free to change it back to *.txt
#echo off
for /f "tokens=1,*" %%i in ('dir /s "%location%\UAV_camera_coords_*.txt" ^| findstr "File(s)"') do set cnt=%%i
if %cnt% gtr 1 (
for /f %%a in ('dir /b /s "%location%\UAV_camera_coords_*.txt"') do type "%%~a"
)>"%location%\UAV_camera_coords-all.txt"
Note, I changed the output filename to be -all and not _all as that would type the file onto itself if it was to be a txt file as well.
Edit adding back original answer, before I realised you were recursively searching through directories:
#echo off
for /f "tokens=1,*" %%i in ('dir "UAV_camera_coords_*.txt" ^| findstr "File(s)"') do if %%i gtr 1 type "UAV_camera_coords_*.txt">>"%location%\UAV_camera_coords_all"
Just for the purpose of providing an alternative, this one uses xcopy to check the file count, and copy to merge them.
PushD "%location%\Output" 2>NUL && For /F %%G In ('""%__AppDir__%xcopy.exe" "UAV_camera_coords_*.txt" . /SQL"')Do If %%G Gtr 1 Copy /Y /B "UAV_camera_coords_*.txt" "..\UAV_camera_coords_all.txt">NUL & PopD

Variable not Echoing in For Loop

I am trying to get the file names using a batch file from a folder but it just doesn't work.
I followed guidelines from here but for some reason this isn't returning anything at all when it should!
FOR /F "tokens=*" %%G IN ('dir /b C:\Users\Desktop\UPD\*.txt') DO SET result=%%G
I also tried:
FOR /F "tokens=*" %%G IN (dir /b C:\Users\Desktop\UPD\*.txt') DO SET _result=%%~G
echo %_result% >> %~dp0Outputfile.txt
What I get is:
ECHO is on.
EDIT
Here is what I did so far now:
IF EXIST C:\Users\Nathanael\Desktop\UPD\*.txt (
echo file found >> %~dp0Outputfile.txt
chDIR C:\Users\Nathanael\Desktop\UPD\
dir *.txt /b >> %~dp0Outputfile.txt
FOR /F "tokens=*" %%G IN ('dir /b C:\Users\Nathanael\Desktop\UPD\*.txt') DO SET result=%%G
echo %result% >> %~dp0Outputfile.txt
)
The output is:
file found
NewVHD.txt
random.txt
ECHO is on.
If the for /f returns multiple files the last one will overwrite the previous in the set
The way cmd.exe parses (code blocks) requires delayed expansion when a var is set and used inside the same code block as is the case with your if exist
So either avoid the code block with reversed logic
IF NOT EXIST "C:\Users\Nathanael\Desktop\UPD\*.txt" Goto :Eof or other label
Or (always indent code blocks to better keep track) :
Setlocal EnableDelayedExpansion
IF EXIST "C:\Users\Nathanael\Desktop\UPD\*.txt" (
echo file found >> %~dp0Outputfile.txt
chDIR "C:\Users\Nathanael\Desktop\UPD\"
dir "*.txt" /b >> %~dp0Outputfile.txt
FOR /F "tokens=*" %%G IN ('dir /b C:\Users\Nathanael\Desktop\UPD\*.txt') DO SET result=%%G
echo(!result! >> %~dp0Outputfile.txt
)
It's a good habit to always enclose pathes in double quotes
to avoid echo is off messages use an other command separator than a space if the var is possibly empty (I used a ( here
FOR /F "tokens=*" %%G IN ('dir /b C:\Users\Desktop\UPD*.txt') DO SET result=%%G
Make sure that the path is correct (for example, perhaps it should be c:\Users\YourName\Desktop\UPD*.txt where YourName is the user name)?

Delayed expansion and exclamation marks in strings

Ok, so I'm still pretty new to batch scripting and I have this problem with my code that I'm using setlocal enabledelayedexpansion in my code for the for loop, the For loop goes through folder names in a specific directory, but the problem is that some of the names may include "!"(exclamation marks) and if that is the case they are not counted for in the "!filename!" and when the code creates a new directory it does not include the "!". Also when the xcopy tries to copy the files from the original folder, the folder is not found, because the variable "!filename!" is not the same as the original(does not have the exclamation point).
So I found that for this I need to only add "setlocal enable delayed expansion" to some parts of the code and turn it off at other, but I cant seem to find the right places.
The code:
#ECHO OFF
setlocal enabledelayedexpansion
SET Location_Folder=V:
SET Destination_folder=V:\Nonimportable
SET Check_file_validation=V:\Nonimportable\result.txt
SET Excluded_folder=Nonimportable
set "lineNr=12"
For /f "tokens=*" %%O in ('dir /b /a:d "%Location_Folder%"') do (
set filename=%%O
call D:\somefolder\otherfolder\batscriptname.bat !filename!
set Validation=
echo !filename!| FINDSTR /i /c:"%Excluded_folder%" >NUL
IF ERRORLEVEL 1 (
for /F "skip=12 delims=" %%a in (%Check_file_validation%) do if not defined Validation (
set Validation=%%a
call :valid
)
) else (
echo This folder name is excluded: !filename!
)
)
goto Finish
:valid
echo !Validation!| FINDSTR /c:"1" >NUL
if ERRORLEVEL 1 (
set Folder_path=%Location_Folder%\!filename!
set New_Folder_path=%Destination_folder%\!filename!
mkdir "!New_Folder_path!"
echo D | xcopy /o /y /q /s /v "!Folder_path!" "!New_Folder_path!"
rmdir /s /q "!Folder_path!"
) else (
echo Folder is valid !filename!
goto Finish
)
:Finish
exit /b
The Call part calls another small (~5lines) batch file that checks the sqlplus server if the "!filename!" is valid
EDIT: The whole code works fine and does what it should, unless there is a "!" in the name of some folder.
The problem is the parameter expansion in set filename=%%O.
In %%O is still the exclamation mark, but when delayed expansion is enabled, the bangs are dropped.
The conclusion is simple, delayed expansion have to be disabled when you expand a FOR parameter.
But when you also need delayed expansion?
You simply toggle the mode.
setlocal DisableDelayedExpansion
For /f "tokens=*" %%O in ('dir /b /a:d "%Location_Folder%"') do (
set "filename=%%O" -- Here the DelayedExpansion have to be disabled
setlocal EnableDelayedExpansion
call D:\somefolder\otherfolder\batscriptname.bat filename
set "Validation="
...
endlocal
)
See also my modification of the CALL myBat.bat filename instead of CALL myBat.bat !filename!.
You shouldn't use content with CALL, better use a variable by reference and in your function take the content by
set "_filename=!%1!"
It's because CALL itself has some nasty behaviour with spaces, carets, etc
If you use a variable within a code block (parenthesised series of commands) then %var% will yield the value of the variable when the block is originally encountered (ie parse-time value) and !var! the value of the variable as it changes during the block (ie "run-time" value).
If you call a procedure - internal or external, then the values of the variables that the procedure sees are the run-time values from the parent. If these values are changed within the called procedure then the same rules apply, and the changed values are returned to the parent procedure.
However if you invoke setlocal then any value-variation made is reverted to its original value if you execute an endlocal instruction or reach end-of-file within the context of the setlocal.
OK - so that's how delayedexpansion works.
In your case, there is no need for delayedexpansion at all. In the loop in the mainline (%%O) you can use %%O in place of !filename!. In the :valid procedure, you can move the two set commands outside of the code block and then there's no need at all to use !vars! since no access is required to variables whose values change within blocks.
#ECHO OFF
setlocal
SET Location_Folder=V:
SET Destination_folder=V:\Nonimportable
SET Check_file_validation=V:\Nonimportable\result.txt
SET Excluded_folder=Nonimportable
set "lineNr=12"
For /f "tokens=*" %%O in ('dir /b /a:d "%Location_Folder%"') do (
set filename=%%O
call D:\somefolder\otherfolder\batscriptname.bat %%O
set Validation=
echo %%O| FINDSTR /i /c:"%Excluded_folder%" >NUL
IF ERRORLEVEL 1 (
for /F "skip=12 delims=" %%a in (%Check_file_validation%) do if not defined Validation (
set Validation=%%a
call :valid
)
) else (
echo This folder name is excluded: %%O
)
)
goto Finish
:valid
set Folder_path=%Location_Folder%\%filename%
set New_Folder_path=%Destination_folder%\%filename%
echo %Validation%| FINDSTR /c:"1" >NUL
if ERRORLEVEL 1 (
mkdir "%New_Folder_path%"
echo D | xcopy /o /y /q /s /v "%Folder_path%" "%New_Folder_path%"
rmdir /s /q "%Folder_path%"
) else (
echo Folder is valid %filename%
rem redundant instruction : goto Finish
)
:Finish
exit /b

Search file with wildcard path

I want to write a script to prompt user for file path and list all files found. The file path can contain wildcards. Something similar to this. But the batch script version of it. For example:
C:\Somewhere\user*\app\version-*.*\start.exe
The files might be located like this:
C:\Somewhere\user345\app\version-1.0\start.exe
C:\Somewhere\user898\app\version-1.2\start.exe
C:\Somewhere\user898\app\version-1.3\start.exe
I tried to use FOR and it turns out to be so much harder than expected because FOR does not support wildcards in the middle of a path.
Is there a way to list these files? (Maybe without using for?)
I think this recursive solution works pretty well; you may name it WCDIR.bat:
#echo off
setlocal
if "%~1" neq "" set "next=%~1" & goto next
echo Show files selected by several wild-cards
echo/
echo WCDIR wildcardPath
echo/
echo Each folder in the path may contain wild-cards
echo the last part must be a file wild-card
goto :EOF
:next
for /F "tokens=1* delims=\" %%a in ("%next%") do set "this=%%a" & set "next=%%b"
if defined next (
for /D %%a in ("%this::=:\%") do (
setlocal
cd /D "%%~a" 2>NUL
if not errorlevel 1 call :next
endlocal
)
) else (
for /F "delims=" %%a in ('dir /B /A:-D "%this%" 2^>NUL') do echo %%~Fa
)
exit /B
EDIT: I fixed a small bug in the last for /F command.
For example, the output of WCDIR.bat C:\Windows\Sys*\find*.exe command in my Windows 8.1 64-bits computer is:
C:\Windows\System32\find.exe
C:\Windows\System32\findstr.exe
C:\Windows\SysWOW64\find.exe
C:\Windows\SysWOW64\findstr.exe
You can try with the command Where /?
The WHERE command is roughly equivalent to the UNIX 'which' command. By default, the search is done in the current directory and in the PATH.
#echo off
Where /R "%programfiles%" *winrar.exe
pause
#echo off
:: Example d'input
set UserInput=*drive*
:: building the Pattern
set cmd=%Userinput%.exe
:: storage Where.exe command in a macro, the execution will be faster
set whereCmd=where.exe /r c:\windows\ %cmd%
:: execution of macro and output formatting
for /f %%a in ('%whereCmd%') do echo %%~nxa --^> %%a
pause

Resources