Home > OS >  BATCH FOR-loop but start with specific file
BATCH FOR-loop but start with specific file

Time:09-23

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?

CodePudding user response:

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

CodePudding user response:

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.

  • Related