45

我有一些使用参数的脚本,它们工作得很好,但我希望它们能够从标准输入读取,例如从管道读取,假设这被称为读取:

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

read $*

现在这适用于read "foo" "bar",但我想将其用作:

echo "foo" | read

我该如何做到这一点?

4

7 回答 7

42

编写一个可以读取标准输入的函数有点棘手,但在没有给出标准输入的情况下可以正常工作。如果您只是尝试从标准输入中读取,它将一直阻塞,直到它收到任何内容,就像您只是cat在提示符下键入一样。

在 bash 4 中,您可以通过使用带有参数 0 的-t选项来解决此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)。

于 2013-09-12T14:18:47.200 回答
39

您可以使用<<<来获取此行为。read <<< echo "text"应该成功。

测试readly(我不喜欢使用保留字):

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

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

使用管道,基于对“Bash script, read values from stdin pipe”的回答

$ echo "hello bye" | { read a; echo $a;  echo "this was a test"; }
hello bye
this was a test
于 2013-09-12T10:04:42.457 回答
15

将许多其他答案组合成对我有用的答案(这个人为的示例将小写输入变为大写):

  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}
    
  • 否则,如果stdin是管道(即不是终端输入),则输出stdin到命令(cat -并且cat是 的简写cat /dev/stdin

    else
      cat - | ${COMMAND}
    
于 2016-05-29T15:26:39.060 回答
6

这是使用标准输入sprintf的 bash中函数的示例实现:printf

sprintf() { local stdin; read -d '' -u 0 stdin; printf "$@" "$stdin"; }

示例用法:

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

这将使您了解函数如何从标准输入中读取。

于 2015-05-29T17:02:25.410 回答
4

在这里聚会迟到了。建立在@andy's answer的基础上,这就是我定义我的to_uppercase功能的方式。

  • 如果 stdin 不为空,则使用 stdin
  • 如果 stdin 为空,则使用 args
  • 如果 args 为空,则什么也不做
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)
于 2019-06-25T21:08:38.770 回答
0

我发现这可以在一行中使用testand 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: ")"

请注意,在 Command Substituion 中调用函数时> /dev/tty,第一个printf似乎是获取打印提示所必需的$(...)

这种使用awk允许消除read用于从键盘或标准输入收集输入的古怪命令。

于 2018-05-11T19:04:33.420 回答
0

另一个版本:

  1. 通过管道或参数传递文本来工作
  2. 通过更改最后一行的命令轻松复制和粘贴
  3. 适用于 bash,zsh
# 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 )"
于 2021-02-02T20:01:15.260 回答