Forum: PC-Programmierung Warum funktioniert mein c++ stream iterator basiertes cat nicht?


von Z. Klatscher (Gast)


Lesenswert?

Liebe Leute, was muss ich ändern, damit mein kleines cat Programm so 
geht wie das von GNU coreutils 8.31? Ich zeigs euch
1
#include <algorithm>
2
#include <fstream>
3
#include <iostream>
4
#include <iterator>
5
using namespace std;
6
int main (int argc, char** argv) {
7
        if (1 == argc) {
8
                copy (istream_iterator <char> (cin)
9
                                , istream_iterator <char> ()
10
                                , ostream_iterator <char> (cout));
11
        } else {
12
                for (size_t n = 1; n < argc; ++n) {
13
                        auto a = ifstream (argv [n]);
14
                        copy (istream_iterator <char> (a)
15
                                        , istream_iterator <char> ()
16
                                        , ostream_iterator <char> (cout));
17
                }
18
        }
19
        return 0;
20
}
soll so machen
1
% cat
2
bla
3
bla
4
blub
5
blub
6
blib
7
blib
macht aber
1
./cat
2
bla
3
blablub
4
blubblib
5
blib
Am liebsten mit Iteratoren und auch mit C glücklich. Besten Dank

von zer0@away (Gast)


Lesenswert?

Vorsichtige Behauptung: Mit den streams wirst du da nicht so einfach 
glücklich. Die Dinger sind für formatierte Text-Operationen gedacht. Das 
heißt, dass alles was gelesen wird, erstmal durch die Mangel gedreht 
wird und irgendwelche Character-Encodings aus der locale angewendet 
werden usw.

Um eine Datei garantiert unverändert einzulesen müsstest du schon mit 
ios::binary und istream::read arbeiten oder ein Guru bezüglich der Spec 
sein, die da zB sagt
1
When reading characters, std::istream_iterator skips whitespace by default (unless disabled with std::noskipws or equivalent), while std::istreambuf_iterator does not. In addition, std::istreambuf_iterator is more efficient, since it avoids the overhead of constructing and destructing the sentry object once per character.
Ich würde mich nicht darauf verlassen, dass ein Text-Stream unverändert 
ankommt.
https://en.cppreference.com/w/cpp/io/c#Binary_and_text_modes

Um den binary mode auf cin (stdin) zu setzen, hilft vielleicht
1
freopen(0, "rb", stdin);
Aber das würde ich an deiner Stelle noch einmal prüfen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Du brauchst einen istreambuf_iterator<char>.

