Using %~dp0 on non-alphanumeric characters [duplicate] - batch-file

I usually get the current working directory by giving the batch command %~dp0 for combine multiple csv files.
But I encountered ampersand (&) symbol while getting current working directories which makes batch file broke after the '&' saying the path after & is not recognized as an internal or external command.
Can any of you guys please help me in modifying my below script in identifying & and replacing it with ^& (as this can escape the & and run the batch file). Any of your suggestions is appreciated;
Here is my code:
#echo off
ECHO Set working directory
pushd %~dp0 - How to escape & in this line???
ECHO Deleting existing combined file
del combined.csv
setlocal ENABLEDELAYEDEXPANSION
REM set count to 1
set cnt=1
REM for each file that matches *.csv
for %%i in (*.csv) do (
REM if count is 1 it's the first time running
if !cnt!==1 (
REM push the entire file complete with header into combined.csv - this will also create combined.csv
for /f "delims=" %%j in ('type "%%i"') do echo %%j >> combined.csv
REM otherwise, make sure we're not working with the combined file and
) else if %%i NEQ combined.csv (
REM push the file without the header into combined.csv
for /f "skip=1 delims=" %%j in ('type "%%i"') do echo %%j >> combined.csv
)
REM increment count by 1
set /a cnt+=1
)
cmd \k

Putting quotation marks around %~dp0 should be enough:
...
pushd "%~dp0"
...
Btw, I don't understand why you use pushd. You don't popd anywhere in your code so pushd seems useless. If I understand ECHO Set working directory correctly, you should replace pushd with CD %~dp0.

Related

Batch file escaping issues

I've written a batch file to modify the attribute of an XML file. The script works and the attribute if modified, however I'm having an issue with escaping some characters.
I've tried every solution I can possibly find online with no luck.
My intended output is:
<xs:import schemaLocation="2e9dd7db-f58b-4c91-8575-3b3af05d3178.xsd" namespace="urn:verastar:veracore:types" />
However I'm getting:
<xs:import schemaLocation="="2e9dd7db-f58b-4c91-8575-3b3af05d3178.xsd"" namespace="urn:verastar:veracore:types
I've tried escaping the quotes with ^, this works when using echo to the console however doesn;t work when writing to the file.
Why am I getting ="=" also I'm getting double quotes, but when I remove one nothing gets written to the file.. and finally the last quote seems to be messing things up and therefore the output is missing the XML closing tag.
How can I escape these characters properly?
My code is:
#echo on
setlocal EnableExtensions DisableDelayedExpansion
rem check if the XSD to modify exists in the batch directory
set "XSDFile=%~dp0test.xsd"
if not exist "%XSDFile%" goto EndBatch
rem environment variables
set "LineNumber="
set "LineCount=0"
set "TmpFile=%TEMP%\%~n0.tmp"
rem Search for the line containing attribute schemaLocation and get its
rem line number and the line itself loaded into environment variables
for /F "tokens=1* delims=:" %%I in ('%SystemRoot%\System32\findstr.exe /L /N /C:schemaLocation= "%XSDFile%" 2^>nul') do (
set "LineNumber=%%I"
set "FileLine=%%J"
)
rem If no line with attribute schemaLocation found, exit this batch file
if not defined LineNumber goto EndBatch
setlocal EnableDelayedExpansion
set "FileName=!FileLine:*schemaLocation=!"
for /f "tokens=1 delims=?" %%a in ("%FileName%") do (set test=%%a)
set "test=<xs:import schemaLocation="%test%.xsd"" namespace="urn:verastar:veracore:types" />"
pause
endlocal & set "FileLine=%test%
rem Make sure the temporary file used next does not already exist.
del "%TmpFile%" 2>nul
rem Copy all lines from XML file to a temporary file including empty
rem lines with the exception of the line containing attribute schemaLocation
rem which is copied to temporary file with the modified schemaLocation.
for /F "tokens=1* delims=:" %%I in ('%SystemRoot%\System32\findstr.exe /R /N "^" "%XSDFile%" 2^>nul') do (
set "XmlLine=%%J"
set /A LineCount+=1
setlocal EnableDelayedExpansion
if not !LineCount! == %LineNumber% (
echo/!XmlLine!>>"%TmpFile%"
) else (
echo/!FileLine!>>"%TmpFile%"
)
endlocal
)
rem Overwrite original file with temporary file automatically deleted on success.
move /Y "%TmpFile%" "%XSDFile%" >nul
:EndBatch
endlocal

