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?
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
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
).
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}
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.
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.