Forum: PC-Programmierung Serielle Daten mit Linux Bash


von Kilian (Gast)


Lesenswert?

Hallo!

Ich stehe vor folgendem Problem: Am seriellen Port kommt ein Datenstring 
rein, der mit <CR><LF> am Ende abgeschlossen ist.

Dieser String soll in eine Datei geschrieben werden. Mit diesem Script 
funktioniert es auch, allerdings nicht so, wie ich es gerne hätte:
1
while true
2
do
3
  read LINE < /dev/ttyS0
4
  echo $LINE >> datei.txt
5
  echo $LINE
6
done

Die Daten werden zwar aufgezeichnet, allerdings immer mit dem <CR><LF> 
am Ende, was zur Folge hat, das in der Datei nach jedem String eine 
Leerzeile entsteht.

Es müsste folgendes realisiert werden:
Daten werden empfangen, wenn <CR><LF> empfangen wird, wird der String 
gespeichert, allerdings ohne <CR><LF> am Ende. Jetzt wird eine neue 
Zeile angefangen und der nächste String gespeichert.

Vielleicht kann mir jemand helfen. Ich komme da einfach nicht weiter.

Danke schonmal.

Kilian

von Jürgen W. (lovos)


Lesenswert?

Ich würde mal ein
stty -F /dev/ttyS0 9600 raw (bzw. entsprechende Baudrate)
ausführen, damit eventuelle CR nicht in zusätzliche CR-NL übersetzt 
werden.

von Malte S. (maltest)


Lesenswert?

Was Jürgen sagt und ansonsten könnte | tr -d '\r\n' Dein Freund sein. 
socat kann auch \r\n <=> \n umsetzen etc.

von Rolf Magnus (Gast)


Lesenswert?

Du möchtest also den Zeilenumbruch entfernen und dafür einen 
Zeilenumbruch einfügen?

Malte S. schrieb:
> Was Jürgen sagt und ansonsten könnte | tr -d '\r\n' Dein Freund sein.
> socat kann auch \r\n <=> \n umsetzen etc.

oder auch einfach dos2unix.

von Werner H. (-heisenberg-)


Lesenswert?

1
while true
2
do
3
  read LINE < /dev/ttyS0
4
  LINE="${LINE%\\r\\n}"
5
  echo $LINE >> datei.txt
6
  echo $LINE
7
done

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

echo $LINE ist suboptimal, da nicht 1:1 transparent. Damit werden zum 
Beispiel führende Leerzeichen unterdrückt.

Wenn schon, dann

   echo "$LINE"

mit Anführungszeichen, damit Whitespaces erhalten bleiben.

Aber warum das Zeugs überhaupt in einer Shell-Variablen 
zwischenspeichern?

Und echo fügt doch den Zeilenumbruch wieder ein? Okay, es fügt ein 
simples '\n' statt einem "\r\n" ein, also einen UNIX-Zeilenumbruch.

Die einfachere Lösung ist:

man tee

Dann bleibt übrig:

IRGENDEIN-FILTER-WIE-TR < /dev/ttyS0 | tee datei.txt

Also z.B.

tr -d '\r' < /dev/ttyS0 | tee datei.txt

Der tr schmeisst also das CR ('\r') raus und lässt das NL ('\n') als 
Unix-Zeilentrenner drin. Der tee-Aufruf speichert die Zeichen in der 
Datei und gibt sie zusätzlich noch auf der Console aus.

Oder wenn man dos2unix hat:

dos2unix < /dev/ttyS0 | tee datei.txt

Was will man mehr? Simpler gehts nicht. Eine While-Schleife, die das TTY 
und die Datei immer wieder neu öffnet, ist einfach Verschwendung.

P.S.
Wenn schon eine while-Schleife, dann:

while true
   read $LINE           # Lesen aus TTY
   IRGENDEIN-FILTER
   echo "$LINE"         # Ausgabe in Datei
   echo "$LINE" >&2     # Ausgabe auf Console
done </dev/ttyS0 >datei.txt

Dann werden beide Dateien nur einmal geöffnet. Aber wie gesagt: Braucht 
man hier nicht, das wäre wie mit Kanonen auf Spatzen zu schießen. Die 
UNIX-Kommandos sind vielfältig und in Ihrer Kombination ein mächtiges 
Werkzeug. Leider beherrschen es immer weniger Leute richtig.

