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

时间:2021-04-27 15:43:28

I was trying to do this to decide whether to redirect stdin to a file or not:

我尝试这么做是为了决定是否将stdin重定向到一个文件:

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

But that doesn't work because when the variable $input is "&0", bash interprets it as a filename.

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

However, I could just do:

但是,我可以这样做:

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

The problem is that ./myScript is actually a long command line that I don't want to duplicate, nor do I want to create a function for it because it's not that long either (it's not worth it).

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

Then it occurred to me to do this:

我突然想到:

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

But that requires to run one more command and a pipe (i.e. a subshell).
Is there another way that's simpler and more efficient?

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

7 个解决方案

#1


21  

First of all stdin is file descriptor 0 (zero) rather than 1 (which is stdout).

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

You can duplicate file descriptors or use filenames conditionally like this:

您可以复制文件描述符或有条件地使用文件名,如下所示:

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

some_long_command_line <&3

#2


7  

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

In a subshell, conditionally redirect stdin and exec the script.

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

#3


4  

Standard input can also be represented by the special device file /dev/stdin, so using that as a filename will work.

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

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

#4


2  

How about

如何

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

I've used the minus sign to denote standard input because that's traditional for many Unix programs.

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

Now you write

现在你写

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

I find these functions/commands which take commands as arguments (like xargs(1)) can be very useful, and they compose well.

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

#5


2  

If you're careful, you can use 'eval' and your first idea.

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

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

However, you say that 'myScript' is actually a complex command invocation; if it involves arguments which might contain spaces, then you must be very careful before deciding to use 'eval'.

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

Frankly, worrying about the cost of a 'cat' command is probably not worth the trouble; it is unlikely to be the bottleneck.

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

Even better is to design myScript so that it works like a regular Unix filter - it reads from standard input unless it is given one or more files to work (like, say, cat or grep as examples). That design is based on long and sound experience - and is therefore worth emulating to avoid having to deal with problems such as this.

更好的方法是设计myScript,使其像常规的Unix过滤器一样工作——它从标准输入中读取数据,除非它被给定一个或多个文件来工作(例如,cat或grep)。这种设计基于长期的、良好的经验——因此值得效仿,以避免不得不处理此类问题。

#6


1  

Use eval:

使用eval:

#! /bin/bash

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

eval "./myScript <$input"

This simple stand-in for myScript

这个简单的替代我的脚本

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

produces the following output:

产生以下输出:

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

$ ./myDemux
foo
oof
bar
rab
baz
zab

Note that it handles spaces in inputs too:

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

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

To pipe input down to myScript, use process substitution:

要将输入导入到myScript,请使用流程替换:

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

Note that if you try to pipe the output directly, as in

注意,如果您试图直接管道输出,如in

$ md5sum /etc/issue | ./myDemux

it will hang waiting on input from the terminal, whereas ephemient's answer does not have this shortcoming.

它会挂起等待来自终端的输入,而ephemient的回答没有这个缺点。

A slight change produces the desired behavior:

一个微小的变化就会产生期望的行为:

#! /bin/bash

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

#7


0  

people show to you very long scripts, but.... you get bash trap :) You must quote everything in bash. for example, you want list file named &0 .

人给你很长的脚本,但是....您将得到bash trap:)您必须在bash中引用所有内容。例如,您希望列出名为&0的列表文件。

filename='&0' #right ls $filename #wrong! this substitute $filename and interpret &0 ls "$filename" #right

文件名='&0' #正确,$文件名#错误!这替换$filename并解释&0 ls "$filename" #right

another, files with spaces.

另一个文件空间。

filename=' some file with spaces ' ls $filename #wrong, bash cut first and last space, and reduce multiple spaces between with and spaces words ls "$filename" righ

文件名='一些带有空格的文件' ls $filename #错误,bash首先和最后一个空格,并减少空格之间的多个空格

the same is in your script. please change:

你的脚本也是如此。请更改:

./myScript < $input

to

./myScript < "$input"

its all. bash has more traps. I suggest make quotation for "$file" with the same reason. spaces and other characters than can be interpreted are allways make problems.

它的所有。bash的陷阱。我建议以同样的理由对“$file”进行报价。空间和其他无法解释的字符总是会产生问题。

