如何在shell中将输出重定向到变量?

259

我有一个类似于这样的脚本

genhash --use-ssl -s $IP -p 443 --url $URL | grep MD5 | grep -c $MD5

我想把由genhash生成的流存到一个变量中,如何将其重定向到变量$hash中,以便在条件语句中进行比较?

if [ $hash -ne 0 ]
  then echo KO
  exit 0
else echo -n OK
  exit 0
fi

4
这个问题比链接的答案更早。因此,链接的答案是重复的,应该标记为“之前已提问”,而不是这个问题。 - Breandán Dalton
Bash和Shell是不同的东西,因此它们不重复。Shell不支持所有Bash的功能。 - matfax
8个回答

396
使用$( ... )结构:
hash=$(genhash --use-ssl -s $IP -p 443 --url $URL | grep MD5 | grep -c $MD5)

73
这会捕获命令的输出,但不使用重定向。如果你确实需要使用重定向来满足更复杂的需求,请查看我的回答。是因为谷歌把你带到这里了对吧?为什么要去其他地方寻找你搜索的答案呢? - Bruno Bronosky
13
实际上并不是对问题的回答。 - Dmitry Zagorulkin
19
@BrunoBronosky 这是对我有效的方法:result=$(( $your_command ) 2>&1) - Katie
4
当你使用$( ... )时,它会使用一个新的子shell,因此命令的结果可能与主shell中的结果不同。 - Andrey Izman
1
如果您能明确说明这个程序只捕获STDOUT还是同时捕获STDERR,那对其他人会很有帮助。如果您能展示如何在$(...)内打印命令的退出码,那将非常有帮助。 - Olsgaard

284

简而言之

"abc" 存储到 $foo 中:

echo "abc" | read foo

但是,由于管道会创建进程,所以必须在管道结束之前使用$foo,因此...

echo "abc" | ( read foo; date +"I received $foo on %D"; )

当然,其他答案都展示了如何不回答提问者的问题,但这真的会让我们其他人很困扰,因为我们搜索的是提问者的问题。
回答这个问题的方法是使用read命令。
以下是如何操作:
# I would usually do this on one line, but for readability...
series | of | commands \
| \
(
  read string;
  mystic_command --opt "$string" /path/to/file
) \
| \
handle_mystified_file

以下是它的功能和重要性:
  1. 假设 series | of | commands 是一系列非常复杂的管道命令。

  2. mystic_command 可以接受文件内容作为标准输入,而不是文件路径,但不能使用 --opt 参数,因此必须将其作为变量输入。该命令输出修改后的内容,并通常被重定向到文件或传递给另一个命令。(例如 sedawkperl 等)

  3. read 获取标准输入并将其放入变量 $string

  4. readmystic_command 放入括号中的“子 shell”中并不是必需的,但使其像连续的管道一样流动,就好像这两个命令在单独的脚本文件中一样。

总是有替代方案,在这种情况下,与我上面的示例相比,替代方案很丑陋且难以阅读。

# my example above as a oneliner
series | of | commands | (read string; mystic_command --opt "$string" /path/to/file) | handle_mystified_file

# ugly and unreadable alternative
mystic_command --opt "$(series | of | commands)" /path/to/file | handle_mystified_file

我的方法完全是按时间顺序和逻辑进行的。另一种方法从第四个命令开始,将命令1、2和3推入命令替换中。
我有一个现实世界的例子在this script中,但我没有将其用作上面的示例,因为它还有其他疯狂/混乱/令人分心的bash魔法。

3
我也认为这是正确的答案。有些情况下,$(...) 无法使用,而管道在任何地方都被接受。 - Pierre
3
谢谢您发表这个答案,能否在父进程中访问变量(例如,在管道线路的更远位置,比如在“handle_mystified_file”部分)?由于read操作在子进程中进行,无法影响父进程中的变量,因此似乎这是不可能的。我正在尝试使用tee来拆分进程的输出,并通过将一个进程的输出作为另一个进程的参数来“重新连接”tee分割的进程。 - user001
3
@PhươngNguyễn,这种方法行不通,因为管道会创建进程分支。这也是我不得不添加括号的另一个原因。echo "test" | (read ROLE_X; echo $ROLE_X ) 关于此分支的更多信息请参见 https://dev59.com/CmYr5IYBdhLWcg3wVYwg#13764018。 - Bruno Bronosky
3
带括号的命令仍然无法运行。 ls | (read $x; echo $x) 只会输出 ls 输出的第一行。因为 'read' 命令不能读取多于一行的输出。 - Phil Goetz
5
如果您的标准输入将有多行,您可以使用 read -d '' string - Bruno Bronosky
显示剩余20条评论

