![如何编写可以从 stdout 获取输入的脚本](https://linux22.com/image/68190/%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E5%8F%AF%E4%BB%A5%E4%BB%8E%20stdout%20%E8%8E%B7%E5%8F%96%E8%BE%93%E5%85%A5%E7%9A%84%E8%84%9A%E6%9C%AC.png)
我希望能够编写一个可以stdout
作为参数的脚本,如果有任何内容通过管道传输到其中(最终,我希望它是多态的)-
问题是,我搜索了又搜索如何做到这一点却无济于事 - 很多关于如何做其他事情的替代建议不是 - 这:
cat /var/log/some.log | grep something | awk '{print $1 $6 $8}' | myscript
为什么要这样做,而不是? :myscript $(!!)
此时此刻,只是为了证明这是可能的......
我知道您可以在脚本中“读取变量”,但假设我不关心这些行 - 比方说,我想接受整个脚本作为文本块并在脚本中对其执行某些操作 -
我真的必须:
while read x; do
stdin=$stdin" "$x;
done;
只是为了从 STDIN 读取?
一定会有更好的办法 ...
答案1
如果您想将所有 stdin 读入 shell 脚本,通常只需将其捕获到临时文件中:
TMPFILE=$(mktemp -- "${TMPDIR:-/tmp}/${0##*/}.$$.XXXXXX") || exit
cat > "$TMPFILE"
# Script works with $TMPFILE and its contents,
# ultimately writing everything to stdout.
rm -f -- "$TMPFILE"
甚至系统实用程序也做类似的事情。sort
例如,必须拥有所有标准输入才能将任何内容打印到标准输出。
答案2
您可以通过重定向文件描述符 1 的输入来读取 stdout。根据定义,Stdout 是文件描述符 1。文件描述符 1 用于输出这一事实是惯例问题,而不是技术义务。然而,这是一件奇怪的事情,必然会让使用你的脚本的人感到困惑。
read line <&1
如果要读取整个文件,请使用cat
。要将内容填充到变量中,请使用命令替换。
whole_input=$(cat)
whole_input_from_stdout=$(cat <&1)
有些 shell 允许您将其编写$(</dev/stdin)
为稍微更有效的快捷方式$(cat)
。
请注意,这会删除尾随换行符。此行为内置于命令替换中。要保留尾随换行符,请将它们隐藏在另一个字符后面并删除该字符。
whole_input=$(cat; echo .); whole_input=${whole_input%?}
如果输入中存在空字节,则 shell 变量将仅包含第一个空字节之前的数据。使用某些 shell,您将获得去除空字节的所有数据。 shell 无法处理二进制数据。 Zsh 是一个例外,它保留空字节。
答案3
我认为您的意思是您想要编写脚本,以便它可以从其标准输入或参数中处理命令的标准输出。
然后,最明显的方法是检查参数的数量。
如果您的脚本在 shell 变量中处理其输入:
#! /bin/sh -
if [ "$#" -gt 0 ]; then
# arguments joined with space, the first character of $IFS
input="$*"
else
input=$(cat) # stdin without the trailing newline characters
fi
printf 'I got "%s"\n' "$input"
或者,如果未传递任何参数,您可以将 stdin 的每个非空行读入位置参数($@
: $1
, ...):$2
#! /bin/sh -
NL='
'
if [ "$#" -eq 0 ]; then
set -o noglob
IFS=$NL
set -- $(cat) # split+glob with splitting on NL and glob disabled
fi
echo "I got $# arguments:"
[ "$#" -eq 0 ] || printf ' - "%s"\n' "$@"
或者,如果它将输入作为流处理:
#! /bin/sh -
main() {
# process standard input as a stream
grep -i foo | tr '[:lower:]' '[:upper:]'
# as an example
}
if [ "$#" -gt 0 ]; then
# feed arguments as separate lines through a pipe:
printf '%s\n' "$@" | main
else
# stdin just passed along
main
fi
所有这些都可以通过以下方式调用:
cmd | the-script
或者:
the-script "$(cmd)"
(不要忘记引号,没有引号, 的输出cmd
将受到 split+glob 的影响!)
也可以看看:
- 为什么使用 shell 循环处理文本被认为是不好的做法?
- var=$(</dev/stdin) 将 stdin 读入变量有什么问题?(以防您想用它来代替
input=$(cat)
)。
答案4
我的理解是,您必须单独考虑 1)设备,2)一般数据流接口,3)特别是线路读写器接口,4)语义。
stdout
是您的流程私有的技术端点,具有相同的生命周期,用于写入字节流,而不是读取。您可以复制对它的引用,以便用另一个名称引用它,它的功能始终是输出。它是随进程一起创建和删除的。stdin
是您读取字节流的等效项。它的能力始终是输入。根据底层设备的不同,它们可能具有这两种功能,但标准和可靠的模型对此并不可知。
A字节流是一个通用接口,在其写端接受两个信号:put
,close
并在其读端发出两个相应的信号:byte
, EOD
。当写端(例如stdout
)接收到时put
,读端必须得到一个byte
。当写端accept时close
,读端get EOD
。EOD
( ) 在描述符关闭时或进程终止时close
显式发送。stdout
exec >&-
A行读取器或写入器是一种使用字节流的特殊方式,带有附加信号newline
,但它不是stdin
Nor固有的stdout
,它只是数据转换为和从文本行因为它方便、易操作且坚固。这是一个线路协议或纪律、历史标准、可迭代且合理不可知论者。信号newline
是约定;无论它是什么,它都必须有一个值。除非依赖另一种状态,否则别无选择,但设备只有两种状态:打开或关闭。换句话说,字节流是设备驱动的,而行读取器是数据驱动的。
鉴于此,假设您想在两个进程之间进行通信通过一个管道,扬声器可以发送给听者的最小信号是byte
和EOD
,但是这个不能通过迭代来完成正如您对文本行所做的那样,因为一旦关闭(EOD
发送),文件描述符就是不可重复使用(即使它可能是可破解的)。但,只要你不需要迭代,您可以信赖EOD
。
然后,让我们现实一点:多态性正如你所说,如果你在这样做时丢失了可迭代的行模型,那么这不是字节流中的原始数据的问题。多态性是一个问题模型在一个简单的可迭代协议之上,比如行规则,在一个原始协议之上,比如字节流,可以在套接字、管道、文件、磁带、打印机或其他任何协议上实现。
然而,在流模型之上,只有两种迭代控制:要么你提前知道要读多少,要么你逐一读直到有信号。知道这一点后,您可以在原始流之上采用您喜欢的规则而不是文本行,这始终是一个问题解析器。解析器是一种读取流并以其自己的语言模型构建其他对象的机器。
而且,为了成为流顶部可接受的接口,它必须在上次迭代离开它的确切位置继续解析,按顺序向前。
现在我们知道了多态+迭代=>顺序解析器,我们可以回到标准并且非常满意。我们需要的是K埃普我tS实施S愚蠢的。