1

I decided to reformat my question due to comments of users (thanks to all) and new knowledge I have got. I'll not open a new post because the main problem remains the same:

How to handle strings produced by some program (e.g. CURL) line-by-line dynamically if those strings don't have LF terminator?

enter image description here

CURL produces each line with interval approx 1 sec. I need to choose only some fields from CURL output for further processing (Current speed, Downloaded bytes, Downloded %), so I need to process each new line that appears. But I can't because each line is ended with CR.

The FOR loop doesn't show anything in CMD window until download is complete, and other users explained to me why:

FOR /F "delims=" %%x in ('curl ... http://some_url 2^>^&1') do echo %%x

So, I am forced to abandon the FOR:

curl ... http://some_url | string_handler.bat

And using JREPL for replacing CR to CRLF on-the-fly:

curl ... http://some_url  2>&1 | jrepl "\r([^\n])" "\r\n$1" /xseq

But this solution produces the hole output, not line-by-line, after CURL is finished because if string doesn't have LF terminator there is nothing to PIPE (thanks @Stephan). Maybe there is the solution to bypass the PIPE behaviour?

For solving problem I've made the simple script - simulation of CURL output. This is countdown timer and it produces every 1 sec a line with CR terminator except first and last lines that have CRLF:

:: bears.bat

@echo off
setlocal enabledelayedexpansion

::Define LF variable containing a linefeed (0x0A)
(set LF=^
%=empty line%
)

::Get a CR character (0x0D)
for /F %%a in ('copy /Z "%~F0" NUL') do set "CR=%%a"

:: First argument is CountDown high level, default 4 sec
if "%1"=="" (set /a high=4) else (set /a high=%1)

echo Hello bears
for /l %%i in (%high%,-1,0) do (
    if %%i gtr 0 (
        <nul set /p="Counter: %%i!CR!" 
    ) else (
        <nul set /p="Counter: %%i!CR!!LF!"
    )
    :: Pause 1 sec
    if %%i gtr 0 ping 127.0.0.1 -n 2 >nul
)
exit /b
user1581016
  • 61
  • 1
  • 8
  • 1
    Just to clarify, regarding your statement, "That is why FOR loop doesn't show anything in CMD window until download is complete:". In a `FOR /F` loop, the entire parenthesized command, in this case `curl ... http://some_url 2>&1`, is run and completed before any output is transferred to the `do` portion of the command line. That is always the case, and has absolutely nothing to do with the type of line endings used. – Compo Sep 20 '22 at 11:58
  • Because the entire output is only shown once the for loop has completed. Why do you need `for` though? You do not require assigning anything to variables. It is simply output results? – Gerhard Sep 20 '22 at 12:01
  • to compo: curl download meter is in error thread, so 2>&1 is necessary. Without this redirection the download meter doesn't appear at all in FOR loop, because FOR loop handles only normal output. Additionally, if lines in bears.bat script ended with CRLF they are shown line-by-line in FOR loop. You can test – user1581016 Sep 20 '22 at 12:09
  • @gerhard Entire output is only shown once the for loop has completed because output line don't have LF char. You can test bears.bat with adding CRLF on every line and you will see than output appers line-by-line in FOR loop – user1581016 Sep 20 '22 at 12:13
  • 1
    without `for` loop: `bears.bat | utils\jrepl "\r([^\n])" "\r\n$1" /xseq`. Without a `CR`, the line isn't finished and so there's nothing to be passed to the pipe, and so nothing is received at the other end of the pipe until there is a `CR`. – Stephan Sep 20 '22 at 12:33
  • 2
    @user1581016, not only did I not tell you, or imply that `2>&1` wasn't necessary, but it appears that you're telling both Gerhard and myself that we are wrong. Prove to me that output is sent to the `do` portion of a `FOR /F` parenthesized command prior to its completion. I'm eager to understand where I have been going wrong all of these years, and ask that you offer me a way I can run a `FOR /F` command and begin outputting to the `do` portion whilst the parenthesized command is still running. – Compo Sep 20 '22 at 12:46
  • @compo Just try: `for /f "delims=" %%x in ('curl "%url%" 2^>^&1') do echo %%x >>curl.txt` You'll see the "curl.txt" file with download meter. Then: `for /f "delims=" %%x in ('curl "%url%"') do echo %%x >>curl.txt` and you will not see "curl.txt" at all. So, without this redirecting data doesn't send to DO portion of FOR loop – user1581016 Sep 20 '22 at 13:11
  • @Stephan, you wrote: _italic_If the line isn't finished and so there's nothing to be passed to the pipe, and so nothing is received at the other end of the pipe until there is a CR _italic_ Maybe, but try: `con ping 127.0.0.1 -n 2 >nul con ping 127.0.0.1 -n 2 >nul` Then: `script_above.bat|xargs echo` You'll see otuput one-by-one, and there is no LF character. So, CR character in not necessary for piping – user1581016 Sep 20 '22 at 13:34
  • 1
    What are you talking about, @user1581016? My initial comment, has absolutely nothing to do with whether or not a command outputs to stdErr or stdOut, and as I've already stated did not imply or state otherwise. Please read what I wrote very carefully. Nothing whatsoever is output to `do` until the entire parenthesized command has completed. You stated that the reason for that is due to line endings without a linefeed, _(CR only)_, and I'm telling you that the reason for that is because that is how a parenthesized `FOR /F` command works. Which part of that is unclear to you? – Compo Sep 20 '22 at 13:35
  • @Compo Sorry, but I dont understand English very good. If I understand you, the FOR loop cannot output anything until the command in (..) is complete? – user1581016 Sep 20 '22 at 13:40
  • 1
    That is correct @user1581016! The line endings `CR` `LF` or any combination thereof is irrelevant to that fact. _Please also note that your filenames, text, code and images, all use only English, so it was not obvious to anyone that English was not your native language._ – Compo Sep 20 '22 at 13:42
  • 1
    LOL - you write *with* `!CR!` to prove that `CR` isn't necessary? Put the one-liner `nul &nul` to a batch file and try `test.bat` vs. `test.bat | more` – Stephan Sep 20 '22 at 14:31
  • 1
    Btw: simple proof of @Compo's claim: `for /f "delims=" %a in ('ping localhost') do @echo %a` – Stephan Sep 20 '22 at 14:36
  • 1
    …or ```For /F Delims^=^ EOL^= %G In ('Echo Line 1 ^& %SystemRoot%\System32\timeout.exe /T 5 ^& Echo Line 2') Do @Echo %G```. This will return all three lines output almost together, and will not do so until at least five seconds after the parenthesized command has started, i.e. after all of the parenthesized commands have completed. – Compo Sep 20 '22 at 16:11
  • OK, people. The FOR loop behaviour sometimes is not very clear, ss64.com says nothing about that and not very strange that I dont know everything. That is why I'm here. And I will be grateful if someone helps me to solve the problem: how to handle CURL's output line-by-line – user1581016 Sep 20 '22 at 16:47
  • A `for` loop *does* handle the output line by line. Just not "in real-time" (as you seem to expect). Other languages may be able to do that, but `cmd` can't by design. – Stephan Sep 21 '22 at 06:44

