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

我非常熟悉 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

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

99912 次浏览

I think your solution is command substitution with backticks: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

See section 3.4.5

`ls | sed ...`

I sort of feel like ls | sed ... | source - would be prettier, but unfortunately source doesn't understand - to mean stdin.

Why not use source then?

$ ls | sed ... > out.sh ; source out.sh
$ ls | sed ... | source /dev/stdin

UPDATE: This works in bash 4.0, as well as tcsh, and dash (if you change source to .). Apparently this was buggy in bash 3.2. From the bash 4.0 release notes:

Fixed a bug that caused `.' to fail to read and execute commands from non-regular files such as devices or named pipes.

The eval command exists for this very purpose.

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

More from the bash manual:

eval

          eval [arguments]

The arguments are concatenated together into a single command, which is then read and executed, and its exit status returned as the exit status of eval. If there are no arguments or only empty arguments, the return status is zero.

Wow, I know this is an old question, but I've found myself with the same exact problem recently (that's how I got here).

Anyway - I don't like the source /dev/stdin answer, but I think I found a better one. It's deceptively simple actually:

echo ls -la | xargs xargs

Nice, right? Actually, this still doesn't do what you want, because if you have multiple lines it will concat them into a single command instead of running each command separately. So the solution I found is:

ls | ... | xargs -L 1 xargs

the -L 1 option means you use (at most) 1 line per command execution. Note: if your line ends with a trailing space, it will be concatenated with the next line! So make sure each line ends with a non-space.

Finally, you can do

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

to see what commands are executed (-t is verbose).

Hope someone reads this!

Try using process substitution, which replaces output of a command with a temporary file which can then be sourced:

source <(echo id)

I believe this is "the right answer" to the question:

ls | sed ... | while read line; do $line; done

That is, one can pipe into a while loop; the read command command takes one line from its stdin and assigns it to the variable $line. $line then becomes the command executed within the loop; and it continues until there are no further lines in its input.

This still won't work with some control structures (like another loop), but it fits the bill in this case.

To use the mark4o's solution on bash 3.2 (macos) a here string can be used instead of pipelines like in this example:

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