von Jens G. (jensig)


Lesenswert?

Ich hatte mal solch ein Konstrukt:

cat <file> | while read line
# tail -f -n 0 <file> | while read line
    do
          echo "$line" > <outfile>
    done

(entweder die cat, oder die tail-Zeile aktivieren, wobei das tail hier 
nicht unbedingt speziellen Sinn macht - ist halt aus einem meiner 
Scripte, wo es nötig war).
Ob das auch zusätzliche Breaks erzeugt bei der seriellen Schnittstelle, 
weis ich nicht. Aber bei normalen Inputfiles jedenfalls nicht.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jens G. schrieb:
> Ich hatte mal solch ein Konstrukt:
>
> cat <file> | while read line

Useless use of cat. Der Prozess ist hyperfluid.

Ein

  while read line .... done <file

macht dasselbe.

> # tail -f -n 0 <file> | while read line
>     do
>           echo "$line" > <outfile>
>     done
>
> (entweder die cat, oder die tail-Zeile aktivieren,

Deine while-Schleife mit cat macht exakt dasselbe wie ein

  dd if=infile of=outfile

oder

  cat infile >outfile

oder

  cp infile outfile

berücksichtigt also 2 Anforderungen des TOs nicht:

1. Umwandlung von \r\n nach \n
2. Zusätzliche Ausgabe auf der Console

Erklär mir bitte mal, warum Du so etwas wie da oben "in einem Deiner 
Scripte" benötigtest.

von Jens G. (jensig)


Lesenswert?

@Frank M. (ukw)

Eigentlich sollte das ja nur ein Denkanstoß sein, falls es von Vorteil 
sein sollte. Wenn Du meinst, daß es anders besser geht, dann ist auch 
gut.
Klar, die cat-Variante kopiert letztendlich nur ein File. Dieses File 
sollte aber eben /dev/ttyS0 sein in des TOs Aufgabe.

Auch war mir anfangs nicht ganz klar, woher das zweite crlf herkam 
(auser von den Ausgangsdaten) - inzwischen ist mir jetzt klar, daß jedes 
Echo da einen crlf reinmacht (da hatte ich anfangs nicht dran gedacht).

Aber um es Dir zu erklären: in meinem Scripts benutze ich die 
tail-Variante, nicht die cat-Variante (um bestimmte Dateien nach 
bestimmten neuen Strings permanent zu filtern, und bestimmte Aktionen 
auslösen zu können).
Das mit dem cat hatte ich jetzt nur schnell mal eingebaut für diesen 
thread als Anregung, was aber nun offensichtlich keinen Sinn mehr macht.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jens G. schrieb:
> Eigentlich sollte das ja nur ein Denkanstoß sein, falls es von Vorteil
> sein sollte. Wenn Du meinst, daß es anders besser geht, dann ist auch
> gut.

Ist schon okay. Ich wollte nur damit ausdrücken, dass man auf cat in 99% 
aller Fälle verzichten kann. Der Ausdruck "Useless use of cat" ist dafür 
ein gängiges Stichwort in den einschlägigen Kreisen. Google sollte Dir 
da viele tausend Treffer zeigen.

> Klar, die cat-Variante kopiert letztendlich nur ein File. Dieses File
> sollte aber eben /dev/ttyS0 sein in des TOs Aufgabe.

Ja, aber auch beim /dev/ttyS0 bleibt das cat überflüssig. Auch hier geht 
Redirection. Unter UNIX/Linux bleibt eine Datei eine Datei. Egal, ob 
sich eine reale Datei im Filesystem oder ein Gerät dahinter verbirgt.

> Auch war mir anfangs nicht ganz klar, woher das zweite crlf herkam
> (auser von den Ausgangsdaten) - inzwischen ist mir jetzt klar, daß jedes
> Echo da einen crlf reinmacht (da hatte ich anfangs nicht dran gedacht).

Falsch: Ein echo erzeugt unter UNIX nur einen LF, aber kein CR. Der TO 
bekommt das CRLF-Paar von der seriellen Schnittstelle und will das CR 
nicht in der Protokoll-Datei, weil das eben UNIX-unüblich ist.

> Aber um es Dir zu erklären: in meinem Scripts benutze ich die
> tail-Variante, nicht die cat-Variante (um bestimmte Dateien nach
> bestimmten neuen Strings permanent zu filtern, und bestimmte Aktionen
> auslösen zu können).

Okay. Deine Script-Ausschnitt hat dann aber gar nichts mit dem 
TO-Problem zu tun.

Gruß,

Frank

von Malte S. (maltest)


Lesenswert?

Frank M. schrieb:
>> cat <file> | while read line
>
> Useless use of cat. Der Prozess ist hyperfluid.
>
> Ein
>
>   while read line .... done <file
>
> macht dasselbe.

Und noch schlimmer.
Bei dem Konvolut
1
cat file | while read line; do ... done
kannst Du aufgrund der Pipe z.B. vergessen, in ... irgendwelche 
Variablen zuzuweisen und deren Werte hinterher noch verwenden zu können. 
Da guckt die Katze in die Röhre.

Mit
1
while read line; do ... done < file
ist das kein Problem.

von ein anderer (Gast)


Lesenswert?

Frank M. schrieb:
> Falsch: Ein echo erzeugt unter UNIX nur einen LF, aber kein CR. Der TO
> bekommt das CRLF-Paar von der seriellen Schnittstelle und will das CR
> nicht in der Protokoll-Datei, weil das eben UNIX-unüblich ist.
Wie sicher ist denn das?
Kilian schrieb:
> Die Daten werden zwar aufgezeichnet, allerdings immer mit dem <CR><LF>
> am Ende, was zur Folge hat, das in der Datei nach jedem String eine
> Leerzeile entsteht.
Daraus erlese ich erstmal, dass eine Leerzeile zuviel da ist. Ob das 
wirklich \r\n ist oder doch nur \n sehe ich da nicht, das vermutet 
Kilian bis jetzt nur.
Ich vermute eher, das Kilian gar nicht weiß, dass es unter Linux nur das 
\n ist.
Ich würde erstmal
1
echo -n $LINE
probieren, damit fügt echo kein zusätzlichen Zeilenmubruch an.

von Malte S. (maltest)


Lesenswert?

ein anderer schrieb:
> Ich würde erstmal
>   echo -n $LINE
> probieren, damit fügt echo kein zusätzlichen Zeilenmubruch an.

Aber dann bitte
1
echo -n "$LINE"
sonst ist das wieder underquoted.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

ein anderer schrieb:
> Frank M. schrieb:
>> Falsch: Ein echo erzeugt unter UNIX nur einen LF, aber kein CR. Der TO
>> bekommt das CRLF-Paar von der seriellen Schnittstelle und will das CR
>> nicht in der Protokoll-Datei, weil das eben UNIX-unüblich ist.
> Wie sicher ist denn das?

Todsicher:

# echo "" | hexdump -c
0000000  \n
0000001

> Ich würde erstmal
> echo -n $LINE
> probieren, damit fügt echo kein zusätzlichen Zeilenmubruch an.

Der Befehl

   read $LINE

liest alles bis zum Newline \n - aber ausschließlich.
Es kann also kein Newline in $LINE sein. Wenn Du echo mit -n aufruft, 
dann hast Du eine ganz laaaaaaaaaange Zeile - höchstens durchsetzt mit 
CR.

Dir fehlen essentielle UNIX-/Linux-Grundlagen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Malte S. schrieb:
> Bei dem Konvolut
>
1
cat file | while read line; do ... done
> kannst Du aufgrund der Pipe z.B. vergessen, in ... irgendwelche
> Variablen zuzuweisen und deren Werte hinterher noch verwenden zu können.

Korrekt. Denn hier wird die while-Schleife zu einem Kindprozess und die 
Variablen sind dann nur lokal in der while-Schleife gültig. Ganz böse.

von Jens G. (jensig)


Lesenswert?

@Malte S. (maltest)

>Und noch schlimmer.
> ...

Ich merke schon - ich werde heute ganz miesgelaunt heim gehen ... ;-)

