Lese piped stdout eines Programms am stdin meines Programms

Windowsspezifisches Forum , API ,..
Beiträge, die plattformübergreifend sind, gehören ins 'Allgemein'-Forum.
Benutzeravatar
Stefan2
Beiträge: 5
Registriert: 03.06.2014 20:11
Computerausstattung: Windows XP, Win7 32- und 64-bit.
Meine Sprachen sind Deutsch, Englisch, DOS Batch, VBS, JS, PascalScript, PowerShell, XYScript, RegEx und seit Neusten ein bisschen FreePascal, und jetzt PB ;-)
Wohnort: Germany, EU

Lese piped stdout eines Programms am stdin meines Programms

Beitrag von Stefan2 »

Hallo Leute, ich bin neu hier und brauche mal eure Hilfe.
Ich habe bereits die Hilfe und ein paar tuts durch, ebenso
hier im Forum gesucht, aber nichts wirklich passendes gefunden....

Ich benutze PureBasic_Demo_5.22.exe auf Win7, 32-bit.


FRAGE
Ich würde gern eine Konsolenanwendung schreiben, welches an stdin von einer Pipe die Daten einliest, etwa so wie z.B. SED.

Beispiel:
C:\>dir /b *.txt | myapp.exe

(Zweites Problem: wenn am stdin nichts reinkommt, soll automatisch der dann hoffentlich übergebene Parameter eingelesen werden. Oder umgekehrt.)
C:\>myapp.exe test.txt


PROBLEM
Zur Zeit bekomme ich beim stdin nur ein paar Fragezeichen angezeigt:
C:\>echo example.txt | myapp.exe
Got: ?????4?

Das Einlesen des Inputs über einen Parameter funktioniert:
C:\>myapp.exe example.txt
Got: example.txt



CODE
Mein code sieht folgendermaßen aus:
(Die Logik zur Entscheidung; stdin oder parameter, fehlt mir noch)

Code: Alles auswählen

;OpenConsole()                               ; Wait for user input
;we execute this app already from a console window, we do not need to open one first.

  If OpenConsole()

      ;sInput$ = ProgramParameter(0)

      ;Repeat
           sInput$ = Input() 
           PrintN("Got as input: "+sInput$) 
      ;Until (sInput$ = "" )

  EndIf

End



Zum Vergleich
Mit VBS und Pascal konnte ich die Pipe lesen:

Code: Alles auswählen

begin
        while not EOF do begin
            ReadLn(sInFilename);
            funcDoSomething( sInFilename, sOutputStr );
            //WriteLn( sOutputStr + '_pipe-works' );
            WriteLn( sOutputStr );
        end;
end.


Aber bei PureBasic fehlt mir noch etwas?     :lamer:



Danke für eure Hilfe.


Read piped stdout from other app on stdin from my app
Stefan (PB beginner (PureBasic_Demo_5.22) on M$ Windows 7, both 32+64)
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von NicTheQuick »

Das Problem wird sein, dass du in deinem Programm Unicode aktiviert hast und du die Daten aber als ASCII rein gepiped bekommst.
Die Lösung ist also dein Programm auf Nicht-Unicode umzustellen ODER statt 'ReadProgramString()' einfach 'ReadProgramData()' zu nutzen und dann mit einem 'PeekS(*buffer, -1, #PB_Ascii)' den ASCII-Zeichen-Stream in einen Unicode-String zu verwandeln.

Ansonsten hatten wir das Problem schon mal an anderer Stelle hier im Forum diskutiert. Ich bin nur gerade zu faul zum suchen.
Benutzeravatar
Stefan2
Beiträge: 5
Registriert: 03.06.2014 20:11
Computerausstattung: Windows XP, Win7 32- und 64-bit.
Meine Sprachen sind Deutsch, Englisch, DOS Batch, VBS, JS, PascalScript, PowerShell, XYScript, RegEx und seit Neusten ein bisschen FreePascal, und jetzt PB ;-)
Wohnort: Germany, EU

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von Stefan2 »

Danke.

Das Problem wird sein, dass du in deinem Programm Unicode aktiviert hast und du die Daten aber als ASCII rein gepiped bekommst.
Stimmt. In dieser Richtung hatte ich schon mal geschaut, aber erst jetzt gesehen, dass unter 'Compiler Options' "[X] Create Unicode" angewählt war.
Und auf der Konsole habe ich chcp 850.