but what about /dev/stdin ? this is useable only when you redirected stdin and want to print something to real stdin.

但是/dev/stdin呢?这只有当你重定向stdin并且想要打印一些东西到真正的stdin时才有用。

so, your script should show like this:

所以,你的剧本应该是这样的:

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

#1


21  

First of all stdin is file descriptor 0 (zero) rather than 1 (which is stdout).

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

You can duplicate file descriptors or use filenames conditionally like this:

您可以复制文件描述符或有条件地使用文件名,如下所示:

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

some_long_command_line <&3

#2


7  

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

In a subshell, conditionally redirect stdin and exec the script.

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

#3


4  

Standard input can also be represented by the special device file /dev/stdin, so using that as a filename will work.

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

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

#4


2  

How about

如何

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

I've used the minus sign to denote standard input because that's traditional for many Unix programs.

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

Now you write

现在你写

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

I find these functions/commands which take commands as arguments (like xargs(1)) can be very useful, and they compose well.

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

#5


2  

If you're careful, you can use 'eval' and your first idea.

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

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

However, you say that 'myScript' is actually a complex command invocation; if it involves arguments which might contain spaces, then you must be very careful before deciding to use 'eval'.

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

Frankly, worrying about the cost of a 'cat' command is probably not worth the trouble; it is unlikely to be the bottleneck.

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

Even better is to design myScript so that it works like a regular Unix filter - it reads from standard input unless it is given one or more files to work (like, say, cat or grep as examples). That design is based on long and sound experience - and is therefore worth emulating to avoid having to deal with problems such as this.

更好的方法是设计myScript,使其像常规的Unix过滤器一样工作——它从标准输入中读取数据,除非它被给定一个或多个文件来工作(例如,cat或grep)。这种设计基于长期的、良好的经验——因此值得效仿,以避免不得不处理此类问题。

#6


1  

Use eval:

使用eval:

#! /bin/bash

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

eval "./myScript <$input"

This simple stand-in for myScript

这个简单的替代我的脚本

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

produces the following output:

产生以下输出:

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

$ ./myDemux
foo
oof
bar
rab
baz
zab

Note that it handles spaces in inputs too:

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

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

To pipe input down to myScript, use process substitution:

要将输入导入到myScript,请使用流程替换:

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

Note that if you try to pipe the output directly, as in

注意,如果您试图直接管道输出,如in

$ md5sum /etc/issue | ./myDemux

it will hang waiting on input from the terminal, whereas ephemient's answer does not have this shortcoming.

它会挂起等待来自终端的输入,而ephemient的回答没有这个缺点。

A slight change produces the desired behavior:

一个微小的变化就会产生期望的行为:

#! /bin/bash

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

#7


0  

people show to you very long scripts, but.... you get bash trap :) You must quote everything in bash. for example, you want list file named &0 .

人给你很长的脚本,但是....您将得到bash trap:)您必须在bash中引用所有内容。例如,您希望列出名为&0的列表文件。

filename='&0' #right ls $filename #wrong! this substitute $filename and interpret &0 ls "$filename" #right

文件名='&0' #正确,$文件名#错误!这替换$filename并解释&0 ls "$filename" #right

another, files with spaces.

另一个文件空间。

filename=' some file with spaces ' ls $filename #wrong, bash cut first and last space, and reduce multiple spaces between with and spaces words ls "$filename" righ

文件名='一些带有空格的文件' ls $filename #错误,bash首先和最后一个空格,并减少空格之间的多个空格

the same is in your script. please change:

你的脚本也是如此。请更改:

./myScript < $input

to

./myScript < "$input"

its all. bash has more traps. I suggest make quotation for "$file" with the same reason. spaces and other characters than can be interpreted are allways make problems.

它的所有。bash的陷阱。我建议以同样的理由对“$file”进行报价。空间和其他无法解释的字符总是会产生问题。

but what about /dev/stdin ? this is useable only when you redirected stdin and want to print something to real stdin.

但是/dev/stdin呢?这只有当你重定向stdin并且想要打印一些东西到真正的stdin时才有用。

so, your script should show like this:

所以,你的剧本应该是这样的:

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