von Jens G. (jensig)


Lesenswert?

@Frank M. (ukw) Benutzerseite
>Der Befehl

>   read $LINE

>liest alles bis zum Newline \n - aber ausschließlich.
>Es kann also kein Newline in $LINE sein. Wenn Du echo mit -n aufruft,
>dann hast Du eine ganz laaaaaaaaaange Zeile - höchstens durchsetzt mit
>CR.

Wie paßt das zusammen mit dem Problem des TO?
Er hat schließlich offensichtlich zwei LF drin, weil er ja immer eine 
zusätzliche Leerzeile drin hat.
Wenn read $LINE grundsätzlich keine LF enthält, sollte ja nur ein LF vom 
Echo in der Datei landen. Wo kommt also das zweite her? Das CR selbst 
hat wohl keine steuernde Wirkung unter unix, wenn ich es richtig sehe 
(das wird wohl einfach als ^M angezeigt, und nicht als neue Zeile).

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jens G. schrieb:
> Wenn read $LINE grundsätzlich keine LF enthält, sollte ja nur ein LF vom
> Echo in der Datei landen. Wo kommt also das zweite her? Das CR selbst
> hat wohl keine steuernde Wirkung unter unix, wenn ich es richtig sehe
> (das wird wohl einfach als ^M angezeigt, und nicht als neue Zeile).