82

8
这需要更多的点赞。这是最近对我起作用的唯一解决方案。 - Phương Nguyễn
7
当子命令不能在子shell中运行时,这将非常有用。谢谢! - ensonic
2
我希望更多的人理解<(…)的使用,因为它非常棒。原帖应该给出一个解释。基本上,它可以运行子进程,但不是将其传递,而是返回一个文件描述符。这使您可以在需要文件的地方使用命令。例如(作为root):vimdiff /etc/sysconfig/iptables <(iptables-save),这最终会打开一个类似于/proc/23565/fd/63的文件名。是的,vim可以接受-作为文件名并从stdin读取,但那只是一个例子。很多事情都不使用-或者用<()允许的命令顺序更加易读。 - Bruno Bronosky
2
@IntrepidDude 我编辑了答案,包括你(和其他人)需要知道的内容。感谢你的提问。这将使每个人受益。 - Bruno Bronosky
看起来这只捕获了 STDOUT,而没有捕获 STDERR - Olsgaard
显示剩余2条评论

21

我猜这是一种兼容的方式:

hash=`genhash --use-ssl -s $IP -p 443 --url $URL | grep MD5 | grep -c $MD5`

但我更喜欢

hash="$(genhash --use-ssl -s $IP -p 443 --url $URL | grep MD5 | grep -c $MD5)"

1
谢谢您提到兼容性。在其他方法无法解决问题时,这是在我的环境中有效的方法。 - jlsecrest
1
$(...) 周围不需要引号。 - nikolay
1
是的,确实有需要。 例如,如果输出中有一个*号,根据您稍后处理它的方式,它将尝试将其扩展为全局通配符。 - Koshinae
2
@Koshinae,它将尝试扩展echo $hash。但是,如果您执行hash=$(echo '*'0); echo "$hash",它将保持*不变。 - ony
如果结果是多个单词,它会创建一个数组,对吧?因此,通过将其用引号括起来,它会创建一个字符串,而不是字符串数组。我不是很确定,相对于Bash来说还比较新。 - Mathieu Dumoulin
@MathieuDumoulin,当我需要将单词存储在数组中时,我使用ab=($(echo a b))。否则它将被分配为字符串。 - ony

9

1
你甚至可能想要编写一个复杂的管道到自己的shell脚本中,并调用它hash=$(getHash.sh) -- shell脚本将在新终端中可用,即使在重新启动后也是如此。 - Able Mac

5

您可以进行以下操作:

hash=$(genhash --use-ssl -s $IP -p 443 --url $URL)

或者

hash=`genhash --use-ssl -s $IP -p 443 --url $URL`

如果您想要将整个管道的结果分配给变量,可以在上述赋值语句中使用整个管道。

3

有时候我在使用$(`code`)构造函数时会出现错误。

最终,我在这里找到了一些解决方法:https://dev59.com/WWsz5IYBdhLWcg3wbHTG#7902174

基本上,使用Tee重新读取输出并将其放入变量中。这样你就可以看到正常的输出,然后从输出中读取它。

是这样吗?我猜你当前的任务genhash只会输出一个字符串哈希,所以可能适合你。

我是个新手,仍在寻找完整的输出和保存到一个命令中的方法。 谢谢。


2
创建一个函数,将其调用为您想要调用的命令。在这种情况下,我需要使用 ruok 命令。
然后,调用该函数并将其结果分配给一个变量。在这种情况下,我将结果分配给变量 health
function ruok {
  echo ruok | nc *ip* 2181
}

health=echo ruok *ip*

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