2

I am trying to replace a line in certain files (based on file extension). The program is not working as desired, and the command which is causing issue is the one below.

FOR /F %%k IN ('TYPE !FILE! ^| FINDSTR /N "^"') DO (

This comes back with following error:

FINDSTR: No search strings The process tried to write to a nonexistent pipe.

However, the command itself works as expected when run in command line. I have already spent nearly 1 full day but to no avail. FOR /F %k IN ('TYPE <filename> ^| FINDSTR /N "^"') DO echo(%k

Pointers will be greatly appreciated!

Complete code is provided below for reference.

@echo off
CD data
FOR /F "delims=" %%i IN ('DIR *.ext1 /B') DO (
  SET "FILE=%%i"
  SETLOCAL EnableDelayedExpansion
  echo(!FILE!
  <!FILE! >!FILE!.tmp~ (
    REM Find line number on which Logon command is found
    FOR /F "tokens=1,* delims=: " %%j IN ('FINDSTR /I /N /R "^\.LOGON.*" !FILE!') DO (
      SET "NUM=%%j"
    )
    REM Print all lines along with line number at beginning
    FOR /F %%k IN ('TYPE !FILE! ^| FINDSTR /N "^"') DO (
      SET "LINE=%%k"
      REM Replace entire content of Logon line with Run file command
      FOR /F "tokens=1,* delims=:" %%l IN ("!LINE!") DO IF %%l EQU !NUM! (
        echo(.RUN FILE logon.txt;
      ) ELSE (
        echo(!LINE:*:=!
      )
    )
  )
  MOVE /Y "!FILE!.tmp~" !%FILE!"
  ENDLOCAL
)
CD ..
aschipfl
  • 33,626
  • 12
  • 54
  • 99
stariq
  • 23
  • 5
  • 1
    Maybe try replacing `('TYPE !FILE! ^| FINDSTR /N "^"')` with `('FINDSTR /N "^" !FILE!')`. – Phil Brubaker Jun 24 '20 at 09:51
  • @PhilBrubaker, this won't change anything; the problem is having delayed expansion enabled, which consumes the `^` even though it is quoted… – aschipfl Jun 24 '20 at 13:46

3 Answers3

2

The problem in the line FOR /F %%k IN ('TYPE !FILE! ^| FINDSTR /N "^"') DO ( is the fact that you have got delayed variable expansion enabled, which also consumes the caret symbol ^ for escaping even when being quoted. Refer also to this post for details: How does the Windows Command Interpreter (CMD.EXE) parse scripts?

To resolve the issue you simply need to double the carets:

FOR /F %%k IN ('TYPE !FILE! ^| FINDSTR /N "^^"') DO (

Note that specifying $ as the search string for findstr skips the last line of the input data in case it is not terminated by a line-break. Also note that $ anchors to the carriage-return character, which is only present in text files with the Windows-style end-of-line marker carriage-return plus line-feed.


Anyway, here is a fixed variant of your code, which avoids delayed expansion as long as possible, hence there is actually no need to double the caret symbol:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // It is assumed here that the parent directory of the script is the root location:
pushd "%~dp0data" && (
    for /F "delims=" %%I in ('dir /B /A:-D-H-S "*.ext1"') do (
        set "FILE=%%I"
        echo(%%I
        rem // Here `%%I` is used instead of `!FILE!` since delayed expansion is disabled:
        < "%%I" > "%%I.tmp~" (
            rem // Use right word boundary `\>` in the search string:
            for /F "tokens=1,* delims=:" %%J in ('findstr /I /N /R "^\.LOGON\>" "%%I"') do (
                rem /* Since this loop should iterate once only anyway, the interim variable
                rem    `NUM` is actually not really needed when the remaining code is also
                rem    placed within the loop body: */
                rem set "NUM=%%J"
                rem // At this point delayed expansion is still disabled:
                for /F %%K in ('type "%%I" ^| findstr /N "^"') do (
                    set "LINE=%%K"
                    rem // Here `%%J` is used instead of `!NUM!`:
                    for /F "tokens=1,* delims=:" %%L in ("!LINE!") do if %%L equ %%J (
                        echo(.RUN FILE logon.txt;
                    ) else (
                        rem // This is the only part where delayed expansion is needed:
                        setlocal EnableDelayedExpansion
                        echo(!LINE:*:=!
                        endlocal
                    )
                )
            )
        )
        > nul move /Y "%%I.tmp~" "%%I"
    )
    popd
)

endlocal
exit /B
aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • This works - thanks for providing such a detailed comment with each line of code. I've learned so much more about commands here than reading the documentation :) Reviewing the code, I believe that _delayedvariableexpansion_ can be skipped altogether. I've provided alternate solution using logic you provided. – stariq Jun 25 '20 at 08:18
  • You're very welcome! Concerning delayed expansion: it is needed for safely echoing out lines of text with the preceding line number plus `:` removed; you could of course just do `echo(%%M` instead (without delayed expansion), but this would remove leading colons from the lines… – aschipfl Jun 25 '20 at 09:22
2

As long as your source run files don't have any lines which begin with a : character, the following may provide an alternative method of performing the task:

@%__AppDir__%where.exe /Q "data":"*.mload" >NUL 2>&1 && (CD "data"
    For /F "EOL=? Delims=" %%H In (
        '%__AppDir__%findstr.exe /IM "\<\.LOGON\>" "*.mload" 2^>NUL'
    ) Do @(Copy /Y "%%H" "%%~nH.tmp~" >NUL && (
                For /F "Tokens=1,* Delims=:" %%I In (
                    '%__AppDir__%findstr.exe /N "^" "%%~nH.tmp~"'
                ) Do @Set /P "=:%%J"<NUL|%__AppDir__%findstr.exe /LIB ":.LOGON " >NUL && (
                        Echo .RUN FILE logon.txt;) || Echo=%%J)>"%%H"
        Del "%%~nH.tmp~" 2>NUL))

Just to be clear, my reading of your requirement is to, replace all lines inside all .mload files within .\data, which begin with the case insensitive string .LOGON , with the line .RUN FILE logon.txt;

Compo
  • 36,585
  • 5
  • 27
  • 39
0

Thanks Phil for your suggestion but it did not work.

Oddly, I tried using end of line character $ instead of beginning of line ^ and it seems to have done the trick.

Seems like ^ was being taken as literal, and even escaping it with \ doesn't work as expected.

First Working Solution

FOR /F "delims=" %%i IN ('DIR *.ext1 /B') DO (
  SET "FILE=%%i"
  SETLOCAL EnableDelayedExpansion
  <!FILE! >!FILE!.tmp~ (
    REM Find line number on which Logon command is found
    FOR /F "tokens=1,* delims=:" %%j IN ('FINDSTR /I /N /R "^\.LOGON.*" !FILE!') DO (
      SET "NUM=%%j"
    )
    REM Print all lines along with line number at beginning
    FOR /F "tokens=1,* delims=" %%k IN ('FINDSTR /N "$" !FILE!') DO (
      SET "LINE=%%k"
      REM Replace entire content of Logon line with Run file command
      FOR /F "tokens=1,* delims=:" %%l IN ("!LINE!") DO IF %%l EQU !NUM! (
        echo(.RUN FILE logon.txt;
      ) ELSE (
        echo(!LINE:*:=!
      )
    )
  )
  MOVE /Y !FILE!.tmp~ !FILE!
  echo Auto-generated !FILE!
  ENDLOCAL
)

Revised Working Solution (thanks to aschipfl)

@echo off
setlocal EnableExtensions DisableDelayedExpansion
pushd "%~dp0data" && (
    rem // Loop through all files with .mload file extension:
    for /F "delims=" %%I in ('dir /B /A:-D-H-S "*.mload"') do (
        < "%%I" > "%%I.tmp~" (
            rem // Use beginning of line position with .logon in the search string:
            for /F "tokens=1,* delims=:" %%J in ('findstr /I /N /R "^\.logon" "%%I"') do (
                rem // Use beginning of line position in the search string:
                for /F "delims=" %%K in ('type "%%I" ^| findstr /N "^"') do (
                    rem // Match current line number with previously searched line:
                    for /F "tokens=1,* delims=:" %%L in ("%%K") do if %%L equ %%J (
                        echo(.RUN FILE logon.txt;
                    ) else (
                        echo(%%M
                    )
                )
            )
        )
        > nul move /Y "%%I.tmp~" "%%I"
    )
    popd
)
stariq
  • 23
  • 5
  • stariq, you've not adjusted your code and remarks on the lower answer to match each other!, _(Use right word boundary `\>` remark)_. BTW, my answer doesn't need or use delayed expansion either, and as it nests only one [tag:for-loop], it is likely to be more efficient than answers nesting two or three. Also, after formulating and posting a solution for you, the very least you could have done was to test it and provide feedback. – Compo Jun 25 '20 at 09:06
  • Thanks for pointing out the discrepancy in remark and code - fixed it. Also, apologies for not commenting on your proposed solution. It did work first time (hence marked it as useful). I'm still at an early stage of learning batch commands and for me it was hard to follow. I adopted the final solution based on my limited understanding of code. I imagine your code being efficient but file sets are really and difficult to pickup on execution time difference. – stariq Jun 26 '20 at 11:43
  • You're right! It's perfectly working solution which everyone can make use of. Didn't mean to overlook your effort, so apologies if it came out that way. – stariq Jun 26 '20 at 11:54