I am trying to create a batch file using PDFtk to burst combine file in a certain directory to the output folder using the source file name (which can vary) as the input file name. for example
source directory :- D:\Temp\IN
destination directory :- D:\Temp\OUT
File name :- abcdefgh.pdf (which can vary)
desired output file name :- abcdefgh-001.pdf, abcdefgh-002.pdf and so on
My batch file will reside in D:\Script
The PDFtk.exe is in D:\PDFtk Server\bin
I tried for 1 whole day I can't get the input filename for the output.
Can anyone help
My existing program :-
CD D:\Temp\IN
for /f "tokens=*" %%A in ('dir /b D:\Temp\IN\*.pdf') do (
set prefix=%%~ni )
set outname=%prefix%-%%03d-00.pdf
path D:\PDFtk Server\bin
pdftk.exe D:\Temp\IN\%prefix% burst output D:\Temp\OUT\%outname%
exit
There are multiple errors in your approach the first one is that in a case like this you need local delayed expansion. For an initial % later use it as !.
CD D:\Temp\IN
This is ok but would be better if it allowed for /Drive shifting to "quoted drive folder" (beware input folder must NOT in this case be same as output folder with this approach, it is best if output is a sibling as you have done or use a common parent is better.)
CD /D "D:\Temp\IN"
for /f "tokens=*" %%A in ('dir /b D:\Temp\IN\*.pdf') do (
set prefix=%%~ni )
You have mixed up %%A and %%i they should be the same, tokens is not needed in this case but usebackq is for odd filenames and add quotes, don't need current directory, and ) is way too early.
setlocal enabledelayedexpansion
for /f "usebackq delims==" %%A in (`dir /b *.pdf`) do (
set "prefix=%%~nA"
next part
set outname=%prefix%-%%03d-00.pdf
again best quoted and needs using expansion
set "outname=!prefix!-%%03d-00.pdf"
path D:\PDFtk Server\bin
pdftk.exe D:\Temp\IN\%prefix% burst output D:\Temp\OUT\%outname%
better if combined and quoted you don't need to use Current Directory
"D:\PDFtk Server\bin\pdftk.exe" "!prefix!.pdf" burst output "D:\Temp\OUT\!outname!"
the terminal bracket goes here
)
exit
is superfluous
so all in all
CD /D "D:\Temp\IN"
setlocal enabledelayedexpansion
for /f "usebackq delims==" %%A in (`dir /b *.pdf`) do (
set "prefix=%%~nA"
set "outname=!prefix!-%%03d-00.pdf"
"D:\PDFtk Server\bin\pdftk.exe" "!prefix!.pdf" burst output "D:\Temp\OUT\!outname!"
)
The whole command could be simplified to ONE line however to show where cuts can be made
try this
CD /D "D:\Temp"
for /f "usebackq delims==" %%A in (`dir /b IN\*.pdf`) do (
"D:\PDFtk Server\bin\pdftk.exe" "IN\%%~A" burst output "OUT\%%~nA-%%03d-00.pdf"
)
the need for usebackq delims== is because of later problems with characters such as filename (1).pdf
so we cannot shorten much more than
CD /D "D:\Temp" & for /f "usebackq delims==" %%A in (`dir /b IN\*.pdf`) do ("D:\PDFtk Server\bin\pdftk.exe" "IN\%%~A" burst output "OUT\%%~nA-%%03d-00.pdf")
Related
I have a CI system that regularly publishes new files.
In a script, I want to take the latest version of several (separately published) files. The general file structure is the following: C:\Path\to\publish\<token>\<flavor>\file. There are 3 variable parts in this, per published file. (And all 3 can in some cases have wildcards in it)
The setup I currently have, makes use of delayed expansion to make this configurable:
set A_PATH=C:\Path\to\publish\*
set A_SUBDIR=<flavor>
set A_NAME=file_*.txt
call :searchFile A
goto :logicUsingA
:searchFile
SETLOCAL ENABLEDELAYEDEXPANSION
set SEARCH_PATH=%~1_PATH
set SEARCH_NAME=%~1_NAME
set SEARCH_SUBDIR=%~1_SUBDIR
FOR /D %%I IN ('DIR !%SEARCH_PATH%! /O:-D /B') do (
echo I:%%I
FOR %%J IN (!%SEARCH_SUBDIR%!) do (
echo J:%%J
FOR /F "delims=" %%K IN ('DIR %%I\%%J\!%SEARCH_NAME%! /A:-D /O:-D /B') do (
echo K:%%K
ENDLOCAL
set "%~1=%%I\%%J\%%K"
GOTO :EOF
)
)
) 2> nul
This system almost did what it needed to do, namely, based on a configuration pick some files and fill a variable with them.
Unfortunately, we discovered that something goes wrong with executing this script.
The FOR /D %%I IN ('DIR !%SEARCH_PATH%! /O:-D /B') should sort the directories with the newest first. However, the print below it, prints: (assume 001 was created before 002 ...)
I:'DIR
I:C:\Path\to\publish\001
I:C:\Path\to\publish\002
I:C:\Path\to\publish\003
This makes me suspect that the delayed expansion doesn't play nice with the for-loop, as this is the only noticeable difference with other solutions I find at stackoverflow, like https://stackoverflow.com/a/45104510/2466431
Would it be possible to instruct the for-loop to execute this dir-command iso treating it as a list of items?
EDIT: What the code is trying to achieve is a windows batch variant of the bash one-liner: ls -t C/Path/to/publish/*/<flavor>/file_*.txt
The loop for /D%%I in ('dir !%SEARCH_PATH%! /O:-D /B') do ( ... ) is wrong as you must use for /F to capture and parse the output of a command like dir in our situation. So you may either use for /D %%I in ("!%SEARCH_PATH%!") do ( ... ) or for /F "delims=" %%I in ('dir "!%SEARCH_PATH%!" /O:-D /B') do ( ... ) to be syntactically correct.
But neither will probably be useful for you, because:
for /D does not access the file system unless wildcards are present;
for /D does not provide sort options (it returns directories as it gets them from the file system);
dir, when a directory name is given, lists its contents rather than just returning its name;
Nevertheless, there is a way to work around the said problem with dir, namely appending \ and trying to list the contents of the given directory; if wildcards are given, dir returns an error; if a dedicated directory name is given but it does not exist, dir returns an error too; conditional execution can be applied to make use of that behaviour:
dir /B /A:D "%SomeDir%\" && (echo "%SomeDir%") || dir /B /A:D /O:-D /T:C "SomeDir%"
This provides the following results:
if %SomeDir% points to a dedicated existing directory, the first dir command lists its contents, so it succeeds, hence the echo command is executed, returning the (quoted) variable contents, but the second dir command is skipped; (quotation of the echo string is done here in order to protect whitespaces or other special characters; the surrounding quotation marks "" are no problem though as they can easily be removed later anyway;)
if %SomeDir% points to a dedicated directory that cannot be found, the first dir command fails (error: The system cannot find the file specified.), hence the echo command is skipped, but the second dir command is executed, which also fails (error: File Not Found);
if %SomeDir% contains a wildcard in the last path element, the first dir command fails (error: The filename, directory name, or volume label syntax is incorrect.), hence the echo command is skipped, but the second dir command is executed, which lists all matching directory names, sorted by creation date/time (newest first);
The listing returned by the first dir command as well as any error messages can be suppressed using redirection, so the remaining output is the one by echo or by the second dir:
> nul 2>&1 dir /B /A:D "%SomeDir%\" && (echo "%SomeDir%") || 2> nul dir /B /A:D /O:-D /T:C "SomeDir%"
This can now be applied to your script:
#echo off
set "A_PATH=C:\Path\to\publish\*"
set "A_SUBDIR=<flavor>"
set "A_NAME=file_*.txt"
call :searchFile A
echo A:%A%
rem goto :logicUsingA
goto :EOF
:searchFile
set "%~1="
setlocal EnableDelayedExpansion
set "SEARCH_PATH=%~1_PATH"
set "SEARCH_NAME=%~1_NAME"
set "SEARCH_SUBDIR=%~1_SUBDIR"
cd /D "!%SEARCH_PATH%!\.." || exit /B 1
for /F "delims=" %%I in ('
^> nul 2^>^&1 dir /B /A:D "!%SEARCH_PATH%!\" ^
^&^& ^(echo "!%SEARCH_PATH%!"^) ^
^|^| 2^> nul dir /B /A:D /O:-D /T:C "!%SEARCH_PATH%!"
') do (
echo I:%%~nxI
for /F "delims=" %%J in ('
^> nul 2^>^&1 dir /B /A:D "%%~nxI\!%SEARCH_SUBDIR%!\" ^
^&^& ^(echo "!%SEARCH_SUBDIR%!"^) ^
^|^| 2^> nul dir /B /A:D /O:-D /T:C "%%~nxI\!%SEARCH_SUBDIR%!"
') do (
echo J:%%~J
for /F "delims=" %%K in ('
^> nul 2^>^&1 dir /B /A:-D "%%~nxI\%%~J\!%SEARCH_NAME%!\" ^
^|^| 2^> nul dir /B /A:-D /O:-D /T:C "%%~nxI\%%~J\!%SEARCH_NAME%!"
') do (
echo K:%%K
endlocal
set "%~1=%CD%\%%~nxI\%%~J\%%K"
goto :EOF
)
)
)
goto :EOF
In the inner-most loop that enumerates files rather than directories, I applied the same technique, but I skipped the echo command as it should never be executed anyway; the only reason why I kept the two dir commands here is to handle the situation when you specify a certain file name (no wildcards) but there is actually a directory with that name (a single dir would then unintentionally return its contents).
There have also a few other things changed:
the variable %~1 is initially cleared (in the sub-routine :searchFile, just in case no file could be found at all);
the quoted set syntax is consistently used (set "VAR=Value");
all paths are now quoted to avoit trouble with whitespaces or other special characters;
the dir option /T:C is added to regard the creation date/time rather than the date/time of the last modification; just remove it if you want to regard the latter;
cd /D"!%SEARCH_PATH%!\.." ||exit/B 1 has been added to change to the parent directory, because the later used dir /B commands just return directory or file names but not full paths; that is also the reason why the ~nx-modifier is used in %%~nxI, so there is no more difference whether the value comes from dir /B or echo; the exit /B 1 part makes sure to skip the remaining code in case the parent directory could not be found/accessed;
~-modifiers are used in %%~J to ensure unquoted directory names (remember that I put quotes in the echo command lines);
2> nul has been removed (from the end of the body of the outer-most loop) to not suppress error messages in general;
I have some files that I would like to sort through and keep the newest file.
I cannot do it by file attributes date modified or created, which I could do no problem.
Here is the naming convention of the files. FileABC_YYYYMMDD.txt
FileABC_20190201.txt
FileABC_20190125.txt
FileABC_20190118.txt
FileABC_20190111.txt
FileABC_20190104.txt
You can see that the date stamp is in the filename itself. These files are generated weekly. So I'd like to have a batch file loop through them and delete all but most currently dated file. I have really searched for how to do this best and I'm not finding much so I need ideas. I prefer a pure cmd solution but I'm open to powershell solutions as well.
What I am trying on my own is to parse out the date with...
#echo off
setlocal enabledelayedexpansion
for /f "tokens=* delims= " %%G IN ('dir/b /a-d "C:\Users\thomas.maus\Documents\Tom\dev\Test Batch Files\dev\sortbyFileDateName\FileABC_*.txt"') do (
set fileName=%%G
Set theDate=!fileName:~8,8!
echo !theDate!
)
Then I want to take those dates somehow from the results of the loop and do something like
if "%theDate%" GEQ "*****not sure what to put here*****" (
del *all the old files, also not sure what to put here*
)
How about this?
#echo off
for /f "skip=1" %%i in ('dir /o-n /b *.txt') do del %%i
If you just want to test it (see what it would delete) first, do:
#echo off
for /f "skip=1" %%i in ('dir /o-n /b *.txt') do echo %%i
If you do not care about the file dates but only the dates in the file names, you could do the following, given that the part FileABC is always the same and does not contain any _ on its own:
pushd "C:\Users\thomas.maus\Documents\Tom\dev\Test Batch Files\dev\sortbyFileDateName" && (
for /F "skip=1 delims= eol=|" %%F in ('
dir /B /A:-D "FileABC_????????.txt" ^
^| sort /R
') do (
del "%%F"
)
popd
)
Although sort /R does alphabetic sorting, this works because of your chosen date format, which ensures that alphabetic order equals alphanumeric one.
We just loop through the files, sorted by date in decending order, then skip the first file, now being the latest:
#for /f "skip=1" %%a in ('dir /b /o-d *.txt') do #echo #del %%a
Important!
This example will only echo the delete command as a safe measure so you do not delete files you should not have. To perform the actual delete, remove #echo from the line.
To understand more about the functions we used, run the following from cmd.exe
for /?
dir /?
As an additional option, just in case the filename prefix changes throughout and only the _YYYYMMDD.txt remains constant, you can still peform the task using that date as it is already alphabetically sortable.
Here's an example:
#Echo Off
Set "SrcDir=%UserProfile%\Documents\Tom\dev\Test Batch Files\dev\sortbyFileDateName"
For /F "Delims==" %%A In ('Set $ 2^>Nul') Do Set "%%A="
Set "_="
For /F Delims^=^ EOL^= %%A In ('Where "%SrcDir%":*_????????.txt 2^>Nul'
) Do Set "_=%%~nA" & Call Set "$%%_:~-8%%=%%A"
If Not Defined _ Exit /B
For /F "Tokens=1* Delims==" %%A In ('Set $ 2^>Nul') Do Set "_=%%B"
For /F Delims^=^ EOL^= %%A In ('Where "%SrcDir%":*_????????.txt^|Find /V "%_%"'
) Do Del /A /F "%%A"
This uses the fact that the Set command will output variables in alphabetical order.
Lines 2 to 4 just define and undefine the variables we will be using.
Lines 5 and 6 is a single line split over two lines for readability. This will set variables using the last eight characters of the files basenames, to the value of the full filename.
Line 7 is included to exit the script, just in case no .txt files with a basename ending with an underscore followed by eight characters were found in the directory set at line 2.
Line 8 is the special one here, it outputs each variable and corresponding value in alphabetical order. The output is set to a variable, which overwrites itself until the loop ends. This means that the newest file, last one alphabetically, is held with the value of the file named with the newest date.
Lines 9 & 10 are once again a single line split over two for readability. This loops over all matching files in the directory again and uses the Find command to exclude outputting the one which matches that held in the variable as the file with the newest date. Each file output is simply deleted using the Del command.
Please note that this script assumes you only have a single file with each date, as you've only stated that the files are generated weekly.
I want to create a bat file that compresses all the folders inside a specific directory and then deletes them.
For example,
Folder structure (before running bat file):
VidoesArch
January
February
March
....
TextualArch
January
February
March
....
Folder structure (after running bat file):
VideosArch
January.zip
February.zip
March.zip
....
TextualArch
January.zip
February.zip
March.zip
....
This what I have developed so far:
#echo off
setlocal
set INPBKP=C:\Data\Backup\VideosArch
set OUTBKP=C:\Data\Backup\TextualArch
set WZLOC=C:\PROGRA~2\WINZIP
:: Caution: Use a drive where sufficient disk space is available for this setting.
set WORKDIR=K:\
:: DO NOT CHANGE ANYTHING BEYOND THIS POINT
set COMPCMD="%WZLOC%\WZZIP.EXE" -m -ex -b%WORKDIR% -a
for /F "USEBACKQ DELIMS==" %%t in (`dir %INPBKP%\*.dat /a/s/b`) do #%COMPCMD% %%t.zip %%t
for /F "USEBACKQ DELIMS==" %%t in (`dir %OUTBKP%\*.dat /a/s/b`) do #%COMPCMD% %%t.zip %%t
endlocal
What this file does is, it zip all the .dat files in the folders. What I want to achieve is it shall zip all the folders.
Update:
I have changed the file content and now it looks like this. Its not working. If I try to run on command prompt it says- 'The system cannot find the file specified'.
#echo off
setlocal
set INPBKP=C:\Data\Backup\VideosArch
set OUTBKP=C:\Data\Backup\TextualArch
set WZLOC=C:\PROGRA~2\WINZIP
:: Caution: Use a drive where sufficient disk space is available for this setting.
set WORKDIR=C:\
:: DO NOT CHANGE ANYTHING BEYOND THIS POINT
set COMPCMD="%WZLOC%\WZZIP64.EXE" -r -p -m -ex -b%WORKDIR%
for %%s in ("%INPBKP%" "%OUTBKP%") do for /F "DELIMS==" %%t in ('dir %%s /ad /b') do %COMPCMD% %%~nt.zip "%%~s\%%t"
endlocal
set COMPCMD="%WZLOC%\WZZIP.EXE" -r -p -m -ex -b%WORKDIR%
for /F "DELIMS==" %%t in ('dir %INPBKP% /ad /b') do %COMPCMD% "%INPBKP%\%%~nt.zip" %INPBKP%\%%t
Adding -r -p to the compress command should gather and retain all the files and directories.
By executing dir /b /ad, %%t will be assigned the directorynames from the target directory. %%~nt selects simply the name part. The usebackq is not required if single quotes are used.
You could possibly also use
for %%s in ("%INPBKP%" "%OUTBKP%") do for /F "DELIMS=" %%t in ('dir /ad /b %%s') do %COMPCMD% "%%~s\%%~nt.zip" "%%~s\%%t"
%%s containing the quoted directoryname and %%~s unquoted (in case of separators in directorynames)
I haven't tested this, so I'd suggest you try it on some disposible test directories first.
[edit - to insert the destination directory for the zip file]
[also changed delims== to delims= (cosmetic - change = as delimiter to no delimiter)]
Windows, Command Prompt, need to generate a .txt file output containing of all files from a big and complex dir tree with one (1) line for each files as:
CreationDateYYYYMMDD-HHMMSS, LastModifiedYYYYMMDD-HHMMSS, filesize[no K commas], filename.ext
for example:
20100101-174503, 20120202-191536, 1589567, myfile.ext
The list should not contain lines of dir name entries, etc., only filenames, even if the same file is present in more than once. Time in 24 hours format.
dir /s/t:c/t:w/-c > filelist.txt
command does not exactly works this way.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=c:\program files"
FOR /f "delims=" %%a IN (
'dir /s /b /a-d "%sourcedir%\*" '
) DO (
FOR %%d IN (timewritten timecreated) DO SET "%%d="
FOR %%k IN (-d s h) DO (
IF NOT DEFINED timewritten FOR /f "tokens=1,2 delims= " %%d IN ('dir /tw %%~k "%%a" 2^>nul ^|find "%%~nxa"') DO SET "timewritten=%%d %%e"
IF NOT DEFINED timecreated FOR /f "tokens=1,2 delims= " %%d IN ('dir /tc %%~k "%%a" 2^>nul ^|find "%%~nxa"') DO SET "timecreated=%%d %%e"
)
ECHO !timecreated! !timewritten! %%~za %%~nxa
)
)
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
Interesting problem. This code processes it by
First, applying the standard directory-list for filenames on the tree from the relative root (%sourcedir%) to %%a
Using the full filename in %%a, set timewritten and timecreated from an ordinary dir list targeting the file in question.
It appeared that %%~ta didn't play nicely to extract the timestamp for hidden and system files, so I decided to build them from the ordinary dir listing with the appropriate t setting, specifically listing with /a-d, /as and /ah and filtering for the line which matched the filename, which seemed to extract the data appropriately.
I left the date/time in raw format. It should be an easy task to extract the various elements and construct the report in the format you want.
This question is a dupe of the SO post cmd dir /b/s plus date, but posting what worked for me:
#echo off
REM list files with timestamp
REM Filename first then timestamp
for /R %I in (*.*) do #echo %~dpnxI %~tI
#echo off
REM list files with timestamp
REM Timestamp first then name
for /R %I in (*.*) do #echo %~tI %~dpnxI
The above are the versions that you would directly paste into a command prompt.
If you want to use these in a batch file and log the output, you could do something like:
rem: Place the following in a batch file such as DirectoriesBareWithTS.cmd.
rem: As the first step in the batch file, net use to the directory or share you want the listing of
rem: Change to your target directory
Y:
for /R %%I in (*.mp4) do #echo %%~tI %%~dpnxI
Then you can pipe the output to a log file when you execute:
DirectoriesBareWithTS.cmd > C:\temp\GiantLongDirListing.log
You could then import that log into Excel.
I have a Directory with a deep Directory->Sub-directory tree structure. I need to write a batch file to copy all the numbered files (files with names as digits and no alphabetic characters) from all the sub-directories.
For example, a sub-directory might contain the following files:
WR10091.txt
AX10091.htm
10091.txt
AX10091.xml
10091.xml
I need to copy 10091.txt and 10091.xml to another location. I can copy files like AX10091.xml and AX10091.htm by specifying AX*.*. But I cannot figure out how to copy just numbered files with no alphabetic characters. There are thousands of directories and the directory structure does not have any pattern (the depth of a tree branch can vary considerably).
Any help will be appreciated.
#echo off
setlocal enableextensions disabledelayedexpansion
set "source=%cd%"
set "target=x:\target\folder"
for /r "%source%" %%a in (*) do (
(for /f "delims=0123456789" %%b in ("%%~na") do (
break
)) || echo copy "%%~fa" "%target%"
)
In this code the for %%a will iterate over all the files under the indicated folder. For each of them, the for /f %%b will try to tokenize the file name (%%~na) using numbers as delimiters. If the file name only contains numbers, there will be nothing to process (only delimiters) and the inner for raises errorlevel. This is checked with conditional execution (the code after the || is executed if the previous command fails) and if errorlevel was raised the copy operation is echoed to console.
If the output is correct, remove the echo to perform the copy.
note: the break in the inner for loop is included just to have a command that does nothing when files with non numeric names are found.
#echo off
for /f "tokens=* delims=" %%a in ('dir /b /s /a:-d "*"') do (
echo %%~na|findstr /e /b /r "[1-9]*" >nul 2>nul && (
copy %%~fa c:\somewhere\
)
)
should be executed in the same directory as the files.
for /f "delims=" %%a in ('dir /b/s/a-d ^| findstr /reic:"\\[0-9][0-9]*\..*" /c:"\\[0-9][0-9]*"') do copy "%%~a" "targetDir"
This might not work with XP and/or Vista, but this can be fixed if needed (see What are the undocumented features and limitations of the Windows FINDSTR command).