如何在当前shell中执行命令的输出?

115

我非常清楚source(也称为.)实用程序,它可以将文件内容获取并在当前 shell 中执行。

现在,我正在将一些文本转换为 shell 命令,然后按照以下方式运行它们:

$ ls | sed ... | sh
ls只是一个随机示例,原始文本可以是任何内容。 sed也是如此,只是用于转换文本的示例。有趣的部分是sh。我将所有内容都输入到sh中并运行它。
我的问题是,这意味着要启动一个新的子 shell。我更喜欢在当前的 shell 中运行命令,就像如果我有一个包含命令的文本文件,我可以使用source some-file来运行它们。
我不想创建临时文件,因为感觉很不好。
或者,我希望使用与当前 shell 完全相同的特性启动我的子 shell。
更新:
好吧,使用反引号的解决方案当然有效,但我经常需要在检查和更改输出时执行此操作,因此我更喜欢将结果管道传递到最终的某个地方。
悲伤的更新:
啊,/dev/stdin 看起来很漂亮,但在更复杂的情况下,它没有起作用。
所以,我有这个:
find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/git mv -f $1 $2.doc/i' | source /dev/stdin

这确保所有的.doc文件都转换成小写的扩展名。

顺带一提,可以使用xargs来处理,但这并不是重点。

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/$1 $2.doc/i' | xargs -L1 git mv

所以,当我运行前者时,它会立即退出,什么也不会发生。


当您首先将命令管道到临时文件,然后再进行源操作时,您的复杂命令是否有效?如果无效,生成的输出有什么问题?如果您的文件名中带有空格或某些序列未正确转义,则您的命令输出将无法正常工作。我建议至少在$1和$2.doc周围添加引号。 - Kaleb Pederson
有没有必要在原始 shell 中运行这个程序的好理由?- 这些示例不会操作当前 shell,因此这样做并没有任何好处。快速解决方案是将输出重定向到文件中,然后再通过 source 命令执行该文件。 - nos
@kaleb 输出正常运行。在这种情况下,即使我管道到 sh,文件名也是安全的,但还是谢谢你的注意。@nos git 环境变量在原始 shell 上。再次强调,这只是示例。问题涉及生命。 - kch
当我需要分配的变量保持不变时,source / dev / stdin对我无效。 来自freenode bash的geirha指向了http://mywiki.wooledge.org/BashFAQ/024,并建议我尝试使用进程替换 source <(command) 这适合我。 - srcerer
9个回答

159

eval 命令就是为了这个目的而存在的。

eval "$( ls | sed... )"

来自bash手册的更多内容:

eval

          eval [arguments]

这些参数会被连接成一个单一的命令,然后被读取和执行,并且它的退出状态将作为 eval 的退出状态返回。如果没有参数或只有空参数,则返回状态为零。


11
这里唯一的问题在于你可能需要插入分号来区分命令。我自己使用这种方法来处理AIX、Sun、HP和Linux。 - Tanktalus
2
Tanktalus,感谢您的评论,它让我的脚本工作了。在我的机器上,eval不会将命令分隔到新行,并且即使使用分号,使用source也无法正常工作。带有分号的eval是解决方案。我希望我能给你一些积分。 - Milan Babuškov
3
我希望能为您翻译。这句话的意思是“我给他升了级,为你做的”。 - Phil
3
非常好。然而,针对我的使用情况,我最终不得不在$()周围加上引号,像这样:eval "$( ssh remote-host 'cat ~/.bash_profile' )"请注意,我确实正在使用bash 3.2。 - Zachary Murray
3
这对我很有效,而被接受的答案则没有。 - Dan Tenenbaum

103
$ ls | sed ... | source /dev/stdin

更新:这在bash 4.0、tcsh和dash中都有效(如果将source更改为.)。显然,在bash 3.2中存在错误。根据bash 4.0发布说明

修复了一个 bug,导致“.”无法从非常规文件(如设备或命名管道)读取和执行命令。