von zer0@away (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Du brauchst einen istreambuf_iterator<char>.

Auf welchen Teil der Spec beziehst du dich da, dass der streambuf 
garantiert "unverfälschte" Daten enthält?
Ich lese da
1
The character representation and encoding in the controlled sequence may be different from the character representations in the associated sequence

Im Speziellen
1
Data read in from a text stream is guaranteed to compare equal to the data that were earlier written out to that stream only if all of the following is true: ...
1
A binary stream is an ordered sequence of characters that can transparently record internal data. Data read in from a binary stream always equals to the data that were earlier written out to that stream. Implementations are only allowed to append a number of null characters to the end of the stream.

Ist da garantiert, dass diese Nullen nicht schon im Buffer auftauchen?

von Z. Klatscher (Gast)


Angehängte Dateien:

Lesenswert?

zer0@away schrieb:
> Vorsichtige Behauptung: Mit den streams wirst du da nicht so einfach
> glücklich.

Danke für deine Antwort. Jetzt hab ichs mit unformatiertem input. Das 
müsste ja jetzt auch im Textmode gehen und das lineending der Platform 
nehmen?

cat.cpp
1
#include <fstream>
2
#include <iostream>
3
#include <string>
4
using namespace std;
5
int main (int argc, char** argv) {
6
        if (1 == argc) {
7
                for (string s; getline (cin, s);) {
8
                        cout << s << endl;
9
                }
10
        } else {
11
                for (size_t n = 1; n < argc; ++n) {
12
                        auto a = ifstream (argv [n]);
13
                        for (string s; getline (a, s);) {
14
                                cout << s << endl;
15
                        }
16
                }
17
        }
18
        return 0;
19
}

Falls es jemand braucht, im Anhang cat.exe für Windows 32.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Beim Lesen eines char über einen input_iterator wird sämtlicher
Whitespace übersprungen.

Die Verwendung von getline kann Probleme bei überlangen Textzeilen
machen.

Ich würde es so machen:

1
#include <fstream>
2
#include <iostream>
3
4
using namespace std;
5
6
int main (int argc, char **argv) {
7
  if (argc == 1)
8
    cout << cin.rdbuf();
9
  else {
10
    for (int n = 1; n < argc; ++n)
11
      cout << ifstream(argv[n]).rdbuf();
12
  }
13
  return 0;
14
}

: Bearbeitet durch Moderator
von Heiko L. (zer0)


Lesenswert?

Z. Klatscher schrieb:
> Das
> müsste ja jetzt auch im Textmode gehen und das lineending der Platform
> nehmen?

Die Frage wäre, ob du das willst:
1
On systems like MS-DOS that distinguish between text and binary files, cat normally reads and writes in binary mode. However, cat reads in text mode if one of the options -bensAE is used or if cat is reading from standard input and standard input is a terminal.
https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html#cat-invocation

Das normale Verhalten ist, die Datei unverändert zu lassen. Man weiß 
nie, auf welcher Platform welche Datei geschrieben wurde und auch nicht, 
ob es eine Binärdatei ist. Das resultierende Chaos sieht man immer, 
wenn man Dateien zwischen Posix und Windows hin- und herschiebt.
Ich würde ein cat, dass per Default Dateien verändert, nicht gerade 
begrüßen... >:

von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Ich würde ein cat, dass per Default Dateien verändert, nicht gerade
> begrüßen... >:

Ich auch nicht. Die Hauptaufgabe von cat (wenn auch oft für anderes 
benutzt) ist ja eigentlich, mehrere Dateien zu einer zusammenzufügen 
(conCATenate). Und das sollte natürlich nicht nur mit Text-, sondern 
auch mit Binärdateien sauber funktionieren.

von Wilhelm M. (wimalopaan)


Lesenswert?

Z. Klatscher schrieb:
> Falls es jemand braucht, im Anhang cat.exe für Windows 32.

Ich hoffe, es benutzt niemand, denn es ist falsch!

Hier wird immer(!) ein endl am Ende der Datei angefügt, auch wenn in der 
Ursprungsdatei keins drin ist. Die Datei wird also in diesem Fall um 1 
Byte länger.

Richtig sind:

Beitrag "Re: Warum funktioniert mein c++ stream iterator basiertes cat nicht?"

oder

Beitrag "Re: Warum funktioniert mein c++ stream iterator basiertes cat nicht?"

von DPA (Gast)


Lesenswert?

Wozu das ganze? Cat gibt es doch schon, wenn du es brauchst, nimm doch 
ein bestehendes. Unter windows kann man vergleichbares zeug über ein 
paar Umwege übrigens auch mit nativen Tools machen.

Dateien zusammensetzen, cat.bat:
1
@ECHO OFF
2
:Loop
3
IF "%1"=="" GOTO Continue
4
   type "%1"
5
SHIFT
6
GOTO Loop
7
:Continue
1
cat.bat a.txt b.txt

Consolen Eingabe auf console ausgeben: (nicht das selbe wie stdin, 
windows hat nichts äquivalentes zu /dev/stdin bzw. /dev/fd/0 btw. 
/proc/self/fd/0, geht also nicht mit dem Output des Vorgängerprogramms.)
1
type CON

Klar, unter Windows ist das alles etwas Komplexer, zum einfachen 
Scripting echt nicht zu gebrauchen, aber wenn man was richtiges will, 
muss man halt auch das echte & gute zeug nehmen, statt den Windows mist.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.