Batch file to rename text files within directory with text from within text file at specific line except if line blank

#Echo off&SetLocal EnableExtensions EnableDelayedExpansion
cd "C:\Documents and Settings\John\Desktop\New\Interest\f2"
Pushd "C:\Documents and Settings\John\Desktop\New\Interest\f2"
Set Line#=26
Set /A LOfs=24 -1, Len=34 - LOfs
For %%A in (*.txt) do For /F "tokens=1* delims=:" %%B in (
'Findstr /N ".*" "%%A" ^|Findstr "^%Line#%:"'
) do if %errorlevel% == 0 Set "Line=%%C"&Ren "%%~fA" "!Line:~%LOfs%,%Len%! - %%A!""
Popd
In the above I am trying to change the filename of files in a directory with text in it at a certain position.
If line 26 is blank do nothing and do not change filename.
I have gone wrong somewhere and am going round in circles.
Can anyone help?
Thank you.
You don't state how your script fails, but I can see some potential problems. I also see possible simplifications.
You certainly don't need both CD and PUSHD
I got rid of the numeric variables and included the number literals in the actual code. You can revert back to variables if you want.
You don't need the outer FOR loop. FINDSTR can search multiple files when using wildcards in the file name, and then it will include the filename, followed by : in the output. So if you add the /N option, output will have the form filename:line#:text. You can then adjust the 2nd FINDSTR to return only the correct line numbers.
It is not enough to ignore blank lines. Your rename only works if there is at least one valid file name character after the 23rd character. Filenames cannot include :, *, ?, /, \, <, >, or |. (I may have missed some). I adjusted the FOR /F delims and the FINDSTR search to compensate.
FOR variable expansion like %%A will corrupt values if they contain ! and delayed expansion is enabled. ! is a valid character in file names. So the delayed expansion must be toggled on and off within the loop.
I believe the following will do what you want. The code below will simply echo the rename commands. Remove the ECHO before the ren once it gives the correct results.
#echo off
setlocal disableDelayedExpansion
pushd "C:\Documents and Settings\John\Desktop\New\Interest\f2"
for /f "tokens=1,3 delims=:*?\/<>|" %%A in (
'findstr /n "^" "*.txt" ^| findstr "^[^:]*:26:.......................[^:*?\\/<>|]"'
) do (
set "old=%%A"
set "line=%%B"
setlocal enableDelayedExpansion
ECHO ren "!old!" "!line:~23,11! - !old!"
endlocal
)
popd
An slightly different method to Daves:
#Echo Off
Set "SrcDir=%UserProfile%\Desktop\New\Interest\f2"
Set "Mask=*.txt"
Set "Line#=26"
Set "LOfs=23"
Set "Len=11"
If /I Not "%CD%"=="%SrcDir%" Pushd "%SrcDir%"2>Nul&&(Set _=T)||Exit/B
For /F "Tokens=1-2* Delims=:" %%A In ('FindStr/N "^" "%Mask%" 2^>Nul'
) Do If "%%B"=="%Line#%" If Not "%%~C"=="" (Set "Line=%%C"
SetLocal EnableDelayedExpansion
If Not "!Line:~%LOfs%,%Len%!"=="" (
If Not Exist "!Line:~%LOfs%,%Len%! - %%A" (
Ren "%%A" "!Line:~%LOfs%,%Len%! - %%A"))
EndLocal)
If "_"=="T" PopD
This method don't require findstr.exe nor toggle setlocal/endlocal, so it should run faster. Also, it avoids to re-process any already renamed file changing the plain for %%A by a for /F combined with dir command.
#Echo off
SetLocal EnableDelayedExpansion
cd "C:\Documents and Settings\John\Desktop\New\Interest\f2"
Set /A Line#=26, LOfs=24 -1, Len=34 - LOfs
For /F "delims=" %%A in ('dir /A-D /B *.txt') do (
rem Read the desired line from this file
(for /L %%i in (1,1,%Line#%) do set "Line=" & set /P "Line=") < "%%A"
if defined Line ECHO Ren "%%~fA" "!Line:~%LOfs%,%Len%! - %%A"
)
Note also that when this Batch file ends the current directory is automatically recovered to the current one when setlocal command was executed, so pushd/popd commands are not needed either.

Remove duplicate value in csv on combine

I encountered the below code online and modified this on my need.
I just wanna ask since i am new to batch file if there is a way to remove duplicate values after the combine.
#echo off
ECHO Set working directory
pushd %~dp0
ECHO Deleting existing combined file
del combined.csv
setlocal ENABLEDELAYEDEXPANSION
set cnt=1
for %%i in (*.csv) do (
if !cnt!==1 (
for /f "delims=" %%j in ('type "%%i"') do echo %%j >> combined.csv
) else if %%i NEQ combined.csv (
for /f "skip=1 delims=" %%j in ('type "%%i"') do echo %%j >> combined.csv
)
set /a cnt+=1
)
#ECHO OFF
SETLOCAL
ECHO Set working directory
pushd %~dp0
ECHO Deleting existing combined file
del combined.csv
set "flag="
for %%i in (*.csv) do if %%i NEQ combined.csv (
IF DEFINED flag (
findstr /l /x /v /g:combined.csv "%%i">#.vsc
TYPE #.vsc >>combined.csv
) ELSE (
COPY "%%i" combined.csv >nul
SET flag=y
)
)
DEL #.vsc /F /Q
POPD
GOTO :EOF
This may suit you better.
It uses a simple setlocal rather than the delayedexpansion version, initialising flag to empty then setting it within the loop and using if defined which works on the run-time value of flag.
First time through, it simply copies the detected source file to combined.csv and then sets flag to a value so it's ow defined
each other time through, findstr outputs those lines in the source file %%i that /v do not /x exactly match /l literally /g:filename any line in the combined.txt file TO a tempfile I'be nominated as #.vsc (name not important). Then that file is appended to combined.csv
Consequently, provided any particular .csv is free of duplicate lines within itself, the combined.csv will also be free of duplicate lines.
Since the header line is evidently identical in every file, the initial copy of the first file will place the header into combined.csv and hence findstr will neatly exclude it thereafter.
Revision to combat evil unicode:
#ECHO OFF
SETLOCAL
ECHO Set working directory
pushd %~dp0
ECHO Deleting existing combined file
del combined.csv
set "flag="
for %%i in (*.csv) do if %%i NEQ combined.csv (
(FOR /f "delims=" %%j IN ('type "%%i"') DO ECHO %%j)>#.vsc
IF DEFINED flag (
findstr /l /x /v /g:combined.csv "#.vsc" >##.vsc
TYPE ##.vsc>>combined.csv
) ELSE (
REN #.vsc combined.csv
SET flag=y
)
)
DEL #.vsc /F /Q
DEL ##.vsc /F /Q
POPD
GOTO :EOF
I suspect that the problem is using UNICODE within your files. Cutting-and-pasting your data showed that it was unicode.
The for /f... ceremony reads unicode and produces ASCII, hence this version first simply converts to ASCII using your familiar technique then operates on the converted file #.vsc. findstr does not appreciate outputting to the same file as it's attempting to read as /g:, so a further tempfile ##.vsc is used for the findstr output.
Note that the unicode characters between (header) Last modified and date and also elsewhere will be replaced by question-marks.

Batch script to pick filename from a text file and find the latest file

Scenario:
We have multiple releases of a product, and for each release, a folder is created in the main folder. A help file is modified in various releases. I have all the help file names listed in a text file.
I need a script to:
Take each file name from the filenames.txt file
Search for the file by that name in the entire directory (in all releases)
Find the latest file
Copy it to a specified folder
I took help from the various pieces of code I found on Stack Overflow, and combined them to get this code:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
echo.
FOR /F "usebackq delims=" %%a in ("filenames.txt") do (
SET "x=%%a"
ECHO '!x!'
SET FFPath=C:\SVN\nlbavwdocsvn\rep_doc_erpln\trunk\ERPLN
SET NewPath=C:\Lavanya\extracted
SET NewestDate=20160824
ECHO Recursively searching %FFPath%
FOR /F %%I in ('DIR %FFPath%\ !x! /a:-d /s /b') DO (
SET FullDate=%%~tI
ECHO %FFPath%
REM Set CurrDate to yyyymmdd format. Note: Will fail if regional settings changed.
SET CurrDate=!FullDate:~6,4!!FullDate:~0,2!!FullDate:~3,2!
If !CurrDate! gtr !NewestDate! (
SET NewestDate=!CurrDate!
SET NewestFile=%%~fI )
ECHO Copying %NewestFile% to %NewPath%
ECHO.
COPY /Y "%NewestFile%" "%NewPath%"
ECHO.
)
)
PAUSE
This code is not working. And I am unable to figure out the error.
Here is a script to search for the most recently modified file, using the wmic command to retrieve the last modification date/time in a locale-independent manner (e. g., 20160824115500.000000+060).
So for every file name read from the list file .\filenames.txt, the directory tree routed at directory C:\SVN\nlbavwdocsvn\rep_doc_erpln\trunk\ERPLN is searched for matching files recursively, and the respective modify date/time stamp is gathered. Due to its format, a simple greater-than (GTR) comparison can be done do determine whether or not it is a later point of time than a cached one; if the criterion is fulfilled, the cache is updated accordingly.
The upper-case REM and ECHO commands constitute placeholders only for the real action to be performed on the files. Extend the script there as you like. Variable !LASTFILE! holds the full path to each encountered file.
So here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "LOCATION=C:\SVN\nlbavwdocsvn\rep_doc_erpln\trunk\ERPLN"
set "FILELIST=.\filenames.txt"
set "WMICPROP=LastModified" & rem // {CreationDate | LastAccessed | LastModified}
pushd "%LOCATION%" || exit /B 1
for /F "usebackq eol=| delims=" %%L in ("%FILELIST%") do (
set "LASTFILE="
set "LASTFAGE=00000000000000.000000+000"
for /F "eol=| delims=" %%F in ('dir /B /S /A:-D "%%~L"') do (
set "FILE=%%F"
setlocal EnableDelayedExpansion
set "FILE=!FILE:\=\\!"
for /F "tokens=2 delims==" %%J in ('
2^> nul wmic DataFile WHERE ^(Name^="!FILE!"^) GET %WMICPROP% /VALUE ^|^| ^
2^> nul wmic DataFile WHERE Name^="!FILE!" GET %WMICPROP% /VALUE
') do for /F %%I in ("%%J") do (
endlocal
set "FAGE=%%I"
setlocal EnableDelayedExpansion
if !FAGE! GTR !LASTFAGE! (
endlocal
set "LASTFILE=%%F"
set "LASTFAGE=%%I"
setlocal EnableDelayedExpansion
)
)
endlocal
)
if defined LASTFILE (
setlocal EnableDelayedExpansion
REM Do whatever you want with the file here...
ECHO Newest file: "!LASTFILE!"
endlocal
)
)
popd
endlocal
exit /B

Renaming .txt files in bulk

I downloaded about 34000 books in .txt format from Project Gutenberg. Now I want to rename all of them by its content. For example every text file includes its "Title" and "Author's Name" so I want to rename all the text files on its "Title" and "Author's Name" by some commands.
I created a batch file. It runs but is not renaming the files. This is my code:
#echo off&setlocal
cd E:\Test
for /f "delims=" %%i in ('dir /a-d/b *.txt') do (
set "nname="
set "fname=%%~i"
for /f "usebackqskip=7delims=" %%f in ("%%~i") do if not defined nname
set "nname=%%f"
setlocal enabledelayedexpansion
set "nname=!nname:~0,40!"
echo rename "!fname!" "!nname!"
endlocal
)
You can use this as a base
#echo off
setlocal enableextensions disabledelayedexpansion
rem Change to source folder
pushd "e:\test" && (
rem Where the renamed files will be placed to avoid re-rename
if not exist renamed\ md renamed
rem For each input file
for %%f in (*.txt) do (
rem Retrieve the data from inside the file
set "author=" & set "title="
for /f "tokens=1,* delims=: " %%a in ('
findstr /b "Author: Title:" "%%~ff"
') do if not defined %%a set "%%a=%%b"
rem If the fields have been retrieved then do the rename
if defined author if defined title (
setlocal enabledelayedexpansion
for /f "delims=" %%a in ("!author! - !title!") do (
endlocal
echo move "%%~ff" "renamed\%%a%%~xf"
rem NOTE: operation is only echoed to console
rem if console output seems correct, then
rem remove the echo command
)
)
)
rem Done. Return to previous active directory
popd
)
Of course, filesystem has rules about what is allowed in a file name and, not knowing what kind of characters can be found, this code could and probably will fail to rename some files.
Your current script will just print the rename commands, not execute them. You should remove echo (after checking what it produces) in this line:
echo rename "!fname!" "!nname!"
Your script also has a few formatting issues. There should be spaces like this:
for /f "usebackq skip=7 delims=" %%f in ("%%~i") do
And there should be no newline just after:
if not defined nname

Resources