Bash:在脚本中动态重定向标准输入

问题描述 投票:28回答:7

我试图这样做来决定是否将stdin重定向到文件:

[ ...some condition here... ] && input=$fileName || input="&0"
./myScript < $input

但这不起作用,因为当变量$ input为“&0”时,bash会将其解释为文件名。

但是,我可以这样做:

if [ ...condition... ];then
    ./myScript <$fileName
else
    ./myScript

问题是./myScript实际上是一个我不想复制的长命令行,也不想为它创建一个函数,因为它也不长(它不值得)。

然后我突然想到这样做:

[ ...condition... ] && input=$fileName || input=  #empty
cat $input | ./myScript

但是这需要再运行一个命令和一个管道(即子shell)。 还有另一种更简单,更有效的方法吗?

linux bash shell io-redirection
7个回答
24
投票

首先,stdin是文件描述符0(零)而不是1(stdout)。

您可以像这样有条件地复制文件描述符或使用文件名:

[[ some_condition ]] && exec 3<"$filename" || exec 3<&0

some_long_command_line <&3

请注意,如果条件为false或第一个exec失败,则显示的命令将执行第二个exec。如果您不希望潜在的失败,那么您应该使用if / else

if [[ some_condition ]]
then
    exec 3<"$filename"
else
    exec 3<&0
fi

但是如果第一次重定向失败(在条件为真之后),则文件描述符3的后续重定向将失败。


7
投票
(
    if [ ...some condition here... ]; then
        exec <$fileName
    fi
    exec ./myscript
)

在子shell中,有条件地重定向stdin并执行脚本。


5
投票

标准输入也可以由特殊设备文件/dev/stdin表示,因此使用它作为文件名将起作用。

file="/dev/stdin"
./myscript < "$file"

2
投票

怎么样

function runfrom {
    local input="$1"
    shift
    case "$input" in
        -) "$@" ;;
        *) "$@" < "$input" ;;
    esac
}

我使用减号来表示标准输入,因为这是许多Unix程序的传统。

现在你写

[ ... condition ... ] && input="$fileName" || input="-"
runfrom "$input" my-complicated-command with many arguments

我发现这些函数/命令将命令作为参数(如xargs(1))非常有用,并且它们组合得很好。


2
投票

如果你小心,你可以使用'eval'和你的第一个想法。

[ ...some condition here... ] && input=$fileName || input="&1"
eval ./myScript < $input

但是,你说'myScript'实际上是一个复杂的命令调用;如果它涉及可能包含空格的参数,那么在决定使用'eval'之前必须非常小心。

坦率地说,担心'cat'命令的成本可能不值得这么麻烦;它不太可能成为瓶颈。

更好的是设计myScript,使其像普通的Unix过滤器一样工作 - 它从标准输入读取,除非给出一个或多个文件(例如,catgrep作为示例)。该设计基于长期和良好的体验 - 因此值得模仿,以避免不得不处理这样的问题。


1
投票

使用eval

#! /bin/bash

[ $# -gt 0 ] && input="'"$1"'" || input="&1"

eval "./myScript <$input"

这个简单的替身为myScript

#! /usr/bin/perl -lp
$_ = reverse

产生以下输出:

$ ./myDemux myScript
pl- lrep/nib/rsu/ !#
esrever = _$

$ ./myDemux
foo
oof
bar
rab
baz
zab

请注意,它也处理输入中的空格:

$ ./myDemux foo\ bar
eman eht ni ecaps a htiw elif

要将输入传输到myScript,请使用process substitution

$ ./myDemux <(md5sum /etc/issue)
eussi/cte/  01672098e5a1807213d5ba16e00a7ad0

请注意,如果您尝试直接管道输出,请参阅

$ md5sum /etc/issue | ./myDemux

它将等待来自终端的输入,而ephemient's answer没有这个缺点。

稍微改变会产生所需的行为:

#! /bin/bash

[ $# -gt 0 ] && input="'"$1"'" || input=/dev/stdin
eval "./myScript <$input"

0
投票

人们向你展示很长的剧本,但是......你得到了bash陷阱:)你必须用bash引用一切。例如,您需要名为&0的列表文件。

filename ='&0'#right ls $ filename #wrong!这个替换$ filename并解释&0 ls“$ filename”#right

另一个,带空格的文件。

filename ='带空格的文件'ls $ filename #wrong,bash剪切第一个和最后一个空格,并减少with和空格之间的多个空格ls“$ filename”righ

你的脚本也一样。请更换:

./myScript < $input

./myScript < "$input"

一切。 bash有更多的陷阱。我建议出于同样的原因为“$ file”报价。空间和其他可以解释的字符总是会产生问题。

但是/ dev / stdin怎么样?这只有在重定向stdin并想要将内容打印到真正的标准输入时才可用。

所以,你的脚本应该显示如下:

[ ...some condition here... ] && input="$fileName" || input="&0"
./myScript < "$input"
© www.soinside.com 2019 - 2024. All rights reserved.