Подтвердить что ты не робот

Как сделать функцию bash, которая может считываться со стандартного ввода?

У меня есть некоторые скрипты, которые работают с параметрами, они работают нормально, но я бы хотел, чтобы они могли читать из stdin, например, из трубы, например, это называется read:

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

read $*

Теперь это работает с read "foo" "bar", но я хотел бы использовать его как:

echo "foo" | read

Как это сделать?

4b9b3361

Ответ 1

Вы можете использовать <<<, чтобы получить это поведение. read <<< echo "text" должен сделать это.

Тест с readly (я предпочитаю не использовать зарезервированные слова):

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

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

С трубками, основанными на этом ответе на "Bash script, читайте значения из stdin pipe" :

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

Ответ 2

Немного сложно написать функцию, которая может читать стандартный ввод, но работает нормально, когда не задан стандартный ввод. Если вы просто попробуете читать со стандартного ввода, он будет блокироваться до тех пор, пока он не получит какой-либо, подобно тому, как просто введите cat в приглашении.

В bash 4 вы можете обойти это, используя опцию -t для read с аргументом 0. Это удается, если есть доступный вход, но он не потребляет ни одного из них; в противном случае он терпит неудачу.

Здесь простая функция, которая работает как cat, если она имеет что-либо со стандартного ввода и echo в противном случае.

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

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

Это приводит к тому, что стандартный ввод имеет приоритет над аргументами командной строки, т.е. echo foo | catecho bar выводит foo. Чтобы аргументы имели приоритет над стандартным вводом (echo foo | catecho bar выходы bar), вы можете использовать более простую функцию

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

(что также имеет преимущество в работе с любой совместимой с POSIX оболочкой, а не только с некоторыми версиями bash).

Ответ 3

Чтобы объединить несколько других ответов в то, что сработало для меня (этот надуманный пример превращает строчный ввод в верхний регистр):

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

Некоторые примеры (первый не имеет ввода и, следовательно, не выводит):

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

Шаг за шагом:

  • проверить, был ли дескриптор файла 0 (/dev/stdin) терминалом

    if [ -t 0 ]; then
    
  • тесты для аргументов вызова CLI

    if [ $# -gt 0 ]; then
    
  • эхо все аргументы CLI для команды

    echo "$*" | ${COMMAND}
    
  • else, если stdin имеет канал (т.е. не терминал), вывод stdin для команды (cat - и cat являются сокращением для cat /dev/stdin)

    else
      cat - | ${COMMAND}
    

Ответ 4

Ниже приведен пример реализации функции sprintf в bash, которая использует printf и стандартный ввод:

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

Пример использования:

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

Это даст вам представление о том, как функция может считывать со стандартного ввода.

Ответ 5

Я обнаружил, что это можно сделать в одну строку, используя test и awk...

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

test -p проверяет наличие ввода в канале, который принимает ввод через стандартный ввод. Только если вход присутствует, мы хотим запустить awk так как в противном случае он будет зависать бесконечно, ожидая ввода, который никогда не придет.

Я поместил это в функцию, чтобы упростить ее использование...

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

Использование...

_stdin="$(inputStdin)"

Другая функция использует awk без теста для ожидания ввода из командной строки...

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
}

Использование...

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

Обратите внимание, что >/dev/tty в первом printf кажется необходимым для получения приглашения на печать при вызове функции в подстановке команд $(...).

Такое использование awk позволяет исключить причудливую команду read для сбора ввода с клавиатуры или стандартного ввода.

Ответ 6

Опоздал на вечеринку здесь. Построение ответа @andy, вот как я определяю свою функцию to_uppercase.

  • если стандартный ввод не пуст, используйте стандартный ввод
  • если stdin пуст, используйте args
  • если аргументы пусты, ничего не делайте
to_uppercase() {
  local input="$([[ -p /dev/stdin ]] && cat - || echo "[email protected]")"
  [[ -n "$input" ]] && echo "$input" | tr '[:lower:]' '[:upper:]' 
}

Обычаи:

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

Информация о версии Bash:

$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)