Das kommt darauf an. less bzw. vi zeigen es als ^M, bei cat sieht man 
das CR gar nicht.

Grund: Der Consolentreiber wandelt das einzelne \n für das Terminal(!) 
sowieso um in ein \r\n, damit der Cursor nicht nur eine Zeile runter 
geht (NL), sondern auch noch an den Anfang der Zeile geht (CR), siehe 
auch [1]. Ein weiteres CR vom cat ausgegeben bewirkt dann in dem Moment 
gar nichts.

Aber wer weiß, mit welchem Programm der TO seine datei.txt betrachtet. 
Da kann alles mögliche bei einem CR passieren.... Am besten macht er mal 
einen Hexdump.

Meine vorgeschlagene Lösung

tr -d '\r' < /dev/ttyS0 | tee datei.txt

ist auf jeden Fall die sinnvollste Methode. Dann noch einen Hexdump 
drauf und man kann auch sehen, was da tatsächlich über die Leitung geht 
bzw. in der Datei landet. Alles andere ist Spekulation.

[1] "stty onlcr" für die Console steuert das: Wenn ein \n ausgegeben 
werden soll, dann wird es auf dem TTY bei der Ausgabe in ein \r\n 
gewandelt. Aber mit der Datei datei.txt hat dies nichts zu tun. Weitere 
Infos dazu: man stty.

Außerdem könnte /dev/ttyS0 (jetzt also nicht die Console, sondern die 
serielle Schnittstelle) natürlich noch mit dem Flag icrnl eingestellt 
sein. In diesem Fall wird jedes reinkommende \r in ein \n gewandelt.

Folge: der TO sieht Doppel-Newlines! Ja, das passt.

Das kann er mit

  stty -a </dev/ttyS0

schnell überprüfen.

In diesem Fall sollte er das mit

  stty -icrnl </dev/ttyS0

abstellen.

Also Fazit:

Mit

  stty -icrnl </dev/ttyS0

und anschließendem

  tr -d '\r' < /dev/ttyS0 | tee datei.txt

sollte es dann sauber laufen.

von Kilian (Gast)


Lesenswert?

Hallo zusammen!

Danke schonmal für die zahlreichen und auch hilfreichen Antworten. 
Leider komme ich bei meinem Problem immer noch nicht so ganz weiter.

Hier nochmal die Anforderungen, die realisiert werden sollten:

-es kommt an ttyS0 ein String "bla bla bla<CR><LF>" an
-dieser sollte unverfälscht in eine Datei geschrieben werden (also mit 
<CR><LF>)
-gleichzeitig sollte der String am Terminal ausgegeben werden, 
allerdings immer ein String pro Zeile

Ich habe es bis jetzt so versucht:
1
stty -F /dev/ttyS0 9600 raw
2
3
read LINE < /dev/ttyS0
4
echo $LINE >> store.txt
5
echo $LINE

