1

As the question states, I want to write a batch script that uses GhostScript to invert all the colors of a PDF file and place the result in a sub-directory. I have installed GhostScript then attempted to add the paths to an environment variable but the calls were not recognized, so I placed a copy of the executable in the same directory as the batch file which seems to successfully call GhostScript. I am using the following GhostScript code as template which was found on this post:

gs -o output.pdf     \
-sDEVICE=pdfwrite \
-c "{1 exch sub}{1 exch sub}{1 exch sub}{1 exch sub} setcolortransfer" \
-f input.pdf

Below you will find my current attempt at this batch file, my first batch file for that matter, so any advice is appreciated:

SETLOCAL EnableDelayedExpansion
@ECHO OFF

:: Setting some variables...
SET outdir=InvertedOutputs
SET gs=gswin64c.exe

:: Check for the inverted output directory,
:: make the directory if it doesn't exist.
IF NOT EXIST %outdir% MKDIR %outdir%

:: Iterate through all files in current directory.
FOR %%f IN (*) DO (

    :: Set the GhostScript / PostScript commands, before if statement.
    SET commands="{1 exch sub}{1 exch sub}{1 exch sub}{1 exch sub} setcolortransfer"

    :: Check that file extension is correct.
    IF %%~xf == .pdf (
        %gs% -o %outdir%\%%~f -sDEVICE=pdfwrite -c !commands! -f %%~f
    )
)

ECHO.
PAUSE

I have edited the code to reflect the addition of enabling delayed expansion per @aschipfl comment. The following error still persists:

Error: /undefinedfilenmae in ...
Community
  • 1
  • 1
two_what
  • 31
  • 5
  • You need [delayed expansion](http://ss64.com/nt/delayedexpansion.html) as you are writing *and* reading the same variables within one parenthesised block of code... – aschipfl Mar 14 '17 at 20:06
  • I thought I was only reading the source file and writing to a new file in the sub-directory? I don't intend to modify the original file, only read it. – two_what Mar 14 '17 at 20:10
  • No, I am talking about **environment variables**: you assign variable `commands` in the same block as you are expanding it later by `%commands%`; this does not work as variables are read when a block is *parsed*; to read them when the code is *executed*, you need to put `setlocal EnableDelayedExpansion` somewhere before that block (usually on top of the script), then use `!commands!` rather than `%commands%`... – aschipfl Mar 14 '17 at 20:14
  • See my code changes above - I enabled delayed expansion and moved the variable declaration outside of the block as well. But am still getting the following output: `Error: /undefinedfilename in ...` – two_what Mar 14 '17 at 20:35
  • Let me recommend to use `rem` for comments rather than `::` as the latter may result in unexpected behaviour; in addition, put `""` around file paths (`-o "%outdir%\%%~f"` and `-f "%%~f"`). I cannot help you with `gs` itself because I do not know this command... – aschipfl Mar 14 '17 at 22:17

1 Answers1

0

The gs portion works fine. Thank you for that. In my testing I saw similar errors as the OP when the filename included spaces. So I assume this question is only about batch-files and the lack of double quotes, a common problem in cmd.

When to use double quotes

The general rules I was taught are:

(See Windows NT Shell Scripting Apr 27, 1998 by Timothy Hill)

  • Don’t use quotes in SET statements. This matches environment variables, where values are not quoted even if there are embedded spaces.

  • Do use quotes anywhere environment variables are expanded, which is pretty much everywhere else: IF, FOR, CALL and command invocations.

  • Finally FOR variables (in a batch file) use a doubled %. Otherwise %X will resolve to just X, which is not what you want.

Note that script (and subroutine) arguments must have quotes if there are embedded spaces in the value, but are otherwise optional. Thus you cannot always wrap the parameters %0 .. %9 with quotes. Because doubled double-quotes are ignored ""a b c"" is parsed as three values a b and c not one.

The best way to normalize arguments, which may have quotes, is strip them (if present) using the ~ operator with something like:

SET SOURCE=%~1
SET DESTINATION=%~2

Then all variables will consistently not have quotes, and they should be added back everywhere else. Note %~f1 is useful for getting the full path from an argument, but is only appropriate when %1 is expected to be a filename.

Regarding the OP’s script

  • Values from FOR (e.g. %%F) are not wrapped with double quotes, thus must be wrapped with double quotes in case there are embedded spaces. This is what was causing the somewhat cryptic error message Error: /undefinedfilename in (xxx). I didn't see this at first, but if you read down a bit further in the console output you will see: Last OS error: No such file or directory In my case xxxx was the first part of a path before the embedded space.

  • The SET COMMANDS= can be moved outside of the FOR loop to eliminate the need for deferred expansion. Even simpler is to eliminate the variable completely, since it is only used once.

    I prefer using a subroutine to avoid the quirks of FOR loops and multiple statements in parentheses. Subroutines also eliminate the need for deferred expansion and cmd echoes each line, making expansions easy to debug.

  • -o %outdir%\%%~f works (assuming no embedded spaces) but probably not for the reasons you might think. The ~ syntax takes one or more modifiers, then the argument letter from the FOR loop. Typical modifiers are: d,p,n,x. Try Call /? for details.

    In this case ‘f’ is not a modifier, since it is the last letter in the ~ expression. Instead it refers to the variable f from the FOR statement. Since there are no modifiers, %%~f is almost equivalent to %%f, except that ~ strips quotes.

    I use -o "%outdir%\%~nx1" to extract the name and extension from the 1st argument to the subroutine (%1). This handles the case where %1 is a full path. Note since ~ strips quotes they have to be added back.

  • -f %%~f can also be written %%f in your example, but it also must have quotes since values from FOR are not wrapped with quotes. Otherwise an embedded space will cause problems, as you have seen.

    In my example, in a subroutine, since %1 was wrapped with quotes at the call site FOR %%F IN (*.pdf) DO Call :DoOne "%%F" without additional quotes is correct.

The Updated Script (tested with file names that have embedded spaces)

@ECHO OFF
SETLOCAL EnableExtensions

SET OUTDIR=InvertedOutputs
SET GS=C:\Program Files\gs\gs9.22\bin\gswin64c.exe

REM Make the output directory under the current one, hiding errors (e.g. directory exists)
MKDIR %OUTDIR% 1>nul: 2>>&1

REM Iterate through pdf files in current directory only.
REM Do not recurse child folders, since this would find files in the output directory
FOR %%F IN (*.pdf) DO Call :DoOne "%%F"

GOTO :Done

REM a subroutine eliminates the need for deferred expansion. All expansions are echoed to the console.
REM e.g. Call :DoOne "%%F" - processes one file
:DoOne
REM %~nx1 = the file name + extension of the argument. Since ~ strips quotes, they added back here.
REM including the commands 
REM -f %1 -- quotes are not used here, since %1 had to be quoted at the call site
ECHO ON
"%GS%" -o "%OUTDIR%\%~nx1" -sDEVICE=pdfwrite -c "{1 exch sub}{1 exch sub}{1 exch sub}{1 exch sub} setcolortransfer" -f %1
ECHO OFF
GOTO :EOF

:Done
Pause
Andrew Dennison
  • 1,069
  • 11
  • 9