2 Answers2

1

If you want the Mac 'CR' ending lines replaced with Windows 'CRLF`, which is what I think you are asking for, then the following method should achieve that:

@(For /F Delims^=^ EOL^= %%G In ('%SystemRoot%\System32\curl.exe
 "http://some_url" 2^>^&1') Do @(Set "LineWithCR=%%G"
 SetLocal EnabledelayedExpansion & Echo !LineWithCR!& EndLocal)) 1>"curl.log"

Quick Explanation:

A For /F command terminates a line at the first occurrence of a 'LF', and if that is preceded by a 'CR', that 'CR' is stripped, but any other 'CR' characters remain. If you expand a For variable containing a 'CR', in this case %%G, that 'CR' will also remain. However if you expand an environment variable, %LineWithCR%, all 'CR' characters will be stripped. However, in order to expand an environment variable containing a 'CR', you would need to use delayed expansion, i.e. !LineWithCR!.

Compo
  • 36,585
  • 5
  • 27
  • 39
  • Thanks, but I need processing CURL output on-the-fly, line by line while CURL is working. The way you suggest is the same as I tried, but I replaced CR to CRLF with JREPL. – user1581016 Sep 20 '22 at 18:25
  • Not with a `For /F` loop though. What I've tried to do in the comments is explain to you why you cannot do that, and in my answer how to get each line of output with the required line endings, _(without having to download and use a third party utility/script)_. IMO, those have answered your question. Perhaps you need to explain exactly what you intend to do with specific fields of each line of output, and why that needs to be done dynamically, then somebody may be able to assist you with advice on how that may be possible, or alternative ways of getting that same information. – Compo Sep 20 '22 at 20:13
  • I just need to extract only some fields from bunch of CURL's output: `Current Speed, Current Downloaded Bytes, Current Downloaded %`. Then I'll put that data to the CMD window in some place and add some description. And the things I said have sence only when CURL still downloading, not after it finished – user1581016 Sep 21 '22 at 07:11
1

As already said, it can't be solved with a FOR /F loop, because it always wait for the complete output before starting to loop.

But it can be solved with an async pattern and two threads.
You can test it, replace the call slowOutput.bat to call bears.bat

Important: The consumer just reads all available content into the line variable.
If the producer is fast enough, this can lead into fetching two lines (with CR).
With your bears.bat example:
Content of line = Counter: 4<CR>Counter: 3<CR> If your real world curl command produces this problem, you need to split the line at the <CR> character.

@echo off
REM *** Trampoline jump for function calls of the form ex. "C:\:function:\..\MyBatchFile.bat"
FOR /F "tokens=3 delims=:" %%L in ("%~0") DO goto :%%L

REM Create an empty file, this has to exist before the consumer starts
break > async.tmp

REM Start the "producer" thread
start "" /b "cmd /c "%~d0\:producer:\..\%~pnx0"

REM The consumer thread runs parallel to the producer thread
call :consumer
exit /b

:producer
(
  call slowOutput.bat 
  (echo ende)
) > async.tmp
exit /b

:consumer
echo This is the consumer thread
setlocal EnableDelayedExpansion
< async.tmp call :_consumer
exit /b

:_consumer
set "line="
set /p line=

if not defined line goto :_consumer
if "!line!" EQU "ende" exit /b

echo(!line!
goto :_consumer
jeb
  • 78,592
  • 17
  • 171
  • 225
  • Very interesting, I'll thinking a lot. At first glance there is `start /b` call, so the hidden window appears. Does it mean that script execution cannot be interrupted by CTRL-C? – user1581016 Sep 21 '22 at 11:45
  • 1
    @user1581016 `start /b` starts in the same cmd window, so there doesn't appear anything. `CTRL-C` will not really work in this scenario – jeb Sep 21 '22 at 11:47
  • Thanks, it works, but delay between each counter output is about 4 sec instead of 1 sec ( – user1581016 Sep 21 '22 at 11:59
  • @user1581016 For me it works with the 1 sec delay, you should check your `bears.bat` standalone – jeb Sep 21 '22 at 12:03
  • Yet checked, `cmd>bears.bat` outputs counter values with 1 sec delay – user1581016 Sep 21 '22 at 12:06