Comment capturer stdin dans une variable sans supprimer les sauts de ligne de fin?

9

Dans un script shell ...

Comment capturer stdin dans une variable sans supprimer les sauts de ligne de fin?

En ce moment, j'ai essayé:

var=`cat`
var=`tee`
var=$(tee)

Dans tous les cas, $varil n'y aura pas de nouvelle ligne de fin du flux d'entrée. Merci.

AUSSI: S'il n'y a pas de nouvelle ligne de fin dans l'entrée, la solution ne doit pas en ajouter une .

MISE À JOUR À LA LUMIÈRE DE LA RÉPONSE ACCEPTÉE:

La solution finale que j'ai utilisée dans mon code est la suivante:

function filter() {
    #do lots of sed operations
    #see https://github.com/gistya/expandr for full code
}

GIT_INPUT=`cat; echo x`
FILTERED_OUTPUT=$(printf '%s' "$GIT_INPUT" | filter)
FILTERED_OUTPUT=${FILTERED_OUTPUT%x}
printf '%s' "$FILTERED_OUTPUT"

Si vous souhaitez voir le code complet, veuillez consulter la page github pour expandr , un petit script shell de filtre d'extension de mot-clé git open-source que j'ai développé à des fins de sécurité de l'information. Selon les règles définies dans les fichiers .gitattributes (qui peuvent être spécifiques à une branche) et git config , git redirige chaque fichier via le script shell expandr.sh chaque fois qu'il est entré ou sorti du référentiel. (C'est pourquoi il était essentiel de conserver les nouvelles lignes ou leur absence.) Cela vous permet de nettoyer les informations sensibles et d'échanger différents ensembles de valeurs spécifiques à l'environnement pour les branches de test, de transfert et actives.

CommaToast
la source
ce que vous faites ici n'est pas nécessaire. filterprend stdin- il court sed. Vous attrapez stdindans $GIT_INPUTpuis imprimer ce retour à stdoutplus d' un tuyau pour filteret attraper son stdoutdans $FILTERED_OUTPUTpuis l' imprimer retour à stdout. Les 4 lignes en bas de votre exemple ci - dessus pourraient être remplacés par ceci: filter. Aucune infraction ne signifie ici, c'est juste que ... vous travaillez trop dur. Vous n'avez pas besoin des variables shell la plupart du temps - dirigez simplement l'entrée au bon endroit et transmettez-la.
mikeserv du
Non, ce que je fais ici est nécessaire parce que si je le fais filter, cela ajoutera des caractères de nouvelle ligne aux extrémités de tous les flux d'entrée qui ne se terminaient pas initialement par des nouvelles lignes. En fait, à l'origine, je venais de le faire, filtermais je suis tombé sur ce problème qui m'a conduit à cette solution car ni "toujours ajouter des nouvelles lignes" ni "toujours supprimer les nouvelles lignes" ne sont des solutions acceptables.
CommaToast
sedfera probablement la nouvelle ligne supplémentaire - mais vous devriez gérer cela filteravec pas tout le reste. Et toutes ces fonctions que vous avez font essentiellement la même chose - a sed s///. Vous utilisez le shell pour diriger les données qu'il a enregistrées dans sa mémoire sedafin de sedremplacer ces données par d'autres données que le shell a stockées dans sa mémoire afin de sedpouvoir les rediriger vers le shell. Pourquoi pas juste [ "$var" = "$condition" ] && var=new_value? Je ne reçois pas non plus les tableaux - stockez-vous le nom du tableau dans [0]puis l'utiliser sedpour le remplacer par la valeur dans [1]? Peut-être discuter?
mikeserv
@mikeserv - Quel serait l'avantage de déplacer ce code à l'intérieur filter? Cela fonctionne parfaitement tel quel. En ce qui concerne le fonctionnement du code sur mon lien et pourquoi je l'ai configuré comme je l'ai fait, oui, parlons-en dans un salon de discussion.
CommaToast

Réponses:

7

Les sauts de ligne de fin sont supprimés avant que la valeur ne soit stockée dans la variable. Vous voudrez peut-être faire quelque chose comme:

var=`cat; echo x`

et utiliser à la ${var%x}place de $var. Par exemple:

printf "%s" "${var%x}"

Notez que cela résout le problème de retour à la ligne, mais pas celui d'octet nul (si l'entrée standard n'est pas du texte), car selon la substitution de commande POSIX :

Si la sortie contient des octets nuls, le comportement n'est pas spécifié.

Mais les implémentations de shell peuvent conserver des octets nuls.

vinc17
la source
Les fichiers texte contiennent-ils généralement des octets nuls? Je ne vois pas pourquoi ils le feraient. Mais le script que vous venez de mentionner ne semble pas fonctionner.
CommaToast
@CommaToast Les fichiers texte ne contiennent pas d'octets nuls. Mais la question dit simplement stdin / flux d'entrée, qui peut ne pas être du texte dans le cas le plus général.
vinc17
D'ACCORD. Eh bien, je l'ai essayé à partir de la ligne de commande et cela n'a rien fait, et à partir de mon script lui-même, votre suggestion échoue car elle ajoute "..." à la fin du fichier. De plus, s'il n'y avait pas de nouvelle ligne, il en ajoute toujours une.
CommaToast
@CommaToast Le "..." n'était qu'un exemple. J'ai clarifié ma réponse. Aucune nouvelle ligne n'est ajoutée (voir le texte avant le "..." dans l'exemple).
vinc17
1
Eh bien, les coquilles ne devraient pas cacher des choses, ce n'est pas cool. Ces obus devraient être tirés. Je n'aime pas ça quand mon ordinateur pense qu'il sait mieux que moi.
CommaToast
4

Vous pouvez utiliser le readintégré pour accomplir ceci:

$ IFS='' read -d '' -r foo < <(echo bar)

$ echo "<$foo>"
<bar
>

Pour qu'un script lise STDIN, ce serait simplement:

IFS='' read -d '' -r foo

 

Je ne sais pas dans quels coquilles cela fonctionnera cependant. Mais fonctionne bien dans bash et zsh.

Patrick
la source
Ni -dla substitution de processus ( <(...)) ne sont portables; ce code ne fonctionnera pas dash, par exemple.
chepner
Eh bien, la substitution de processus ne fait pas partie de la réponse, ce n'était qu'une partie de l'exemple montrant que cela fonctionne. Quant à -d, c'est pourquoi j'ai mis l'avertissement en bas. L'OP ne spécifie pas le shell.
Patrick
@chepner - bien que le style diffère légèrement, le concept fonctionne certainement dash. Vous utilisez simplement <<HEREDOC\n$(gen input)\nHEREDOC\n- dans dash- qui utilise des tuyaux pour les heredocs de la même manière que les autres shells les utilisent pour la substitution de processus - cela ne fait aucune différence. La read -dchose consiste simplement à spécifier un délimiteur - vous pouvez faire de même une douzaine de façons - assurez-vous simplement à ce sujet. Bien que vous ayez besoin d'un peu de queue gen input.
mikeserv du
Vous définissez IFS = '' pour qu'il ne place pas d'espaces entre les lignes qu'il lit en hein? Truc cool.
CommaToast
En fait, dans ce cas, ce IFS=''n'est probablement pas nécessaire. Il est conçu pour que readles espaces ne s'effondrent pas. Mais quand il lit dans une seule variable, cela n'a aucun effet (que je me souvienne). Mais je me sens plus en sécurité en le laissant :-)
Patrick
2

Vous pouvez faire comme:

input | { var=$(sed '$s/$/./'); var=${var%.}; }

Quoi que vous fassiez $vardisparaît dès que vous sortez de ce { current shell ; }groupe de toute façon. Mais cela pourrait aussi fonctionner comme:

var=$(input | sed '$s/$/./'); var=${var%.}
mikeserv
la source
1
Il est à noter qu'avec la première solution, c'est-à-dire avoir à utiliser $vardans le { ... }groupement, ce n'est pas toujours possible. Par exemple, si cette commande est exécutée à l'intérieur d'une boucle et qu'il faut en $vardehors de la boucle.
vinc17
@ vinc17 - si elle est une boucle que je souhaite utiliser, alors je l' utilise à la place des {}accolades .Il est vrai - et il est explicitement indiqué dans la réponse - que la valeur $varest très susceptible de disparaître complètement lorsque le { current shell; }groupe est fermé. Y a-t-il une manière plus explicite de le dire que, quoi que vous fassiez $vardisparaisse ...?
mikeserv
@ vinc17 - probablement la meilleure façon, cependant:input | sed "s/'"'/&"&"&/g;s/.*/process2 '"'-> &'/" | sh
mikeserv
1
Il y a aussi la _variablesfonction de bash_completion, qui stocke le résultat d'une substitution de commande dans une variable globale COMPREPLY. Si une solution de pipeline était utilisée pour conserver les nouvelles lignes, le résultat serait perdu. Dans votre réponse, on a l'impression que les deux solutions sont également bonnes. De plus, il convient de noter que le comportement de la solution de pipeline dépend fortement du shell: un utilisateur pourrait tester echo foo | { var=$(sed '$s/$/./'); var=${var%.}; } ; echo $varavec ksh93 et ​​zsh, et pense que c'est OK, alors que ce code est bogué.
vinc17
1
Vous n'avez pas dit "ça ne marche pas". Vous venez de dire " $vardisparaît" (ce qui n'est pas vrai car cela dépend du shell - le comportement n'est pas spécifié par POSIX), ce qui est une phrase plutôt neutre. La deuxième solution est meilleure car elle ne souffre pas de ce problème et son comportement est cohérent dans tous les shells POSIX.
vinc17