Jetzt bekomme ich:

Code: Alles auswählen

C:\>echo example.txt | myapp.exe
Got: 
Ein Prozess hat versucht, zu einer nicht bestehenden Pipe zu schreiben.

C:\>
C:\>dir /b myapp.pb | myapp.exe
Got: 

C:\>
:roll:

- - -
ODER statt 'ReadProgramString()' einfach 'ReadProgramData()' zu nutzen
Diese Befehle haben doch mit meinem Anliegen gar nichts zu tun??? Ich will doch kein externes Programm starten und dessen Output einlesen???
Oder ich verstehe die Hilfe falsch?



Ich versuch's weiter.... :D
Stefan (PB beginner (PureBasic_Demo_5.22) on M$ Windows 7, both 32+64)
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von NicTheQuick »

Stefan2 hat geschrieben:
ODER statt 'ReadProgramString()' einfach 'ReadProgramData()' zu nutzen
Diese Befehle haben doch mit meinem Anliegen gar nichts zu tun??? Ich will doch kein externes Programm starten und dessen Output einlesen???
Oder ich verstehe die Hilfe falsch?
Du hast Recht. Mein Fehler. Ich war wohl wieder ganz wo anders. :D

Ich habe leider sonst gerade keine Zeit mir das genauer anzuschauen, zumal es unter Linux wahrscheinlich wieder andere Tücken gibt wie unter Windows. Vielleicht meldet sich ja noch jemand anderes. Oder vielleicht habe ich morgen Zeit dafür.
Benutzeravatar
Regenduft
Beiträge: 574
Registriert: 25.03.2008 15:07
Wohnort: THE LÄÄÄND!

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von Regenduft »

Uiuiui... Pipes sind unter Windows ein ganzes Stolperstein-Baggerloch... ;)

Hast Du in PB in den Compileroptionen "Executeableformat: Console"?
Bei Windows sind Konsolen-Programme und "normale" Programme mit grafischer Oberfläche zwei unterschiedliche paar Schuhe.

Ein riesen Problem ist noch die Tatsache, dass Windows-Konsolenprogramme nicht den "normalen" ASCII-Zeichensatz (bzw. Codepage 819) verwenden, sondern den OEM-Zeichensatz (bzw. Codepage 437). Das ist original noch wie früher unter MS-DOS! Meistens merkt man nichts, aber ab und zu, wenn "komische Sachen passieren", dann kann das an OEM liegen.

Hier mal zwei kleine Prozeduren zum Umwandeln von OEM nach ASCII und umgekehrt. Das für den Anfang etwas kompliziert... Einfach vorerst mal blind benutzen und wenn Du die ersten Erfolge hattest, dann kannst Du ja versuchen die Prozeduren verstehen zu lernen!

Code: Alles auswählen

Procedure.s AsciiNachOem( Ascii$ )
    
  Define Oem$ = Space( Len( Ascii$ ) )
  If CharToOem_( Ascii$ , @Oem$ )
    ProcedureReturn Oem$
  EndIf
  ProcedureReturn #NUL$
    
EndProcedure