Allerdings ist das <CR> und <LF> in der Datei dann weg (wenn ich diese 
auf einem Windows System betrachte).

Ich hab auch schon verschiedene Vorschlage aus diesem Thread versucht, 
allerdings ohne Erfolg. Ein tee kann ich nicht verwenden, da bei jedem 
Schleifendurchlauf die Dateigröße mit
1
FS=$(ls l datei.txt | tr -s " " | cut -d " " -f 5)
2
if [ $FS -gt 5000000 ]
3
  then
4
     ...
5
  else
6
    ...
7
fi

überprüft werden muss.

Ich denke das es mit stty etwas zu tun hat. Aber ich steige da nicht 
wirklich durch welche Parameter gesetzt werden müssen.

Kilian

von Malte S. (maltest)


Lesenswert?

Kilian schrieb:
> -es kommt an ttyS0 ein String "bla bla bla<CR><LF>" an
> -dieser sollte unverfälscht in eine Datei geschrieben werden (also mit
> <CR><LF>)
> -gleichzeitig sollte der String am Terminal ausgegeben werden,
> allerdings immer ein String pro Zeile

Das klingt für mich immer noch nach tee:
1
tee < /dev/ttyS0 store.txt | tr -d \\r

Da ich grad nichts serielles hier habe, ist das ne reine Trockenübung.

> Ein tee kann ich nicht verwenden, da bei jedem
> Schleifendurchlauf die Dateigröße mit
[...]
> überprüft werden muss.

Okay, DEN Teil habe ich auch erst jetzt gesehen. Dann eben ohne tee. Es 
sei denn, es geht nur darum, die Datei ab einer bestimmten Größe zu 
splitten? Dann könnte Dir split(1) helfen:

Ausgabe auf Dateien mit max. 10000 Zeilen aufteilen:
1
(tee </dev/ttyS0 /dev/stderr | split -l10000 - store.txt) 2>&1 | tr -d \\r
Geht bestimmt eleganter, nur als Denkanstoss zur Vermeidung der nicht so 
optimalen Schleife.

Was auch immer Du tust,
1
echo $LINE
ohne Quoting verfälscht nicht nur <CR><LF>, sondern auch sämtliche 
Sequenzen von Leerzeichen und co.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Kilian schrieb:

> -dieser sollte unverfälscht in eine Datei geschrieben werden (also mit
> <CR><LF>)

Also cp, cat oder dd.

> -gleichzeitig sollte der String am Terminal ausgegeben werden,

tee.

> allerdings immer ein String pro Zeile

tee </dev/ttyS0 datei.txt

> read LINE < /dev/ttyS0

Liest bis zum '\n' - ausschließend.

> echo $LINE >> store.txt

Schreibt den gelesenen Inhalt plus '\n'.

Hier fehlen allerdings wieder die doppelten Anführungszeichen, die ich 
hier schon erwähnt habe. Damit dürfte auch Dein CR verschwinden, da 
dieses ein Whitespace ist und ohne Anführungszeichen im Nirvana 
verschwindet.

Also:

  echo "$LINE" >> store.txt

> Allerdings ist das <CR> und <LF> in der Datei dann weg (wenn ich diese
> auf einem Windows System betrachte).

Schlecht, Begründung siehe oben.

> Ich hab auch schon verschiedene Vorschlage aus diesem Thread versucht,
> allerdings ohne Erfolg. Ein tee kann ich nicht verwenden, da bei jedem
> Schleifendurchlauf die Dateigröße mit
>
> [code]
> FS=$(ls l datei.txt | tr -s " " | cut -d " " -f 5)
> if [ $FS -gt 5000000 ]

Bis dahin war davon aber keine Rede! Wenn Du hier dauernd die Aufgaben 
neu stellst, macht das keinen Spaß mehr :-(

Da kann ich Dir nur noch den Tipp geben, ein eigenes kleines C-Programm 
zu schreiben, was alle Deine oben genannten Anforderungen erfüllt. Ist 
wirklich nicht schwierig.

Dann kannst Du Deine Anforderungen auch beliebig neu formulieren und 
wieder neu lösen, ohne hier die Leser zu verärgern. Für mich ist 
jedenfalls jetzt hier Schluss.

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.