Comment puis-je gérer des données binaires brutes dans un canal bash?

15

J'ai une fonction bash qui prend un fichier en paramètre, vérifie que le fichier existe, puis écrit tout ce qui sort de stdin dans le fichier. La solution naïve fonctionne bien pour le texte, mais j'ai des problèmes avec les données binaires arbitraires.

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
la source

Réponses:

15

Votre façon est d'ajouter des sauts de ligne à tout ce qu'il écrit dans l'espace de tout ce que separator ( $IFS) utilise pour diviser la lecture. Au lieu de le décomposer en nouvelles lignes, prenez le tout et passez-le. Vous pouvez réduire le bit entier de code ci-dessus à ceci:

 cat - > $file

Vous n'avez pas besoin du bit tronqué, cela tronquera et y écrira le flux STDIN entier.

Edit: Si vous utilisez zsh, vous pouvez simplement utiliser > $fileà la place du chat. Vous êtes en train de rediriger vers un fichier et de le tronquer, mais s'il y a quelque chose qui attend en attendant que quelque chose accepte STDIN, il sera lu à ce moment-là. Je pense que vous pouvez faire quelque chose comme ça avec bash mais vous devrez définir un mode spécial.

Caleb
la source
Je n'ai pas pu faire fonctionner l'exemple de redirection stdin, mais changer l'exemple de chat en> | (J'ai un jeu de noclobber) fonctionne comme un charme. Merci d'avoir fait ma journée ^. ^
David Souther
+1 pour la version sans chat. Évitez toujours les chats inutiles;)
rozcietrzewiacz
@rozcietrzewiacz: C'est vrai, sauf que c'était après coup et que j'avais tort. Ce n'est peut-être pas une utilisation inutile du chat. La seule chose que vous pourriez faire est > $file. Cela ne fonctionne que comme la première chose qui recherche stdin dans le script shell parent. Fondamentalement, tout le code de David peut être réduit à un seul caractère, mais je pense que cat -c'est plus élégant et moins difficile à comprendre car il est compris à vue.
Caleb
Parfois, j'enchaîne quatre ou cinq cats ensemble, juste pour agacer les fanatiques de l'UUOC
Michael Mrozek
@MichaelMrozek: Parfois, je nomme mes fichiers de données catjuste pour que les gens qui insistent pour l'utiliser doivent nécessairement faire de la gymnastique mentale pour lire le code. Les pipes nommées sont également de bonnes cibles.
Caleb
7

Pour lire un fichier texte littéralement, n'utilisez pas plain read, qui traite la sortie de deux manières:

  • readinterprète \comme un personnage d'échappement; utiliser read -rpour désactiver cette fonction.
  • readse divise en mots sur les caractères en $IFS; défini IFSsur une chaîne vide pour désactiver cette option.

L'idiome habituel pour traiter un fichier texte ligne par ligne est

while IFS= read -r line; do 

Pour une explication de cet idiome, voir Pourquoi est-il while IFS= readutilisé si souvent au lieu de IFS=; while read..? .

Pour écrire une chaîne littéralement, n'utilisez pas simplement plain echo, qui traite la chaîne de deux manières:

  • Sur certains shells, les echoprocessus anti-slash s'échappent. (Sur bash, cela dépend si l' xpg_echooption est définie.)
  • Quelques chaînes sont traitées comme des options, par exemple -nou -e(l'ensemble exact dépend du shell).

Une façon portable d'imprimer une chaîne est littéralement avec printf. (Il n'y a pas de meilleur moyen en bash, sauf si vous savez que votre entrée ne ressemble pas à une option echo.) Utilisez le premier formulaire pour imprimer la chaîne exacte, et le second formulaire si vous souhaitez ajouter une nouvelle ligne.

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

Cela ne convient que pour le traitement de texte , car:

  • La plupart des shells s'étoufferont avec des caractères nuls dans l'entrée.
  • Lorsque vous avez lu la dernière ligne, vous n'avez aucun moyen de savoir s'il y avait une nouvelle ligne à la fin ou non. (Certains shells plus anciens peuvent avoir de plus gros problèmes si l'entrée ne se termine pas par une nouvelle ligne.)

Vous ne pouvez pas traiter les données binaires dans le shell, mais les versions modernes des utilitaires sur la plupart des unités peuvent gérer des données arbitraires. Pour passer toutes les entrées à la sortie, utilisez cat. Aller sur une tangente, echo -n ''est une façon compliquée et non portable de ne rien faire;echo -nserait tout aussi bon (ou non selon le shell), et :est plus simple et entièrement portable.

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

ou, plus simple,

cat >|"$file"

Dans un script, vous n'avez généralement pas besoin d'utiliser >|car il noclobberest désactivé par défaut.

Gilles 'SO- arrête d'être méchant'
la source
merci d'avoir signalé xpg_echo, c'est en fait un problème que j'avais ailleurs dans mon code et je ne m'en suis même pas rendu compte. Re noclobber, j'ai l'habitude de l'allumer dans mon bashrc.
David Souther
0

Cela fera exactement ce que vous voulez:

( 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}

Notez cependant l'utilisation de la mémoire. Cela lit l'entrée d'une manière délimitée par des valeurs nulles.

S'il n'y a pas d' octets \0 nuls dans l'entrée, bash devra d'abord lire l'intégralité du contenu de l'entrée en mémoire, puis le sortir.

Concernant votre étape tronquée:

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

beaucoup plus simple et équivalent est:

> ${file}   #Truncate the file
Marc Tamsky
la source