Procedure.s OemNachAscii( Oem$ )
  
  CompilerIf #PB_Compiler_Unicode
    
    Define l = MultiByteToWideChar_( #CP_OEMCP , 0 , @Oem$ , -1 , 0 , 0 )
    Define Ascii$ = Space( l )
    If MultiByteToWideChar_( #CP_OEMCP , 0 , @Oem$ , -1 , @Ascii$ , l )
      ProcedureReturn Ascii$
    EndIf
    ProcedureReturn #NUL$
    
  CompilerElse
    
    Define Ascii$ = Space( Len( Oem$ ) )
    If OemToChar_( @Oem$ , @Ascii$ )
      ProcedureReturn Ascii$
    EndIf
    ProcedureReturn #NUL$
    
  CompilerEndIf
  
EndProcedure
Falls es Dich genauer interessiert, was es mit den Zeichencodierungen auf sich hat:
http://de.wikipedia.org/wiki/Codepage_437
http://de.wikipedia.org/wiki/Ascii
http://de.wikipedia.org/wiki/Codepage_819

Einfach mal die Zeichentabellen vergleichen, dann hast Du schon einmal einen goben Überblick. :wink:
PureBasic 5.73 LTE x86/x64 | Windows 7 (x64)
Benutzeravatar
Stefan2
Beiträge: 5
Registriert: 03.06.2014 20:11
Computerausstattung: Windows XP, Win7 32- und 64-bit.
Meine Sprachen sind Deutsch, Englisch, DOS Batch, VBS, JS, PascalScript, PowerShell, XYScript, RegEx und seit Neusten ein bisschen FreePascal, und jetzt PB ;-)
Wohnort: Germany, EU

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von Stefan2 »

Danke für die Antwort.

Aber warum sollte es am Zeichensatz liegen?
Bei VBScript und Pascal war das ja auch kein Problem.
Und ich bekomme ja gar nichts aus der Pipe übermittelt....

Mein Code:

Code: Alles auswählen

EnableGraphicalConsole(0)
If OpenConsole()
    ;sInput$ = ProgramParameter(0)
    sInput$ = Input() 
    ;do here something useful
    PrintN("Got: "+sInput$)
  EndIf

End

Mein Ergebnis:

Code: Alles auswählen

C:\>echo ex.txt |myapp.exe
Got:

C:\>echo exa.txt |myapp.exe
Got:
Ein Prozess hat versucht, zu einer nicht bestehenden Pipe zu schreiben.

C:\>echo example.txt |myapp.exe
Got:
Ein Prozess hat versucht, zu einer nicht bestehenden Pipe zu schreiben.

C:\>myapp.exe example.txt
Got: example.txt

C:\>dir /b C:\Temp\*.txt |myapp.exe
Got:

C:\>

Gewünschtes Ergebnis:

Code: Alles auswählen

C:\>dir /b C:\Temp\*.txt |myapp.exe
Got: C:\Temp\test1.txt
Got: C:\Temp\testtest2.txt
Got: C:\Temp\testtesttest3.txt
Got: C:\Temp\testtesttesttest4.txt
...
C:\>

Hat hier keiner Erfahrung mit Konsolenapps?
Ich werde mal im englischsprachigem Forum posten...

Kann doch nicht so schwer sein mit PB? Oder?
Stefan (PB beginner (PureBasic_Demo_5.22) on M$ Windows 7, both 32+64)
Benutzeravatar
Danilo
-= Anfänger =-
Beiträge: 2284
Registriert: 29.08.2004 03:07

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von Danilo »

Stefan2 hat geschrieben:Gewünschtes Ergebnis:

Code: Alles auswählen

C:\>dir /b C:\Temp\*.txt |myapp.exe
Got: C:\Temp\test1.txt
Got: C:\Temp\testtest2.txt
Got: C:\Temp\testtesttest3.txt
Got: C:\Temp\testtesttesttest4.txt
...
C:\>
Das geht damit:

Code: Alles auswählen

If OpenConsole()
  *Buffer = AllocateMemory(1024)
  If *Buffer
      Repeat
          ReadSize = ReadConsoleData(*Buffer, 1024)      
          If ReadSize
              Print( PeekS(*Buffer,1024,#PB_Ascii) )
          EndIf
      Until ReadSize = 0
  EndIf
EndIf
Problem ist nur das ReadConsoleData() blockiert. Ohne Input bleibt das Programm da einfach hängen.

Ist wohl einfacher mit API OpenFile_(stdin, ...) usw.

Oder Du packst ReadConsoleData() in einen Thread und schießt Diesen nach einem gewissen TimeOut ab,
wenn sich nichts tut. Halt nicht so sehr schön. ;)
cya,
...Danilo
"Ein Genie besteht zu 10% aus Inspiration und zu 90% aus Transpiration" - Max Planck
Benutzeravatar
Danilo
-= Anfänger =-
Beiträge: 2284
Registriert: 29.08.2004 03:07

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von Danilo »

So kann man unter Windows prüfen ob überhaupt Input vorhanden ist, dann hängt es auch nicht mehr:

Code: Alles auswählen

Procedure.l ConsoleInputAvailable()
    PeekNamedPipe_(GetStdHandle_(#STD_INPUT_HANDLE),0,0,0,@bytesAvailable.l,0)
    ProcedureReturn bytesAvailable
EndProcedure


If OpenConsole()
    If ConsoleInputAvailable()
      *Buffer = AllocateMemory(1024)
      If *Buffer
          Repeat
              ReadSize = ReadConsoleData(*Buffer, 1024)      
              If ReadSize
                  Print( PeekS(*Buffer,1024,#PB_Ascii) )
              EndIf
          Until ReadSize = 0
      EndIf
    EndIf
EndIf
Als Console-Programm kompilieren.
cya,
...Danilo
"Ein Genie besteht zu 10% aus Inspiration und zu 90% aus Transpiration" - Max Planck
Benutzeravatar
Stefan2
Beiträge: 5
Registriert: 03.06.2014 20:11
Computerausstattung: Windows XP, Win7 32- und 64-bit.
Meine Sprachen sind Deutsch, Englisch, DOS Batch, VBS, JS, PascalScript, PowerShell, XYScript, RegEx und seit Neusten ein bisschen FreePascal, und jetzt PB ;-)
Wohnort: Germany, EU

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von Stefan2 »

Vielen Dank, Danilo.

Diesen Art Code habe ich bereits schon mal gesehen, aber nicht geglaubt, dass es so ""kompliziert"" ist.
Ich bin wohl doch eher höhere Sprachen gewöhnt :roll: . Da habe ich ja noch 'was vor mir :wink:

Mit deinem Code hat es prima funktioniert.
(nur bei der Verwendung von Functions/Procedure bekomme ich eine Fehlermeldung, wohl wegen Demoversion)

Code: Alles auswählen

If OpenConsole()
  ;first see if there was a parameter provided:
  input$ = ProgramParameter(0)
  If(input$ <> "") 
    ;funcDoIt(input$)
     Gosub doitlabel
     End
  Else
     ;else get stdin:
     ;Allocates a contiguous memory area With the specified size in bytes.
     *MemoryBlock = AllocateMemory(1024)
     If *MemoryBlock
        Repeat
          ;Reads raw input from the console. 
          ;like text redirected to the program through a pipe
              InputData = ReadConsoleData(*MemoryBlock, 1024)     
              If InputData
                ;PeekX = Reads a string from the specified memory address
                input$ = PeekS(*MemoryBlock,1024,#PB_Ascii)
                ;funcDoIt(input$)
                Gosub doitlabel
                input$ = ""   ;//empty the var to not get the output doubled sometimes
              EndIf
            Until InputData = 0
     Else
        Debug "Couldn't allocate the requested memory!"
     EndIf
  EndIf
EndIf

doitlabel:
;Procedure funcDoIt(input$)                
     ;do something useful 
     out$ = LCase(input$)
     ;...                
     ;...
     ;...

     ;write string back to console
     Print( out$ )
;EndProcedure
Return

(wo ist den hier der [ quote ] Button?)
So kann man unter Windows prüfen ob überhaupt Input vorhanden ist,
Danke. Das schau ich mir als nächstes an :allright:
Stefan (PB beginner (PureBasic_Demo_5.22) on M$ Windows 7, both 32+64)
Benutzeravatar
Danilo
-= Anfänger =-
Beiträge: 2284
Registriert: 29.08.2004 03:07

Re: Lese piped stdout eines Programms am stdin meines Progra

Beitrag von Danilo »

Stefan2 hat geschrieben:(nur bei der Verwendung von Functions/Procedure bekomme ich eine Fehlermeldung, wohl wegen Demoversion)
Ich denke Du kannst schon Prozeduren schreiben. Nur müssen die bei PB vor der ersten
Benutzung stehen, oder man muss sie vor der Benutzung mit 'Declare' deklarieren. :)
Stefan2 hat geschrieben:
So kann man unter Windows prüfen ob überhaupt Input vorhanden ist,
Danke. Das schau ich mir als nächstes an :allright:
Das wird dann wohl mit der Demo nicht gehen, wegen WinAPI-Funktion.
cya,
...Danilo
"Ein Genie besteht zu 10% aus Inspiration und zu 90% aus Transpiration" - Max Planck
Antworten