我们中的许多人肯定都知道,让程序接受 stdin 输入总是一个好主意。很多程序都允许 *nix 环境。这让我们可以做一些很酷的事情,比如管道
echo "foo" | less
。人们经常会发现这cat barfile | baz
在逻辑上等同baz barfile
于在幕后它只是读取字符串。
如今,有很多程序默认无法通过管道传输。有些程序有一个标志,允许上述行为,但很多程序不允许。
现在,我的问题是,临时文件管道是否存在?
现在,凭借我几乎不存在的编写 Bash 的能力,以及在 Google 上搜索了大约 5 分钟,我想出了这个
#!/bin/bash
if [ $# -ne 2 ]; then exit 1; fi
f=$(mktemp)
($1) > $f
if [ $? -eq 0 ]; then ($2 $f); else exit $?; fi
rm $f
调用这个 fpipe,我们可以做一些事情,比如fpipe 'wget -O- www.example.com' baz
baz 是一个我们无法通过管道传输但可以执行的程序baz file
。
我的问题是,我们怎样才能做得更好。我怀疑,随着 Bash 知识的增多,重写上述脚本以接受任意数量的参数将相当简单(这样我们就可以执行类似的事情,fpipe 'foo x' bar baz
使用管道我们可以执行类似的事情foo x | bar | baz
。哎,我们可能会将两者混合起来,最终得到类似的事情fpipe 'wget -O- www.example.org | rev' baz
。
是否存在实现此目的的现有构造?我相信我看到了某种构造foo x > bar < baz
或类似的东西。我本以为这是一个相当常见的问题,但我的搜索没有带来任何结果。这意味着我要么搜索得不够仔细,要么我错过了一些相当明显的东西。
如果没有适当的方法(TM)来实现这一点,是否可以定义一个方便的语法?比如说,foo x |> baz | rev
哪里|>
几乎直接翻译成我的脚本。
PS:我知道我的 /script/ 非常脆弱和幼稚(例如在非 0 时退出);请随意发布一个更好的脚本。
答案1
这(原则上)在任何 shell 中都可以工作,但它需要“足够新的版本”的操作系统:
wget -O- www.example.com | baz /dev/stdin
当/dev/stdin
存在时,它通常是 的符号链接/proc/self/fd/0
,因此如果您的盒子没有/dev/stdin
,请检查/proc/self/fd/0
。
当然,管道的发明是 Unix 的伟大创新之一。1临时 中间文件在原则上是可以的,但可能会有性能成本,并且在
产生大量输出的程序| grep匹配极少行的正则表达式
您可能会遇到无法将文件(第一个命令的所有输出)放入磁盘的问题。因此,两全其美的方法是使用命名管道。这可以让您捕获某个程序2的标准输出 并将其作为路径名参数提供给另一个程序作为输入:
f=$(mktemp) && rm -f "$f" && mkfifo "$f"
$1 >> "$f" &
$2 "$f"
rm -f "$f"
第一个rm -f "$f"
是必需的,因为它mktemp
不仅生成文件名;它还创建文件。如果我们不删除它,将mkfifo
失败。(或者,我们可以使用mktemp -u
,它会生成文件名而不创建文件。)
___________
1 Unix 是否“发明”了管道尚有争议。根据Unix 分时系统的演变 (作者:Dennis M. Ritchie)(PDF),在 1972 年管道出现在 Unix 中之前,这个概念就已经以其他形式存在于其他地方。
2或者,一般来说,任何输出流;例如来自 或 cp somefile "$f"
dd … of="$f"
答案2
答案3
如果给出了文件参数,或者应该从 stdin 读取行,则有两种方法可以使您的脚本执行相同的操作:
1)cat 文件,如果命令行中没有指定文件,则使用特殊文件名“-”
#!/bin/bash
[[ $# -eq 0 ]] && set -- "-"
n=0
while read line; do
echo "$((++n)) $line"
done < <(cat "$@")
2)如果指定了文件,则将其内容复制到脚本的标准输入流
#!/bin/bash
[[ $# -gt 0 ]] && exec 0< <(cat "$@")
n=0
while read line; do
echo "$((++n)) $line"
done