Wie kann ich binäre Rohdaten in einer Bash-Pipe verarbeiten?

15

Ich habe eine Bash-Funktion, die eine Datei als Parameter verwendet, überprüft, ob die Datei vorhanden ist, und dann alles, was von stdin kommt, in die Datei schreibt. Die naive Lösung funktioniert gut für Text, aber ich habe Probleme mit beliebigen Binärdaten.

echo -n '' >| "$file" #Truncate the file
while read lines
do  # Is there a better way to do this? I would like one...
    echo $lines >> "$file"
done
David Souther
quelle

Antworten:

15

Ihr Weg ist das Hinzufügen von Zeilenumbrüchen zu allem, was in das Leerzeichen eines Trennzeichens ( $IFS) geschrieben wird, um den Lesevorgang aufzuteilen. Anstatt es in neue Zeilen aufzuteilen, nimm einfach das Ganze und gib es weiter. Sie können das gesamte obige Codebit auf Folgendes reduzieren:

 cat - > $file

Sie brauchen das Kürzungsbit nicht, dies schneidet ab und schreibt den gesamten STDIN-Stream dorthin.

Bearbeiten: Wenn Sie zsh verwenden, können Sie nur > $fileanstelle der Katze verwenden. Sie leiten zu einer Datei um und kürzen sie. Wenn jedoch etwas auf etwas wartet, das STDIN akzeptiert, wird es an diesem Punkt gelesen. Ich denke, Sie können so etwas mit bash machen, aber Sie müssten einen speziellen Modus einstellen.

Caleb
quelle
Ich konnte das Beispiel für die stdin-Weiterleitung nicht zum Laufen bringen, aber ich habe das Beispiel für die Katze in> | geändert (Ich habe noclobber eingestellt) arbeitet wie ein Charme. Danke, dass du meinen Tag gemacht
David Souther
+1 für die Version ohne Katze. Vermeiden Sie immer nutzlose Katzen;)
rozcietrzewiacz
@rozcietrzewiacz: Stimmt, außer es war ein nachträglicher Einfall und ich habe mich geirrt. Dies ist möglicherweise keine sinnlose Verwendung von Katze. Das Einzige, was Sie tun können, ist > $file. Dies funktioniert nur als erstes, das im übergeordneten Shell-Skript nach stdin sucht. Grundsätzlich kann der gesamte Code von David auf ein einziges Zeichen reduziert werden, aber ich denke, das cat -ist eleganter und weniger problematisch, weil es auf Anhieb verstanden wird.
Caleb
Manchmal cat
Michael Mrozek
@MichaelMrozek: Manchmal benenne ich meine Datendateien catnur, damit Leute, die darauf bestehen, sie zu verwenden, unbedingt mental turnen müssen, um den Code zu lesen. Named Pipes sind auch gute Ziele.
Caleb
7

Um eine Textdatei buchstäblich zu lesen, verwenden Sie nicht plain read, da dies die Ausgabe auf zwei Arten verarbeitet:

  • readinterpretiert \als Fluchtzeichen; Verwenden Sie read -r, um dies auszuschalten.
  • readteilt sich in Wörter auf Zeichen in $IFS; Stellen Sie IFSeine leere Zeichenfolge ein, um dies zu deaktivieren.

Die übliche Sprache, um eine Textdatei zeilenweise zu verarbeiten, ist

while IFS= read -r line; do 

Eine Erklärung dieser Redewendung finden Sie unter Warum wird while IFS= readso oft anstelle von verwendet IFS=; while read..?. .

Um eine Zeichenfolge wörtlich zu schreiben, verwenden Sie nicht nur plain echo, sondern verarbeiten die Zeichenfolge auf zwei Arten:

  • Bei einigen Shells tritt ein echoBackslash auf. (Bei Bash hängt es davon ab, ob die xpg_echoOption aktiviert ist.)
  • Einige Zeichenfolgen werden als Optionen behandelt, z. B. -noder -e(die genaue Menge hängt von der Shell ab).

Ein portabler Weg, eine Zeichenkette buchstäblich zu drucken, ist mit printf. (Es gibt keinen besseren Weg in bash, es sei denn, Sie wissen, dass Ihre Eingabe nicht wie eine Option für aussieht echo.) Verwenden Sie das erste Formular, um die genaue Zeichenfolge zu drucken, und das zweite Formular, wenn Sie eine neue Zeile hinzufügen möchten.

printf %s "$line"
printf '%s\n' "$line"

Dies ist nur geeignet für die Verarbeitung von Text , denn:

  • Die meisten Shells verschlucken sich an Nullzeichen in der Eingabe.
  • Wenn Sie die letzte Zeile gelesen haben, können Sie nicht wissen, ob am Ende eine neue Zeile vorhanden war oder nicht. (Einige ältere Shells haben möglicherweise größere Probleme, wenn die Eingabe nicht mit einer neuen Zeile endet.)

Sie können keine Binärdaten in der Shell verarbeiten, aber moderne Versionen von Dienstprogrammen auf den meisten Unices können mit beliebigen Daten umgehen. Verwenden Sie, um alle Eingaben an die Ausgabe weiterzuleiten cat. Sich auf eine Tangente zu begeben, echo -n ''ist eine komplizierte und nicht tragbare Art, nichts zu tun. echo -nwäre genauso gut (oder nicht abhängig von der Shell), und :ist einfacher und vollständig portabel.

: >| "$file"
cat >>"$file"

oder einfacher

cat >|"$file"

In einem Skript müssen Sie normalerweise nicht verwenden, >|da noclobberes standardmäßig deaktiviert ist.

Gilles 'SO - hör auf böse zu sein'
quelle
danke für den hinweis auf xpg_echo, das ist eigentlich ein problem, das ich irgendwo anders in meinem code hatte und das ich nicht mal gemerkt habe. Ich habe die Angewohnheit, es in meinem Baschromat anzuschalten.
David Souther
0

Dies wird genau das tun, was Sie wollen:

( while read -r -d '' ; do
    printf %s'\0' "${REPLY}" ;
  done ;

  # When read hits EOF, it returns non-zero which exits the while loop.
  # That data still needs to be output:
  printf %s "${REPLY}"
) >> ${file}

Beachten Sie jedoch die Speichernutzung. Dies liest die Eingabe in einer durch Nullen getrennten Weise.

Wenn die Eingabe keine \0 Null- Bytes enthält, muss die Bash zuerst den gesamten Inhalt der Eingabe in den Speicher lesen und dann ausgeben.

In Bezug auf Ihren abgeschnittenen Schritt:

echo -n '' >| "$file" #Truncate the file

Ein viel einfacheres und gleichwertiges ist:

> ${file}   #Truncate the file
Marc Tamsky
quelle