Как сделать функцию bash, которая может считываться со стандартного ввода?
У меня есть некоторые скрипты, которые работают с параметрами, они работают нормально, но я бы хотел, чтобы они могли читать из stdin, например, из трубы, например, это называется read:
#!/bin/bash
function read()
{
echo $*
}
read $*
Теперь это работает с read "foo" "bar"
, но я хотел бы использовать его как:
echo "foo" | read
Как это сделать?
Ответы
Ответ 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)