2
我认为这很巧妙,直到我在msys/mingw中尝试它(那里没有/dev文件夹(甚至没有设备)!)。我尝试了许多eval、$()和反引号``的组合。我无法使任何东西工作,所以最后我只是将输出重定向到一个临时文件中,然后对该临时文件进行源处理,最后将其删除。基本上是“sed ... > /tmp/$$.tmp && . /tmp/$$.tmp && rm /tmp/$$.tmp”。有没有人有不使用临时文件的msys/mingw解决方案? - chriv
17
只是一个备注:这个似乎不能像预期的那样处理导出语句。如果管道字符串中有一个导出语句,导出的变量在终端环境中不可用,而使用eval导出也可以完美地工作。 - Phil
2
在bash中,如果没有设置lastpipe选项,这将创建一个子shell,就像“| bash”一样。 - that other guy
1
考虑到MacOS X通常具有bash 3.2(也许Yosemite有4.0?),以及在我的用例中需要使用lastpipe技巧(通过使用sed预处理每行来添加'export'导出文件中的所有变量),我选择采用“eval”$(sed ...)”方法 - 但感谢提供3.2错误警告! - Alex Dupuy
这个答案的问题在于源代码在子shell中执行,因此值在调用shell时丢失。然而,这个变化对我有效 source <(sed ...) ... 但现在我看到它已经在这里被提供了 https://dev59.com/TXM_5IYBdhLWcg3wq1GF#22233248 by @rishta - nhed
显示剩余2条评论

50

尝试使用进程替换,它可以将命令的输出替换为一个临时文件,然后可以从该文件中读取内容:

source <(echo id)

11
这是何等的巫术? - paulotorrens
2
这也是我的第一想法。虽然我更喜欢eval的解决方案。@PauloTorrens <(xyz) 简单地执行xyz并用将写入xyz输出的文件的名称替换<(xyz)。通过例如执行echo <(echo id),很容易理解它的工作原理,输出为/dev/fd/12(12是一个示例),执行cat <(echo id),输出为id,然后执行source <(echo id),与直接编写id相同。 - netdigger
4
@PauloTorrens,这被称为进程替换。请参阅链接文档以获取官方解释,但简短的答案是“<()”是一种特殊语法,专门为此类情况设计的。 - Mark Stosberg
1
@MarkStosberg,你知道这是特殊语法还是只是一个子shell的(...) 重定向吗? - paulotorrens
2
@PauloTorrens,这是一种专门用于进程替换的特殊语法。 - Mark Stosberg
显示剩余3条评论

43

哇,我知道这是一个旧问题,但最近我发现自己也遇到了完全相同的问题(这就是我来到这里的原因)。

无论如何 - 我不喜欢source /dev/stdin的答案,但我认为我找到了更好的答案。实际上,它看起来非常简单:

echo ls -la | xargs xargs

不错,对吧?但实际上这仍然不能满足你的需求,因为如果你有多个命令行,它会将它们合并为单个命令,而不是分别运行每个命令。所以我找到的解决方案是:

ls | ... | xargs -L 1 xargs

-L 1选项意味着每次执行命令最多使用1行。 注意:如果您的行以尾随空格结束,它将与下一行连接!因此,请确保每行都以非空格符结尾。

最后,您可以这样做

ls | ... | xargs -L 1 xargs -t

查看执行的命令(-t为详细模式)。

希望有人能阅读这篇文章!


8
`ls | sed ...`

我觉得 ls | sed ... | source - 看起来更加美观,但是不幸的是,source 不理解 - 代表的是 stdin


看完mark4o的回答,感觉这个问题一直都摆在我们面前了,是吧? - kch
嘿,是啊。我总是忘记那些东西的存在。 - chaos

8
我认为这是这个问题的“正确答案”:
ls | sed ... | while read line; do $line; done

也就是说,在while循环中可以进行管道操作;read命令从其stdin中获取一行文本,并将其分配给变量$line。在循环中,$line成为执行的命令,直到输入结束为止。虽然一些控制结构(如另一个循环)无法正常工作,但在这种情况下,它是适用的。

2

在bash 3.2(macos)上使用mark4o的解决方案,可以使用here字符串来替代管道,例如:

. /dev/stdin <<< "$(grep '^alias' ~/.profile)"

使用以下命令将 ~/.profile 文件中以 alias 开头的行输出到标准输出:. /dev/stdin <<< `grep '^alias' ~/.profile` - William H. Hooper

1

3
如果你在使用Bash,那么$( )语法可能更受推荐。当然了,反引号在更多的Shell中也是可行的。 - dmckee --- ex-moderator kitten
6
$(command) 和 $((1+1)) 可在所有POSIX Shell中使用。我认为许多人因为Vim将它们标记为语法错误而感到困惑,但这只是因为Vim是为很少使用的原始Bourne Shell进行高亮显示。要使Vim正确高亮显示,请在您的.vimrc文件中添加以下内容:let g:is_posix = 1 - pixelbeat

0
为什么不使用source呢?
$ ls | sed ... > out.sh ; source out.sh

他提到临时文件很讨厌。 - chaos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接