I have a number of folders in Windows 10, each of which contains a number of PDF files. For each folder I need to run GhostScript with the folder's PDF files as input but with a certain file as the first one.
Each folder contains a file named, say, "FirstFile-X.pdf", where X can be anything, and for each folder I need that file to be the first input.
I have the following in a batch file:
setlocal enableDelayedExpansion
set gs="C:\Program Files\gs\gs9.54.0\bin\gswin64.exe"
set options=-dNOPAUSE -q -dBATCH -sDEVICE=pdfwrite
%gs% -sDEFAULTPAPERSIZE=a4 -dBATCH
for /d %%d in (*) do (
set a=
set output=%%d.pdf
for %%f in (%%d\*.pdf) do (
set "a=!a!%%d^\%%~nxf "
)
%gs% %options% -sOutputFile=!output! !a!
)
The above code works but it doesn't take that specific file as the first input. Is it possible to have the innermost for-loop run through each file in the order that I need?
The answer given by #aschipfl inspired me to do a different solution:
#echo off
setlocal enableDelayedExpansion
set "gs=C:\Program Files\gs\gs9.54.0\bin\gswin64.exe"
set "options=-dNOPAUSE -q -dBATCH -sDEVICE=pdfwrite"
"%gs%" -sDEFAULTPAPERSIZE=a4 -dBATCH
for /d %%d in (*) do (
set a=
for %%f in (%%d\*.pdf) do (
set string=%%~nf
if "!string:~0,5!"=="First" (
set "a=%%f !a!"
) else (
set "a=!a!%%f "
)
)
"%gs%" %options% -sOutputFile=%%d.pdf !a!
)
endlocal
I simply add the filename to the beginning of the string a, if the filename starts with "First", and if not the filename is added to the end of the string a. I also implemented some of the other small changes that #aschipfl suggested.
You could use an extra for loop that just iterates over the first file matching the pattern FirstFile-*.pdf (where only one match is expected). This file could be excluded in the other already present for loop. See the explanatory rem comments in the code:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem // Use quoted `set` syntax to assign unquoted values but still protect special characters:
set "gs=C:\Program Files\gs\gs9.54.0\bin\gswin64.exe"
set "options=-dNOPAUSE -q -dBATCH -sDEVICE=pdfwrite"
rem // Use quotation during expansion of variables:
"%gs%" -sDEFAULTPAPERSIZE=a4 -dBATCH
for /D %%d in (*) do (
set "a="
rem // Let an extra loop find the first file:
for %%e in ("%%d\FirstFile-*.pdf") do (
rem /* This condition is just necessary in case more than one files are found
rem by the extra loop in order to avoid duplicates in the returned list: */
if not defined a (
rem // Append the first file:
set "a=!a!%%~e "
rem // Iterate over all files (including first file):
for %%f in ("%%d\*.pdf") do (
rem // Exclude already processed first file at this point:
if /I not "%%~NXf"=="%%~NXe" set "a=!a!%%~f "
)
)
)
rem // There is no variable `output` needed:
"%gs%" %options% -sOutputFile=%%d !a!
)
endlocal
exit /B
Moreover, I made some other minor improvements, which are also commented in the code.
Note, that this code will still have troubles with directory and PDF file paths containing spaces and with such containing the characters ! and ^. To overcome them, you will need further quotation and toggling of delayed expansion:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Use quoted `set` syntax to assign unquoted values but still protect special characters:
set "gs=C:\Program Files\gs\gs9.54.0\bin\gswin64.exe"
set "options=-dNOPAUSE -q -dBATCH -sDEVICE=pdfwrite"
rem // Use quotation during expansion of variables:
"%gs%" -sDEFAULTPAPERSIZE=a4 -dBATCH
for /D %%d in (*) do (
set "a="
set "output=%%d"
rem // Let an extra loop find the first file:
for %%e in ("%%d\FirstFile-*.pdf") do (
rem // Store currently iterated item:
set "item=%%~e"
rem /* This condition is just necessary in case more than one files are found
rem by the extra loop in order to avoid duplicates in the returned list: */
if not defined a (
rem // Toggle delayed expansion to avoid issues with `!` and `^`:
setlocal EnableDelayedExpansion
rem // Append the first file in a quoted manner:
set "a=!a!"!item!" "
rem // Transfer value `a` over `endlocal` barrier:
for /F "delims=" %%t in ("a=!a!") do endlocal & set "%%t"
rem // Iterate over all files (including first file):
for %%f in ("%%d\*.pdf") do (
rem // Store currently iterated item:
set "item=%%~f"
rem // Exclude already processed first file at this point:
if /I not "%%~NXf"=="%%~NXe" (
rem // Toggle delayed expansion to avoid issues with `!` and `^`:
setlocal EnableDelayedExpansion
rem // Append the current item in a quoted manner:
set "a=!a!"!item!" "
rem // Transfer value `a` over `endlocal` barrier:
for /F "delims=" %%t in ("a=!a!") do endlocal & set "%%t"
)
)
)
)
rem // Eventually use delayed expansion as well as quotation:
setlocal EnableDelayedExpansion
"!gs!" !options! -sOutputFile="!output!" !a!
endlocal
)
endlocal
exit /B
Related
I have the following which pares down a folder name (FOLDER) to only all text preceding the first space (tmpFOLDER).
#echo off
setlocal EnableExtensions DisableDelayedExpansion
pushd "%~dp0" || exit /B
for %%I in (..) do set "FOLDER=%%~nxI"
for /f "tokens=1 delims= " %%a in ("%FOLDER%") do set tmpFOLDER=%%a
ECHO %FOLDER%
ECHO %tmpFOLDER%
popd
endlocal
PRIMARY REQUEST:
Is there a way to do this in reverse?
Folder name (%FOLDER%): Smith - John
Example current (%tmpFOLDER%): Smith
Example desired (%tmpFOLDER%): John
SECONDARY:
Is there a way to do this with files, as well, disregarding any filetypes (ie. .txt)?
File name (%FILE%): "Smith - John.txt"
Example current (%tmpFILE%): Smith
Example desired (%tmpFILE%): John
The for /F loop cannot extract tokens counted from the end of a string. However, you could use a standard for loop that walks through the words of the file or directory names:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Retrieve base name of grand-parent directory of this script:
for /D %%I in ("%~dp0..") do set "FOLDER=%%~nI"
echo Old name: "%FOLDER%"
set "PREV=" & set "COLL="
rem /* Unnecessary loop iterating once and returning the whole directory name;
rem it is just here to demonstrate how to handle also more than one names: */
for /F "delims= eol=|" %%L in ("%FOLDER%") do (
rem // Store current name strng and reset some interim variables:
set "NAME=%%L" & set "PREV=" & set "COLL= "
rem // Toggle delayed expansion to avoid issues with `!` and `^`:
setlocal EnableDelayedExpansion
rem // Ensure to have each space-separated word quoted, then loop through them:
for %%K in ("!NAME: =" "!") do (
rem /* Build new buffer by concatenating word from previous loop iteration,
rem then transfer it over `endlocal` barrier (localised environment): */
for %%J in ("!COLL! !PREV!") do (
rem // Store current word in an unquoted manner:
endlocal & set "ITEM=%%~K"
rem // Store current buffer, store current word for next iteration:
set "COLL=%%~J" & set "PREV=%%~K"
setlocal EnableDelayedExpansion
)
)
endlocal
)
rem // Retrieve final result:
set "RESULT=%COLL:~3%"
echo New name: "%RESULT%"
echo Last word: "%PREV%"
endlocal
exit /B
This approach returns the name with the last word removed as well as the last word of the name.
An alternative solution is the sometimes so-called code injection technique, which requires delayed variable expansion and is quite hard to understand:
setlocal EnableDelayedExpansion
echo Old name: "%FOLDER%"
set "RESULT=%FOLDER: =" & set "RESULT=!RESULT!!ITEM!" & set "ITEM= %"
echo New name: "%RESULT%"
echo Last word: "%ITEM:* =%"
endlocal
Note, that this will fail when the input string contains !, ^ or " (but the latter cannot occur in file or directory names anyway).
Yet another method is to replace spaces by \ and then to (mis-)use the ~-modifiers, which works because a pure file or directory name cannot contain \ on its own:
echo Old name: "%FOLDER%"
rem // Precede `\` to make pseudo-path relative to root, then replace each ` ` by `\`:
for %%I in ("\%FOLDER: =\%") do (
rem // Let `for` meta-variable expansion do the job:
set "RESULT=%%~pI"
set "LAST=%%~nxI"
)
rem // Remove leading and trailing `\`; then revert replacement of ` ` by `\`:
set "RESULT=%RESULT:~1,-1%"
echo New name: "%RESULT:\= %"
echo Last word: "%LAST%"
This approach does not even require delayed expansion.
EDIT: After great help from #aschipfl, the code is %110 as functional as I wanted it to be! I did some extra research and made it easy to use with prompts for that extra %10 :P
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Create a prompt to set the variables
set /p _FILETYPE="What file type: "
set /p _LINENUM="Which line: "
set /p _NEWLINE="Make line say: "
rem // Start the loop, and set the files
for %%f in (*%_FILETYPE%) do (
set "_FILE=%%f"
echo "_FILE=%%f"
rem // To execute seperate code before the end of the loop, starting at ":subroutine".
call :subroutine "%%f"
)
:subroutine
rem // Write to a temporary file:
> "%_FILE%.new" (
rem /* Loop through each line of the original file,
rem preceded by the line number and a colon `:`:*/
for /F "delims=" %%A in ('findstr /N "^" "%_FILE%"') do (
rem // Store the current line with prefix to a variable:
set "LN=%%A"
rem /* Store the line number into another variable;
rem everything up to the first non-numeric char. is regarded,
rem which is the aforementioned colon `:` in this situation: */
set /A "NUM=LN"
rem // Toggle delayed expansion to avoid trouble with `!`:
setlocal EnableDelayedExpansion
rem /* Compare current line number with predefined one and replace text
rem in case of equality, or return original text otherwise: */
if !NUM! equ %_LINENUM% (
echo(!_NEWLINE!
) else (
rem // Remove line number prefix:
echo(!LN:*:=!
)
endlocal
)
)
rem // Move the edited file onto the original one:
move /Y "%_FILE%.new" "%_FILE%"
endlocal
exit /B
ORIGINAL QUESTION:
Doesn't matter whats in any of the lines already. I just want to be able to pick any line from a .txt and replace it with whatever I choose.
So for example: Maybe I have a bunch of .txt's, and I want to replace line 5 in all of them with "vanilla". And later choose to replace line 10 of all .txt's with "Green". And so on...
I've seen lots of people asking the same main question. But I keep finding situational answers.
"How do I replace specific lines?" "you search for whats already in the line, and replace it with your new text" -I cant have that. I need it to be dynamic, because whats in each "line 5" is different, or there's lots of other lines with the same text.
I had tried the only one answer I could find, but all it ended up doing is replace literally all lines with "!ln:*:=!", instead of echoing.
#echo off
setlocal disableDelayedExpansion
set "file=yourFile.txt"
set "newLine5=NewLine5Here"
>"%file%.new" (
for /f "delims=" %%A in ('findstr /n "^" "%file%"') do for /f "delims=:" %%N in ("%%A") do (
set "ln=%%A"
setlocal enabableDelayedExpansion
if "!ln:~0,6!" equ "5:FMOD" (echo(!newLine5!) else echo(!ln:*:=!
endlocal
)
)
move /y "%file%.new" "%file%" >nul
The following (commented) code should work for you:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=yourFile.txt"
set "_NEWLINE=NewLine5Here"
set /A "_LINENUM=5" & rem // (target line number)
rem // Write to a temporary file:
> "%_FILE%.new" (
rem /* Loop through each line of the original file,
rem preceded by the line number and a colon `:`:*/
for /F "delims=" %%A in ('findstr /N "^" "%_FILE%"') do (
rem // Store the current line with prefix to a variable:
set "LN=%%A"
rem /* Store the line number into another variable;
rem everything up to the first non-numeric char. is regarded,
rem which is the aforementioned colon `:` in this situation: */
set /A "NUM=LN"
rem // Toggle delayed expansion to avoid trouble with `!`:
setlocal EnableDelayedExpansion
rem /* Compare current line number with predefined one and replace text
rem in case of equality, or return original text otherwise: */
if !NUM! equ %_LINENUM% (
echo(!_NEWLINE!
) else (
rem // Remove line number prefix:
echo(!LN:*:=!
)
endlocal
)
)
rem // Move the edited file onto the original one:
move /Y "%_FILE%.new" "%_FILE%"
endlocal
exit /B
Besides the typo in EnableDelayedExpansion in your code, you do not even need a second for /F loop to get the line number, and you do not need to extract a certain number of characters from the prefixed line text.
Note that this approach fails for line numbers higher than 231 - 1 = 2 147 483 647.
...is replace literally all lines with "!ln:*:=!", instead of echoing.
But that's correct, because the FINDSTR /N prefixes each line with a line number before.
The !ln:*:=! only removes the line number again.
And the findstr trick is used to avoid skipping of empty lines or lines beginning with ; (the EOL character).
The !line:*:=! replaces everthing up to the first double colon (and incuding it) with nothing.
This is better than using FOR "delims=:" because delims=: would also strip double colons at the front of a line.
The toggling of delayed expansion is necessary to avoid accidential stripping of ! and ^ in the line set "ln=%%A"
To fix your code:
setlocal DisableDelayedExpansion
for /f "delims=" %%A in ('findstr /n "^" "%file%"') do (
set "ln=%%A"
setlocal EnableDelayedExpansion
if "!ln:~0,6!" equ "5:FMOD" (
set "out=!newLine5!"
) else (
set "out=!ln:*:=!"
)
echo(!out!
endlocal
)
I have this code
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "SPLITCHAR=-" & rem // (a single character to split the file names)
set "SEARCHSTR=_" & rem // (a certain string to be replaced by another)
set "REPLACSTR= " & rem // (a string to replace all found search strings)
set "OVERWRITE=" & rem // (set to non-empty value to force overwriting)
rem // Get file location and pattern from command line arguments:
set "LOCATION=%~1" & rem // (directory to move the processed files into)
set "PATTERNS=%~2" & rem // (file pattern; match all files if empty)
rem /* Prepare overwrite flag (if defined, set to character forbidden
rem in file names; this affects later check for file existence): */
if defined OVERWRITE set "OVERWRITE=|"
rem // Continue only if target location is given:
if defined LOCATION (
rem // Create target location (surpress error if it already exists):
2> nul md "%LOCATION%"
rem /* Loop through all files matching the given pattern
rem in the current working directory: */
for /F "eol=| delims=" %%F in ('dir /B "%PATTERNS%"') do (
rem // Process each file in a sub-routine:
call :PROCESS "%%F" "%LOCATION%" "%SPLITCHAR%" "%SEARCHSTR%" "%REPLACSTR%"
)
)
endlocal
exit /B
:PROCESS
rem // Retrieve first argument of sub-routine:
set "FILE=%~1"
rem // Split name at (first) split character and get portion in front:
for /F "delims=%~3" %%E in ("%~1") do (
rem // Append a split character to partial name:
set "FOLDER=%%E%~3"
)
setlocal EnableDelayedExpansion
rem // Right-trim partial name:
if not "%~4"=="" set "FOLDER=!FOLDER:%~4%~3=!"
set "FOLDER=!FOLDER:%~3=!"
rem /* Check whether partial name is not empty
rem (could happen if name began with split character): */
if defined FOLDER (
rem // Replace every search string with another:
if not "%~4"=="" set "FOLDER=!FOLDER:%~4=%~5!"
rem // Create sub-directory (surpress error if it already exists):
2> nul md "%~2\!FOLDER!"
rem /* Check if target file already exists; if overwrite flag is
rem set (to an invalid character), the target cannot exist: */
if not exist "%~2\!FOLDER!\!FILE!%OVERWRITE%" (
rem // Move file finally (surpress `1 file(s) moved.` message):
1> nul move /Y "!FILE!" "%~2\!FOLDER!"
)
)
endlocal
exit /B
I use Command Prompt in this way to create folders and move files inside from folder1 to folder2
cd /D "C:\Users\Administrator\Downloads\"
"C:\Users\Administrator\Downloads\test1\build-folder-hierarchy.bat" "C:\Users\Administrator\Downloads\test2" "*.mkv"
What is the problem ?
But I want to get a folder consolidation from files moving, not generates same number of folders from files
The.Race.Corsa.Mortale.2019.S1E02.Episodio2.HDTV.AAC.iTA.X264-ARSENAL.mkv
The.Race.Corsa.Mortale.2019.S1E01.Episodio1.HDTV.AAC.iTA.X264-ARSENAL.mkv
The.Feed.1x05.Episodio.5.ITA.DLMux.x264-UBi.mkv
The.Feed.1x04.Episodio.4.ITA.DLMux.x264-UBi.mkv
The.Feed.1x03.Episodio.3.ITA.DLMux.x264-UBi.mkv
The.Feed.1x02.Episodio.2.ITA.DLMux.x264-UBi.mkv
The.Feed.1x01.Episodio.1.ITA.DLMux.x264-UBi.mkv
Swamp.Thing.1x10.La.Resa.Dei.Conti.ITA.DLMux.x264-UBi.mkv
Volevo.Fare.La.Rockstar.1x11.Confusione.ITA.WEBRip.x264-UBi.mkv
Volevo.Fare.La.Rockstar.1x07.Tabu.ITA.WEBRip.x264-UBi.mkv
Volevo.Fare.La.Rockstar.1x01.Buon.Compleanno.Olly.ITA.WEBRip.x264-UBi.mkv
Volevo Fare La Rockstar 1x12 La Tribu Ita Webrip x264-Ubi.mkv
Virgin.River.1x10.Finali.inattesi.720p.iTA.AAC.DLRip.x265.-.T7.mkv
Virgin.River.1x07.A.dire.il.vero.720p.iTA.AAC.DLRip.x265.-.T7.mkv
Virgin.River.1x04.Un.Cuore.Ferito.iTA.AC3.WEBMux.x264-ADE.CreW.mkv
Virgin.River.1x01.La.Vita.Continua.iTA.AC3.WEBMux.x264-ADE.CreW.mkv
Tre.Giorni.Di.Natale.1x03.Episodio.3.iTA.AC3.WEBMux.x264-ADE.CreW.mkv
Tre.Giorni.Di.Natale.1x01.Episodio.1.iTA.AC3.WEBMux.x264-ADE.CreW.mkv
But I want to get folders and move files in this way
├─The Race Corsa Mortale [folder]
│ ├─The.Feed.1x05.Episodio.5.ITA.DLMux.x264-UBi [file]
│ ├─The.Feed.1x04.Episodio.4.ITA.DLMux.x264-UBi [file]
└─ ....
├─Virgin River [folder]
│ └─Virgin.River.1x07.A.dire.il.vero.720p.iTA.AAC.DLRip.x265 [file]
:
I try also to use this batch script but it doesn't work: I click on in via explorer but is like disactivated (I use Windows Server 2012)
#echo off
setlocal EnableDelayedExpansion
rem Change current directory to the one where this .bat file is located
cd "%~P0"
set "digits=0123456789"
rem Process all *.mkv files
for %%f in (*.mkv) do (
rem Get the folder name of this file
call :getFolder "%%f"
rem If this file have a properly formatted name: "headS##Etail"
if defined folder (
rem Move the file to such folder
if not exist "!folder!" md "!folder!"
move "%%f" "!folder!"
)
)
goto :EOF
:getFolder file
set "folder="
set "file=%~1"
set "head="
set "tail=%file%"
:next
for /F "delims=%digits%" %%a in ("%tail%") do set "head=%head%%%a"
set "tail=!file:*%head%=!"
if not defined tail exit /B
if /I "%head:~-1%" equ "S" goto found
:digit
if "!digits:%tail:~0,1%=!" equ "%digits%" goto noDigit
set "head=%head%%tail:~0,1%"
set "tail=%tail:~1%"
goto digit
:noDigit
goto next
:found
for /F "delims=Ee" %%a in ("%tail%") do set "folder=%head%%%a"
exit /B
I accept also Powershell solutions
EDIT: Portion of the file name that I need is that before of S#E##, #x## .#x##, .#x#, .#x## and similar
I would probably use the following script (please consult all the explanatory rem remarks):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=%~1" & rem /* (target directory; `.` is current working directory, `%~dp0.` is
rem parent of this script, `%~1` is first command line argument) */
set _MASKS="*.mkv" & rem // (space-separated list of quoted file patterns)
set _SEPS=" " "." & rem // (space-separated list of quoted separators)
rem /* Specify multiple `findstr` search strings, including the prefix `/C:`, as you would
rem directly state them at the `findstr` command, which are used to match the particular
rem sub-strings of the file names that are used to find the part where to split them at
rem and to derive the name of the sub-directory where to move the respective file to: */
set _FILTERS=/C:"^S[0123456789][0123456789]*E[0123456789][0123456789]*$" ^
/C:"^[0123456789][0123456789]*x[0123456789][0123456789]*$"
rem // Change into root directory:
pushd "%_ROOT%" && (
rem // Loop through all matching files:
for /F "delims= eol=|" %%F in ('dir /B /A:-D-H-S %%_MASKS%%') do (
rem // Store current file name and extension, initialise some auxiliary variables:
set "NAME=%%~nF" & set "EXT=%%~xF" & set "SDIR= " & set "FLAG=#"
rem // Toggle delayed expansion to avoid trouble with `!` (also later on):
setlocal EnableDelayedExpansion
rem // Replace all predefined separators by spaces:
for %%S in (!_SEPS!) do set "NAME=!NAME:%%~S= !"
rem // Loop through all space-separated (quoted) items of the file name:
for %%I in ("!NAME: =" "!") do (
rem // Skip the loop body when a sub-string has already been found before:
if defined FLAG (
rem // Store current portion of the file name:
endlocal & set "ITEM=%%~I"
rem // Use `findstr` to match against the predefined sub-strings:
cmd /V /C echo(!ITEM!| > nul findstr /R /I %_FILTERS% && (
rem // Match encountered, hence skip this and the remaining items:
set "FLAG="
) || (
rem /* No match found, so append the current item to the name of the
rem sub-directory where the file is supposed to be moved then: */
setlocal EnableDelayedExpansion
for /F "delims=" %%E in ("!SDIR!!ITEM! ") do (
endlocal & set "SDIR=%%E"
)
)
setlocal EnableDelayedExpansion
)
)
rem // Process only file naes where sub-directory names could be derived from:
if not defined FLAG if not "!SDIR:~1,-1!"=="" (
rem // Create sub-directory, if not yet existing:
ECHO 2> nul mkdir "!SDIR:~1,-1!"
rem // Move current file into the sub-directory (but do not overwrite in case):
ECHO if not exist "!SDIR:~1,-1!\!NAME!!EXT!" > nul move "!NAME!!EXT!" "!SDIR:~1,-1!\"
)
endlocal
)
popd
)
endlocal
exit /B
Supposing the script is called consolidate.bat and the target directory is %UserProfile%\Downloads, call the script like this:
consolidate.bat "%UserProfile%\Downloads"
After having tested for the correct output, remove the upper-case ECHO commands in front of the mkdir and move commands!
Assuming we can split the file name on the Digits in the name, this should do the needful.
I put a pause in, inspect the output and make sure that it is giving you the right info before clicking enter.
#(SETLOCAL EnableDelayedExpansion
ECHO OFF
REM SET "_SrcFolder=C:\Users\Administrator\Downloads\test1"
REM SET "_DstFolder=C:\Users\Administrator\Downloads\test2"
REM SET "FileGlob=*.mkv"
SET "_SrcFolder=C:\Admin"
SET "_DstFolder=C:\Admin\test2"
SET "FileGlob=*.txt"
)
CALL :Main
( ENDLOCAL
EXIT /B
)
:Main
FOR %%A IN (
"%_SrcFolder%\%FileGlob%"
) DO (
CALL :Process "%%~nA" "%%~xA" "%%~fA"
)
GOTO :EOF
:Process
SET "_OLDName=%~1"
FOR /F "Tokens=1 Delims=0123456789" %%a IN ("%~1") DO (
SET "_NewFolder=%%a"
)
SET "_NewName=!_OLDName:%_NewFolder%=!"
ECHO._OLDName=%_OLDName%.%~2
ECHO._NewFolder=%_NewFolder%
ECHO._NewName=%_NewName%.%~2
REM pause
ECHO.
ECHO. We will now do the following if you press any key:
ECHO. MD "%_DstFolder%\%_NewFolder%\"
ECHO. COPY "%~3" "%_DstFolder%\%_NewFolder%\%_NewName%%~2"
ECHO.
PAUSE
MD "%_DstFolder%\%_NewFolder%\"
COPY "%~3" "%_DstFolder%\%_NewFolder%\%_NewName%%~2"
GOTO :EOF
I am trying to change all *.gpx files in a directory, editing out all instances of "Flag, Blue" with "Waypoint" (without quotation marks). I'm not great at Windows script and so want a little help debugging.
I have based this code on code by in question:
Batch Script - Find and replace text in multiple files in a directory without asking user to install any program or add other files to my batch script
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // based on code by aschipfl
rem // https://stackoverflow.com/questions/46467475/batch-script-find-and-replace-text-in-multiple-files-in-a-directory-without-as
rem // Define constants here:
set "_MASK=*.gpx" & rem // (working on all GPX files)
set "_SEARCH=Flag, Blue" & rem // (find those HORRIBLE blue flags)
set "_REPLAC=Waypoint" & rem // (repace with WAYPOINTS)
set "FOROPT=" & rem // NON-recursive
set "IFSW=" & rem // CaSe sEnSiTiVe YeS
set "_TMPF=%TEMP%\%~n0_%RANDOM%.tmp" & rem // (path to temporary file)
pushd "." || exit /B 1
rem // Loop through all matching files in the directory tree:
for %FOROPT% %%F in ("%_MASK%") do (
rem // Write to temporary file:
> "%_TMPF%" (
rem /* Read current file line by line; use `findstr` to precede every line by
rem its line number and a colon `:`; this way empty lines appear non-empty
rem to `for /F`, which avoids them to be ignored; otherwise empty lines
rem became lost: */
for /F "delims=" %%L in ('findstr /N "^" "%%~fF"') do (
rem // Store current line text:
set "LINE=%%L" & set "FLAG="
setlocal EnableDelayedExpansion
rem // Remove line number prefix:
set "LINE=!LINE:*:=!"
rem // Skip replacement for empty line text:
if defined LINE (
rem /* Use `for /F` loop to avoid trouble in case search or replace
rem strings contain quotation marks `"`: */
for /F "tokens=1* delims== eol==" %%I in ("!_SEARCH!=!_REPLAC!") do (
rem // Query to handle case-sensitivity:
if %IFSW% "!LINE!"=="!LINE:%%I=%%I!" (
rem // Detect whether replacement changes line:
if not "!LINE!"=="!LINE:%%I=%%J!" (
rem // Actually do the sub-string replacement:
set "LINE=!LINE:%%I=%%J!"
set "FLAG=#"
)
)
)
)
rem // Output the resulting line text:
echo(!LINE!
if defined FLAG (endlocal & set "FLAG=#") else (endlocal)
)
)
rem // Check whether file content would change upon replacement:
if defined FLAG (
rem // Move the temporary file onto the original one:
> nul move /Y "%_TMPF%" "%%~fF"
) else (
rem // Simply delete temporary file:
del "%_TMPF%"
)
)
popd
endlocal
exit /B
I run the script but no changes to the GPX files.
A real-world example segment from the GPX file would be:
<ele>1.19734255318821</ele>
<time>2019-07-28T00:42:12Z</time>
<name>CW1002</name>
<sym>Flag, Blue</sym>
<extensions>
<trp:ViaPoint>
Obviously I want this to remain the same except:
<sym>Waypoint</sym>
I would like to create a script that loops reccursively through subfolders of D:\MyFolder\ for example, to find multiple files named MyFile.txt
then look into each file for the keyword FROM and retrieve the string between the FROM and the next semicolon ;.
Sample of MyFile.txt:
LOAD
Thing1,
Thing2,
Thing3,
FROM
Somewhere ;
The desired result is: Somewhere.
(The position of the semicolon ; can be in another line).
I did some tries but I did not succeed in writing a correct script:
#echo off
SET PATH="D:\MyFolder\"
FOR /R %PATH% %%f IN (MyFile.txt) DO (
FOR /F "delims=FROM eol=;" %%A in (%%f) do (
set str=%%A
ECHO %str%
)
)
If it can't be done in batch, please let me know in which language I can do it easily. I would like to have an executable script in the end.
There are some issues in your code:
The delims option of for /F defines characters but not words to be used as delimiter for parsing text files. To find a word, use findstr instead (you could use its /N option to derive the position/line number of the search string).
The eol option of for /F defines a character to ignore a line in case it occurs at the beginning (or it is preceded by delimiters only).
for /R does actually not search for files in case there are no wild-cards (?, *) in the set (that is the part in between parentheses). The dir /S command does, so you can work around this by wrapping a for /F loop around dir /S.
The PATH variable is used by the system to find executables, like findstr, so you must not overwrite it; use a different variable name instead.
Here is the way I would probably do it (supposing any text following the keyword FROM needs to be returned also):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=D:\MyFolder" & rem // (root directory of the tree to find files)
set "_FILE=MyFile.txt" & rem // (name of the files to find in the tree)
set "_WORD=FROM" & rem // (keyword to be searched within the files)
set "_CHAR=;" & rem // (character to be searched within the files)
rem // Walk through the directory tree and find matching files:
for /F "delims=" %%F in ('dir /B /S "%_ROOT%\%_FILE%"') do (
rem // Retrieve the line number of each occurrence of the keyword:
for /F "delims=:" %%N in ('findstr /N /I /R "\<%_WORD%\>" "%%~F"') do (
rem // Process each occurrence of the keyword in a sub-routine:
call :PROCESS "%%~F" %%N
)
)
endlocal
exit /B
:PROCESS
rem // Ensure the line number to be numeric and build `skip` option string:
set /A "SKIP=%~2-1"
if %SKIP% GTR 0 (set "SKIP=skip^=%SKIP%") else set "SKIP="
rem // Read file starting from line containing the found keyword:
set "FRST=#"
for /F usebackq^ %SKIP%^ delims^=^ eol^= %%L in ("%~1") do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem // Split off everything up to the keyword from the first iterated line:
if defined FRST set "LINE=!LINE:*%_WORD%=!"
rem /* Split read line at the first occurrence of the split character;
rem the line string is augmented by preceding and appending a space,
rem so it is possible to detect whether a split char. is there: */
for /F "tokens=1,* delims=%_CHAR% eol=%_CHAR%" %%S in (" !LINE! ") do (
endlocal
set "TEXT=%%S"
set "RMND=%%T"
set "ITEM=%~1"
setlocal EnableDelayedExpansion
rem // Check whether a split character is included in the line string:
if not defined RMND (
rem // No split char. found, so get string without surrounding spaces:
set "TEXT=!TEXT:~1,-1!"
) else (
rem // Split char. found, so get string without leading space:
set "TEXT=!TEXT:~1!"
)
rem // Trimm leading white-spaces:
for /F "tokens=*" %%E in ("!TEXT!") do (
endlocal
set "TEXT=%%E"
setlocal EnableDelayedExpansion
)
rem // Return string in case it is not empty:
if defined TEXT echo(!ITEM!;!TEXT!
rem // Leave sub-routine in case split char. has been found:
if defined RMND exit /B
)
endlocal
set "FRST="
)
exit /B