我有一些带参数的脚本,它们可以很好地工作,但我希望它们能够从标准输入中读取,例如从管道读取。例如,假设这个脚本叫做“read”:
#!/bin/bash
function read()
{
echo $*
}
read $*
现在这个代码可以使用read "foo" "bar"
来实现,但我想把它改成:
echo "foo" | read
我该如何完成这个任务?
我有一些带参数的脚本,它们可以很好地工作,但我希望它们能够从标准输入中读取,例如从管道读取。例如,假设这个脚本叫做“read”:
#!/bin/bash
function read()
{
echo $*
}
read $*
现在这个代码可以使用read "foo" "bar"
来实现,但我想把它改成:
echo "foo" | read
我该如何完成这个任务?
编写一个函数,使其可以读取标准输入,但在没有标准输入时也能正常工作有些棘手。如果您尝试从标准输入读取,它将一直阻塞,直到收到任何输入,就像在提示符下简单地键入cat
一样。
在bash 4中,您可以通过使用-t
选项和参数0来使用read
解决此问题。如果有任何输入可用,则成功,但不会消耗任何输入;否则,失败。
这是一个简单的函数,如果它有来自标准输入的任何内容,则像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标准的shell一起使用,而不仅仅是某些版本的bash
。
sleep 10 | catecho
一起使用。有没有办法实现这个? - hek2mgl<<<
来实现这种行为。read <<< echo "text"
应该就可以了。readly
测试(我更喜欢不使用保留字):function readly()
{
echo $*
echo "this was a test"
}
$ readly <<< echo "hello"
hello
this was a test
根据“Bash脚本,从标准输入流中读取值”的此答案,使用管道:$ echo "hello bye" | { read a; echo $a; echo "this was a test"; }
hello bye
this was a test
readly
的标准输入中,而它会忽略标准输入,并将第一个参数设置为“hello”。你可以将“echo”更改为任何其他单词,结果都会相同。 - chepner 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
逐步操作:
test if file descriptor 0 (/dev/stdin
) was opened by a terminal
if [ -t 0 ]; then
tests for CLI invocation arguments
if [ $# -gt 0 ]; then
echo all CLI arguments to command
echo "$*" | ${COMMAND}
else if stdin
is piped (i.e. not terminal input), output stdin
to command (cat -
and cat
are shorthand for cat /dev/stdin
)
else
cat - | ${COMMAND}
[ -t 0 ]
对我来说失败了。我将其替换为 [ ! -p /dev/stdin ]
并且能够在更广泛的情况下使其工作。 - Eliran Malka这是一个在bash中使用printf
和标准输入实现sprintf
函数的示例:
sprintf() { local stdin; read -d '' -u 0 stdin; printf "$@" "$stdin"; }
示例用法:
$ echo bar | sprintf "foo %s"
foo bar
有点晚了,参考@andy
的回答后,这是我如何定义我的to_uppercase
函数。
to_uppercase() {
local input="$([[ -p /dev/stdin ]] && cat - || echo "$@")"
[[ -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)
我发现可以使用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)"
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: ")"
printf
中的> /dev/tty
似乎是必要的,以便在使用命令替换$(...)
调用函数时打印提示符。awk
允许消除从键盘或stdin收集输入的古怪read
命令。另一个版本:
# Prints a text in a decorated ballon
function balloon()
{
(test -p /dev/stdin && cat - || echo $@) figlet -t | cowsay -n -f eyes | toilet -t --gay -f term
}
使用方法:
# Using with a pipe
$ fortune -s | balloon
# Passing text as parameter
balloon "$(fortune -s )"