web-dev-qa-db-fra.com

Comment créer une fonction bash capable de lire à partir d'une entrée standard?

J'ai des scripts qui fonctionnent avec des paramètres, ils fonctionnent très bien, mais j'aimerais qu'ils puissent lire à partir de stdin, d'un tube par exemple, par exemple, supposons que ceci s'appelle read:

#!/bin/bash
function read()
{
 echo $*
}

read $*

Maintenant cela fonctionne avec read "foo" "bar", mais je voudrais l'utiliser comme:

echo "foo" | read

Comment puis-je accomplir cela?

40
JBoy

Vous pouvez utiliser <<< pour obtenir ce comportement. read <<< echo "text" devrait le faire.

Testez avec readly (je préfère ne pas utiliser de mots réservés):

function readly()
{
 echo $*
 echo "this was a test"
}

$ readly <<< echo "hello"
hello
this was a test

Avec les tubes, basée sur cette réponse à "Script Bash, lisez les valeurs du tube stdin" :

$ echo "hello bye" | { read a; echo $a;  echo "this was a test"; }
hello bye
this was a test
39
fedorqui

C'est un peu délicat d'écrire une fonction capable de lire une entrée standard, mais qui fonctionne correctement lorsqu'aucune entrée standard n'est donnée. Si vous essayez simplement de lire à partir d'une entrée standard, il bloquera jusqu'à ce qu'il en reçoive, comme si vous tapiez simplement cat à l'invite. 

Dans bash 4, vous pouvez contourner ce problème en utilisant l'option -t avec read avec un argument de 0. Il réussit si une entrée est disponible, mais n'en consomme aucune; sinon, ça échoue.

Voici une fonction simple qui fonctionne comme cat si elle a quelque chose d’entrée standard, et echo sinon.

catecho () {
    if read -t 0; then
        cat
    else
        echo "$*"
    fi
}

$ catecho command line arguments
command line arguments
$ echo "foo bar" | catecho
foo bar

Cela donne la priorité à l’entrée standard par rapport aux arguments de la ligne de commande, c’est-à-dire que echo foo | catecho bar produirait foo. Pour que les arguments aient la priorité sur l'entrée standard (echo foo | catecho bar sorties bar), vous pouvez utiliser la fonction la plus simple.

catecho () {
    if [ $# -eq 0 ]; then
        cat
    else
        echo "$*"
    fi
}

(qui présente également l’avantage de travailler avec n’importe quel shell compatible POSIX, et pas seulement avec certaines versions de bash).

34
chepner

Pour combiner plusieurs autres réponses en ce qui a fonctionné pour moi (cet exemple artificiel convertit les minuscules en majuscules):

  uppercase() {
    local COMMAND='tr [:lower:] [:upper:]'
    if [ -t 0 ]; then
      if [ $# -gt 0 ]; then
        echo "$*" | ${COMMAND}
      fi
    else
      cat - | ${COMMAND} 
    fi
  }

Quelques exemples (le premier n'a pas d'entrée, et donc pas de sortie):

:; uppercase
:; uppercase test
TEST
:; echo test | uppercase 
TEST
:; uppercase <<< test
TEST
:; uppercase < <(echo test)
TEST

Pas à pas:

  • test si le descripteur de fichier 0 (/dev/stdin) a été ouvert par un terminal

    if [ -t 0 ]; then
    
  • tests pour les arguments d'appel CLI

    if [ $# -gt 0 ]; then
    
  • echo tous les arguments CLI de la commande 

    echo "$*" | ${COMMAND}
    
  • sinon, si stdin est canalisé (c’est-à-dire pas une entrée de terminal), la sortie stdin de la commande (cat - et cat sont des raccourcis pour cat /dev/stdin)

    else
      cat - | ${COMMAND}
    
13
Andy

Voici un exemple d'implémentation de la fonction sprintf dans bash qui utilise printf et l'entrée standard:

sprintf() { local stdin; read -d '' -u 0 stdin; printf "$@" "$stdin"; }

Exemple d'utilisation:

$ echo bar | sprintf "foo %s"
foo bar

Cela vous donnerait une idée de la façon dont la fonction peut lire à partir d'une entrée standard.

5
kenorb

J'ai découvert que cela peut être fait sur une seule ligne en utilisant test et awk...

    test -p /dev/stdin  && awk '{print}' /dev/stdin

Le test -p teste l'entrée sur un canal, qui accepte l'entrée via stdin. C’est seulement si l’entrée est présente que nous voulons exécuter la awk, sinon elle sera suspendue indéfiniment dans l’attente d’une entrée qui ne viendra jamais.

J'ai mis cela dans une fonction pour la rendre facile à utiliser ...

inputStdin () {
  test -p /dev/stdin  && awk '{print}' /dev/stdin  && return 0
  ### accepts input if any but does not hang waiting for input
  #
  return 1
}

Usage...

_stdin="$(inputStdin)"

Une autre fonction utilise awk sans le test pour attendre une entrée en ligne de commande ...

inputCli () {
  local _input=""
  local _Prompt="$1"
  #
  [[ "$_Prompt" ]]  && { printf "%s" "$_Prompt" > /dev/tty; }
  ### no Prompt at all if none supplied
  #
  _input="$(awk 'BEGIN {getline INPUT < "/dev/tty"; print INPUT}')"
  ### accept input (used in place of 'read')
  ###   put in a BEGIN section so will only accept 1 line and exit on ENTER
  ###   WAITS INDEFINITELY FOR INPUT
  #
  [[ "$_input" ]]  && { printf "%s" "$_input"; return 0; }
  #
  return 1
}

Usage...

_userinput="$(inputCli "Prompt string: ")"

Notez que le > /dev/tty sur la première printf semble être nécessaire pour que l'invite s'imprime lorsque la fonction est appelée dans une commande Substituion $(...).

Cette utilisation de awk permet d’éliminer la commande excentrique read pour la collecte des entrées à partir du clavier ou de stdin.

0
DocSalvager