Ich habe einen Strom von Binärdaten, der über eine Pipe hereinströmt. Danach
wandern die Daten in ein FTP-Programm, dass sie "on-the-fly" auf einen Server
lädt. Das funktioniert alles super, nur scheint es auch FTP-Server zu geben,
die ein recht niedriges Limit bezüglich der Dateigröße haben. Ich muss also den
Datenstrom in mehrere Teile spalten, um ihn auf den Server zu laden. In Perl
habe ich soetwas schonmal realisiert, in reinem Bash-Code fällt es mir jedoch
schwer. Ich skizziere mal grob, wie das ganze laufen soll:
chunk=1; tar ... | ( while readBinary 1000m | ftp-programm ...$chunk... ; do let chunk++; done );
Ich suche also ein Programm, dass eine definierte Anzahl Bytes liest, diese per
STDOUT ausgibt und im Exit-Code anzeigt, ob es über EOF gestolpert ist. Das
builtin "read" scheint leider keine Binärdaten verarbeiten zu können, und dd
lässt nicht durchblicken, ob es EOF erreicht hat.
Hat jemand eine Idee dazu?
Ich muss es leider in bash machen, da "ftp-programm" u.U. auch eine
Shellfunktion sein kann.
--
ste...@pico.ruhr.de
GPG available and welcome
http://stefans.datenbruch.de/
> Ich suche also ein Programm, dass eine definierte Anzahl Bytes
> liest, diese per STDOUT ausgibt und im Exit-Code anzeigt, ob es
> über EOF gestolpert ist. Das builtin "read" scheint leider keine
> Binärdaten verarbeiten zu können, und dd lässt nicht durchblicken,
> ob es EOF erreicht hat.
>
> Hat jemand eine Idee dazu?
Zweimal lesen, zur Not zweimal mit dd. Warum soll eigentlich read
keine Binärdaten lesen können?
Wenn der erste dd-Aufruf die gesamten anstehenden Daten verarbeitet
hat, sieht der zweite (auf stderr!) so aus:
dd of=/dev/null bs=1 count=1
0+0 Datensätze ein
0+0 Datensätze aus
0 Bytes (0 B) kopiert, 0,00250787 s, 0,0 kB/s
Wenn nicht, steht da statt 0+0 halt was anderes. Das sollte man
auswerten können. Oder halt ein Zeichen mit read lesen und den exit
code angucken. Ich sehe da kein Problem, lasse mich aber gerne
belehren ;-)
CU
Hauke
--
http://www.hauke-laging.de/ideen/
Wie können 59.054.087 Leute nur so dumm sein?
> Zweimal lesen, zur Not zweimal mit dd. Warum soll eigentlich read
> keine Binärdaten lesen können?
Ich habe damit schon etwas experimentiert, aber Zeichen wie Nullbytes, Newlines
etc. machen da vermutlich probleme, zumal read auch nicht auf stdout ausgibt,
sondern das ganze in eine Variable schreibt, die dann der Gnade von "echo"
ausgeliefert ist.
> Wenn der erste dd-Aufruf die gesamten anstehenden Daten verarbeitet
> hat, sieht der zweite (auf stderr!) so aus:
Das ist mir klar.
> Wenn nicht, steht da statt 0+0 halt was anderes. Das sollte man
> auswerten können. Oder halt ein Zeichen mit read lesen und den exit
> code angucken. Ich sehe da kein Problem, lasse mich aber gerne
> belehren ;-)
Ich sehe keine elegante Möglichkeit, das ganze im Rahmen einer Pipe
zu verarbeiten, außer den Kram in eine temporäre Datei zu schreiben und
dann von dort zu lesen. Außerdem verändert sich der Exit-Code von dd eben
nicht, sonst wäre das ganze ziemlich einfach:
stefan@nano:~$ echo -n f | dd of=/dev/null bs=1 count=1; echo "exit code: $?"
1+0 records in
1+0 records out
1 byte (1 B) copied, 2.7657e-05 s, 36.2 kB/s
exit code: 0
stefan@nano:~$ echo -n | dd of=/dev/null bs=1 count=1; echo "exit code: $?"
0+0 records in
0+0 records out
0 bytes (0 B) copied, 2.0114e-05 s, 0.0 kB/s
exit code: 0
stefan@nano:~$
Also mein Ansatz wäre es, mit dd in eine temporäre Datei zu schreiben
und anschließend die Dateigröße abzufragen.
dd count=1 bs=4096 of=datei.tmp
if [ -n `find . -maxdepth 1 -name datei.tmp -size 4096` ]; then
echo Volle Datei
else
echo Nicht voll
fi
--
usingwhitespaceisracism
> Also mein Ansatz wäre es, mit dd in eine temporäre Datei zu schreiben
> und anschließend die Dateigröße abzufragen.
Temporäre Dateien wollte ich vermeiden, da es sich um Volumina zwischen
0,5 und 1 Gibibyte handelt. Ein Programm namens "binread", dass als Argument
einen Dateinamen und eine Bytemenge annimmt und sein Tun über den exit-Code
referiert, scheint eine Marktlücke in den Coreutils zu sein. Vielleicht kann
man ja "head" irgendwie patchen...
Ansatz zum Weiterexperimentieren:
cat test.file | while :; do (dd bs=7000 count=1 | (wc -c 2>/dev/null > /dev/tty)) 2>&1 | fgrep -q '0+0' && break; done
wc -c > /dev/tty kann dabei durch etwas sinnvolleres ersetzt werden.
Grüße, Hansjörg
> Hauke Laging <4q2...@hauke-laging.de> schrieb:
>
>> Zweimal lesen, zur Not zweimal mit dd. Warum soll eigentlich read
>> keine Binärdaten lesen können?
>
> Ich habe damit schon etwas experimentiert, aber Zeichen wie
> Nullbytes, Newlines etc. machen da vermutlich probleme, zumal read
> auch nicht auf stdout ausgibt, sondern das ganze in eine Variable
> schreibt, die dann der Gnade von "echo" ausgeliefert ist.
Mir erschließt sich der Zusammenhang von "habe experimentiert"
und "vermutlich" nicht so recht...
hl@server:~> echo -ne a\\0b | (i=0;while read -r -n 1 char; do
((i++)); echo $i; done)
1
2
3
echo -e a\\0b | (i=0;while read -r -n 1 char; do ((i++)); echo $i;
done)
1
2
3
4
Ergo: kein Problem.
hl@server:~> bash --version
GNU bash, version 3.2.25(1)-release (i586-suse-linux-gnu)
> Ich sehe keine elegante Möglichkeit, das ganze im Rahmen einer Pipe
> zu verarbeiten
Hatte ich das nicht gerade erklärt? Ein Pipeelement kann (s.o.)
mehrere Kommandos beinhalten. Es ist nirgendwo festgelegt, dass der
gesamte Input von einem einzelnen Programm gelesen oder der gesamte
Output von einem einzelnen erzeugt werden müsse.
> Mir erschließt sich der Zusammenhang von "habe experimentiert"
> und "vermutlich" nicht so recht...
>
> hl@server:~> echo -ne a\\0b | (i=0;while read -r -n 1 char; do
> ((i++)); echo $i; done)
> 1
> 2
> 3
>
> echo -e a\\0b | (i=0;while read -r -n 1 char; do ((i++)); echo $i;
> done)
> 1
> 2
> 3
> 4
>
> Ergo: kein Problem.
Prima, dann kann ich i also inkrementieren. Aber schonmal versucht, das
gelesene Zeichen auch weiterzuverwenden?
stefan@nano:~$ echo -ne '\0' | hexdump
0000000 0000
0000001
stefan@nano:~$ echo -ne '\0' | (i=0;while read -r -n 1 char; do echo -en "$char"; done) | hexdump
stefan@nano:~$
Auch Variationen mit verschiedenen echo-Kommando-Varianten führen nicht zum
Erfolg. Das Nullbyte signalisiert nunmal das Ende des Strings in der Variablen.
Achja, und versuch doch mal auch direkt, echo den String "-n" ausgeben zu
lassen. Binärkompatibel sieht anders aus.
Ergo: Problem.
> Hatte ich das nicht gerade erklärt? Ein Pipeelement kann (s.o.)
> mehrere Kommandos beinhalten. Es ist nirgendwo festgelegt, dass der
> gesamte Input von einem einzelnen Programm gelesen oder der gesamte
> Output von einem einzelnen erzeugt werden müsse.
s.o.
> Ich suche also ein Programm, dass eine definierte Anzahl Bytes liest, diese per
> STDOUT ausgibt und im Exit-Code anzeigt, ob es über EOF gestolpert ist.
Geht neben dem von mir vorhin geposteten, evtl. unportablen bestimmt
auch mit einem Perl-Einzeiler.
Da ich kein Perl kann, hier eine Python-Alternative:
r() {
python -c 'import sys,os; s=os.read(0,1); os.write(1,s);
sys.exit(int(not s))';
}
Diese Shell-Funktion läßt sich (natürlich ebenfalls unportabel, da
leider nicht überall Python installiert ist) dann in dem Sinne verwenden:
$ echo -ne 'a\0b\0c' | while char=`r`; do if [ -n "$char" ]; then printf
'%s' "$char"; else printf '\0'; fi; done | hexdump -C
00000000 61 00 62 00 63 |a.b.c|
00000005
HTH,
Thomas
echo -ne 'a\0b\0c' |
while read -r -n 1 -d '' char; do
if [ -n "$char" ]
then printf '%s' "$char"
else printf '\0'
fi
done |
hexdump -C
00000000 61 00 62 00 63 |a.b.c|
00000005
Funktioniert in bash. Inwieweit diese -d ''-Konstruktion portabel ist,
weiß ich nicht, ob -d '\0' oder -d $'\0' portabler wäre, auch nicht.
(Intern wird dabei der Effekt ausgenutzt, daß der übergebene Leerstring
mit einem Nullbyte endet...)
> Achja, und versuch doch mal auch direkt, echo den String "-n" ausgeben zu
> lassen. Binärkompatibel sieht anders aus.
Dann ist echo eben nicht binärkompatibel. Hat mit read nicht allzu viel
zu tun.
Thomas
Hm, könnte funktionieren, allerdings vermutlich nicht wirklich performant,
für jedes einzelne Byte, also ein paar Millionen mal, einen Python-Interpreter
zu starten.
Dabei geht auch irgendwas kaputt (und es dauert ewig):
stefan@nano:~$ cat /boot/vmlinuz-2.6.26-1-686 | while read -r -n 1 -d '' char; do
if [ -n "$char" ]
then printf '%s' "$char"
else printf '\0'
fi
done | > /tmp/kernel
stefan@nano:~$ md5sum /tmp/kernel /boot/vmlinuz-2.6.26-1-686
c3685377128871fd085414b10e7e37f4 /tmp/kernel
6ad064a2417d1bd9675e70db90fa786d /boot/vmlinuz-2.6.26-1-686
stefan@nano:~$ ls -l /tmp/kernel /boot/vmlinuz-2.6.26-1-686
-rw-r--r-- 1 root root 1504240 9. Okt 22:06 /boot/vmlinuz-2.6.26-1-686
-rw-r--r-- 1 stefan stefan 1504240 12. Nov 08:49 /tmp/kernel
> Dabei geht auch irgendwas kaputt
Und was? cmp verrät es Dir.
> (und es dauert ewig):
Das ist ja auch nicht Sinn der Sache, alles so einzulesen.
Du holst mit dd (maximal) den möglichen Block. Die Ausgabe von dd
verrät Dir, ob der volle Block eingelesen wurde. Wenn nicht, ist die
Sache klar. Wenn doch, holst Du mit der bash (geht schneller :-) )
ein einzelnes Zeichen, um zu sehen, ob mehr Daten anstehen, als Du
blockweise verarbeiten kannst. Wenn ja, schiebst Du das eine Zeichen
und dann den Rest der Eingabe in den nächsten dd-Block. Bis
irgendwann mal das Ende erreicht ist.
CU
> Dabei geht auch irgendwas kaputt
Und was? cmp verrät es Dir.
> (und es dauert ewig):
Das ist ja auch nicht Sinn der Sache, alles so einzulesen.
Du holst mit dd (maximal) den möglichen Block. Die Ausgabe von dd
verrät Dir, ob der volle Block eingelesen wurde. Wenn nicht, ist die
Sache klar. Wenn doch, holst Du mit der bash (geht schneller :-) )
ein einzelnes Zeichen, um zu sehen, ob mehr Daten anstehen, als Du
blockweise verarbeiten kannst. Wenn ja, schiebst Du das eine Zeichen
und dann den Rest der Eingabe in den nächsten dd-Block. Bis
irgendwann mal das Ende erreicht ist.
CU
Hauke
stefan@nano:~$ cmp -lb /boot/vmlinuz-2.6.26-1-686 /tmp/kernel
22 40 0 ^@
25 11 ^I 0 ^@
52 40 0 ^@
60 40 0 ^@
65 40 0 ^@
72 40 0 ^@
75 40 0 ^@
78 40 0 ^@
85 40 0 ^@
97 12 ^J 0 ^@
[...]
Irgendwas scheint an deinem Algo noch faul zu sein :-/
> Hauke Laging <4q2...@hauke-laging.de> schrieb:
>> Stefan Tomanek schrieb am Mittwoch 12 November 2008 07:51:
>>
>>> Dabei geht auch irgendwas kaputt
>>
>> Und was? cmp verrät es Dir.
>
> stefan@nano:~$ cmp -lb /boot/vmlinuz-2.6.26-1-686 /tmp/kernel
> 22 40 0 ^@
> [...]
>
> Irgendwas scheint an deinem Algo noch faul zu sein :-/
Laut SUSv3 erwartet read an stdin ein "text file". Somit unterliegt
sein Input Beschränkungen (Zeichensatz, Zeilenlänge, ...) und das
Füttern mit Binärdaten erzeugt "undefined behaviour": Kann gehen, muss
aber nicht, und wenns beim Rumprobieren aussieht, als ginge es, sagt das
nur aus, dass es für die Beispieldaten geht, erlaubt aber keine
allgemeinen Schlüsse (wie soeben erlebt).
Also sind wir wieder auf dem Stand, den ich im Ursprungsposting beschrieben
habe: Das builtin "read" mag keine Binärdaten.
Hi *,
wie wäre das?
######### skript ############
#!/bin/bash
typeset TEMP=/tmp/x
rm -f /tmp/kernel
cat /boot/System.map-2.6.16.13-4-smp | \
while dd count=1 of=${TEMP}
do
[[ -s ${TEMP} ]] || break
cat ${TEMP} >> /tmp/kernel
done
# testen:
ls -l /boot/System.map-2.6.16.13-4-smp /tmp/kernel
md5sum /boot/System.map-2.6.16.13-4-smp
md5sum /tmp/kernel
######### skript ############
Ausgabe:
512 bytes (512 B) copied, 0,000125 seconds, 4,1 MB/s
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0,000125 seconds, 4,1 MB/s
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0,000127 seconds, 4,0 MB/s
0+1 records in
0+1 records out
384 bytes (384 B) copied, 0,000129 seconds, 3,0 MB/s
0+0 records in
0+0 records out
0 bytes (0 B) copied, 2,1e-05 seconds, 0,0 kB/s
-rw-r--r-- 1 root root 900480 2006-05-03
12:10 /boot/System.map-2.6.16.13-4-smp
-rw-r--r-- 1 mschindl users 900480 2008-11-12 18:21 /tmp/kernel
27d8b55fadd12b0cc4a7a0cc37c492ec /boot/System.map-2.6.16.13-4-smp
27d8b55fadd12b0cc4a7a0cc37c492ec /tmp/kernel
tschau Michael
> Hm, könnte funktionieren, allerdings vermutlich nicht wirklich performant,
> für jedes einzelne Byte, also ein paar Millionen mal, einen Python-Interpreter
> zu starten.
Das stimmt allerdings. Ließe sich aber erweitern...
Aus Deinem Originalposting:
| Ich suche also ein Programm, dass eine definierte Anzahl Bytes liest,
| diese per STDOUT ausgibt und im Exit-Code anzeigt, ob es über EOF
| gestolpert ist.
r() { python -c '
import sys,os
rdsz=int(sys.argv[1])
s2=s=os.read(0,rdsz); # os.write(2,"%d\n"%len(s2))
while s:
n=os.write(1,s);s=s[n:];
sys.exit(int(not s2))' "$1"
}
Wie gesagt, geht bestimmt auch in Perl, und evtl. "einzeiliger", kann
ich aber nicht.
Damit kommst Du Deinem Ziel schon erheblich näher:
while r 102400 >>/tmp/bl; do :; done </boot/vmlinuz
HTH,
Thomas
> Wie gesagt, geht bestimmt auch in Perl, und evtl. "einzeiliger", kann
> ich aber nicht.
Ja, ich habe es jetzt auch mit Perl gelöst:
readChunk() {
local MiB=$1
perl -Mbytes -e 'my $l=$ARGV[0]*1024*1024;
my $size = 1;
$| = 1;
while( $r = sysread(STDIN, $foo, $size) ) {
print $foo;
$l -= $r;
exit 1 if $l<$size;
}' "$MiB"
}
Böse Falle dabei war, dass "read" (im Gegensatz zu "sysread") mehr liest,
als man eigentlich will, um schonmal ein paar Daten zu puffern. Das ist
natürlich fatal, wenn die Daten aus einer Pipe stammen und dadurch
verlorengehen, weil irgendwann der Perl-Prozess wechselt.
> perl -Mbytes -e 'my $l=$ARGV[0]*1024*1024;
> my $size = 1;
> $| = 1;
> while( $r = sysread(STDIN, $foo, $size) ) {
> print $foo;
> $l -= $r;
> exit 1 if $l<$size;
> }' "$MiB"
Warum machst du nicht einfach ein sysread(STDIN, $foo, $l)?
> Warum machst du nicht einfach ein sysread(STDIN, $foo, $l)?
äh, $size oben ist 1024, nicht 1. Die ganze Länge einzulesen wäre meines
Erachtens nicht sinnvoll, so eine Portition kann dann schonmal ein Gigabyte
groß sein. Die müssen dann erstmal im Perl-Prozess abgelegt werden, bevor sie
wieder via STDOUT ausgegeben werden.
> Paul Hink <paul-...@hink.name> schrieb:
>
>> Warum machst du nicht einfach ein sysread(STDIN, $foo, $l)?
>
> äh, $size oben ist 1024, nicht 1. Die ganze Länge einzulesen wäre
> meines Erachtens nicht sinnvoll, so eine Portition kann dann schonmal
> ein Gigabyte groß sein. Die müssen dann erstmal im Perl-Prozess
> abgelegt werden, bevor sie wieder via STDOUT ausgegeben werden.
Okay, bei einem Gigabyte bietet es sich natürlich an, die Daten Stück
für Stück einzulesen. Aber ob es sinnvoll ist, direkt das andere Extrem
zu wählen, in 1-Byte-Schritten einzulesen und damit für 1 GB eine
Milliarde sysread()-Aufrufe zu verbraten?
> Okay, bei einem Gigabyte bietet es sich natürlich an, die Daten Stück
> für Stück einzulesen. Aber ob es sinnvoll ist, direkt das andere Extrem
> zu wählen, in 1-Byte-Schritten einzulesen und damit für 1 GB eine
> Milliarde sysread()-Aufrufe zu verbraten?
Ich sage ja, die 1 stand an der Stelle nur noch aus Testzeiten, inzwischen
habe ich die Puffer-Größe erhöht.