it-swarm.com.de

Wie erstelle ich eine Bash-Funktion, die Standardeingaben lesen kann?

Ich habe einige Skripte, die mit Parametern arbeiten, sie funktionieren gut, aber ich möchte, dass sie in der Lage sind, aus stdin zu lesen, beispielsweise aus einer Pipe. Angenommen, dies wird als read bezeichnet:

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

read $*

Jetzt funktioniert das mit read "foo" "bar", aber ich möchte es gerne als:

echo "foo" | read

Wie mache ich das?

40
JBoy

Sie können <<< verwenden, um dieses Verhalten zu erhalten. read <<< echo "text" sollte es schaffen.

Testen Sie mit readly (ich bevorzuge keine reservierten Wörter):

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

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

Mit Pipes, basierend auf diese Antwort auf "Bash-Skript, Werte aus stdin pipe lesen" :

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

Es ist etwas schwierig, eine Funktion zu schreiben, die Standardeingaben lesen kann, aber richtig funktioniert, wenn keine Standardeingabe erfolgt. Wenn Sie einfach versuchen, von der Standardeingabe zu lesen, wird sie blockiert, bis eine empfangen wird. Dies ist vergleichbar mit der Eingabe von cat an der Eingabeaufforderung. 

In Bash 4 können Sie dies umgehen, indem Sie die Option -t für read mit einem Argument von 0 verwenden. Dies ist erfolgreich, wenn Eingaben verfügbar sind, aber keine davon verbraucht. Andernfalls schlägt es fehl.

Hier ist eine einfache Funktion, die wie cat funktioniert, wenn sie etwas von der Standardeingabe hat, und echo, ansonsten.

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

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

Dadurch hat die Standardeingabe Vorrang vor Befehlszeilenargumenten, d. H. echo foo | catecho bar würde foo ausgeben. Damit Argumente Vorrang vor Standardeingaben haben (echo foo | catecho bar ausgaben bar), können Sie die einfachere Funktion verwenden

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

(was auch den Vorteil hat, mit any POSIX-kompatibler Shell zu arbeiten, nicht nur mit bestimmten Versionen von bash).

34
chepner

Um einige andere Antworten in das zu kombinieren, was für mich funktioniert hat (dieses Beispiel verwandelt Kleinbuchstaben in Großbuchstaben):

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

Einige Beispiele (das erste hat keine Eingabe und daher keine Ausgabe):

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

Schritt für Schritt:

  • test, ob der Dateideskriptor 0 (/dev/stdin) von einem Terminal geöffnet wurde

    if [ -t 0 ]; then
    
  • testet auf CLI-Aufrufargumente

    if [ $# -gt 0 ]; then
    
  • echo alle CLI-Argumente für den Befehl 

    echo "$*" | ${COMMAND}
    
  • sonst, wenn stdin ein Pipe ist (d. h. keine Terminal-Eingabe), Ausgabe stdin an Befehl (cat - und cat sind eine Abkürzung für cat /dev/stdin)

    else
      cat - | ${COMMAND}
    
13
Andy

Hier ist eine Beispielimplementierung der Funktion sprintf in bash, die printf und Standardeingabe verwendet:

sprintf() { local stdin; read -d '' -u 0 stdin; printf "[email protected]" "$stdin"; }

Verwendungsbeispiel:

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

Dies gibt Ihnen eine Vorstellung davon, wie die Funktion von der Standardeingabe lesen kann.

5
kenorb

Ich habe festgestellt, dass dies in einer Zeile mit test und awk... erfolgen kann.

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

Der test -p prüft die Eingabe in eine Pipe, die die Eingabe über stdin akzeptiert. Nur wenn eine Eingabe vorhanden ist, möchten wir die Variable awk ausführen, da sie andernfalls unbegrenzt hängen bleibt und auf eine Eingabe wartet, die niemals kommt.

Ich habe dies in eine Funktion gestellt, um es einfach zu benutzen ...

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

Verwendungszweck...

_stdin="$(inputStdin)"

Eine andere Funktion verwendet awk ohne den Test, um auf die Eingabe der Befehlszeile zu warten ...

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
}

Verwendungszweck...

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

Beachten Sie, dass der > /dev/tty in der ersten printf notwendig ist, um die Aufforderung zum Drucken zu erhalten, wenn die Funktion in einer Befehls-Substituierung $(...) aufgerufen wird.

Diese Verwendung von awk ermöglicht die Beseitigung des schrulligen read-Befehls zum Erfassen von Eingaben über die Tastatur oder den Standardeintrag.

0
DocSalvager

Spät zur Party hier. Aufbauend auf dem Antwort von @andy Definiere ich hier meine to_uppercase - Funktion.

  • wenn stdin nicht leer ist, verwende stdin
  • wenn stdin leer ist, verwende args
  • wenn args leer sind, nichts tun
to_uppercase() {
  local input="$([[ -p /dev/stdin ]] && cat - || echo "[email protected]")"
  [[ -n "$input" ]] && echo "$input" | tr '[:lower:]' '[:upper:]' 
}

Verwendungen:

$ to_uppercase 
$ to_uppercase abc
ABC
$ echo abc | to_uppercase 
ABC
$ to_uppercase <<< echo abc 
ABC

Bash-Versionsinfo:

$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-Apple-darwin17)
0
zwbetz