与过程替代误解的三通

时间:2017-03-13 13:53:41

标签: bash tee process-substitution

我正在尝试为LDAP条目编写一个漂亮的打印机,它只获取一次根LDAP记录,然后将输出通过管道传输到tee,为每个部分调用漂亮的打印机。

为了便于说明,请说我的group_entry函数返回特定LDAP DN的LDIF。其中的细节并不重要,所以我们总是说它会回归:

dn: cn=foo,dc=example,dc=com
cn: foo
owner: uid=foo,dc=example,dc=com
owner: uid=bar,dc=example,dc=com
member: uid=foo,dc=example,dc=com
member: uid=baz,dc=example,dc=com
member: uid=quux,dc=example,dc=com
custom: abc123

我可以通过grep&{39}和cut分别轻松提取所有者和成员。然后,我可以将这些辅助DN管道传输到另一个LDAP搜索查询中以获取其真实姓名。为了举例,我们假设我有一个pretty_print函数,它在LDAP属性名称上进行参数化,它完成了我刚才提到的所有内容,然后使用AWK很好地格式化了所有内容:

$ group_entry | pretty_print owner
Owners:
foo    Mr Foo
bar    Dr Bar

$ group_entry | pretty_print member
Members:
foo    Mr Foo
baz    Bazzy McBazFace
quux   The Artist Formerly Known as Quux

这些单独工作很好,但当我尝试tee他们在一起时,没有任何反应:

$ group_entry | tee >(pretty_print owner) | pretty_print member
Members:
[Sits there waiting for Ctrl+C]

显然,我对这应该如何运作有一些误解,但它逃脱了我。我做错了什么?

编辑为了完整起见,这是我的完整脚本:

#!/usr/bin/env bash

set -eu -o pipefail

LDAPSEARCH="ldapsearch -xLLL"

group_entry() {
  local group="$1"
  ${LDAPSEARCH} "(&(objectClass=posixGroup)(cn=${group}))"
}

get_attribute() {
  local attr="$1"
  grep "${attr}:" | cut -d" " -f2
}

get_names() {
  # We strip blank lines out of the LDIF entry, then we always have "dn"
  # followed by "cn" records; we strip off the attribute name and
  # concatenate those lines, then sort. So we get a sorted list of:
  # {{distinguished_name}} {{real_name}}
  xargs -n1 -J% ${LDAPSEARCH} -s base -b % cn \
  | grep -v "^$" \
  | cut -d" " -f2- \
  | paste - - \
  | sort
}

pretty_print() {
  local attr="$1"
  local -A pretty=([member]="Members" [owner]="Owners")

  get_attribute "${attr}" \
  | get_names \
  | gawk -F'\t' -v title="${pretty[${attr}]}:" '
    BEGIN { print title }
    { print "-", gensub(/^uid=([^,]+),.*$/, "\\1", "g", $1), "\t", $2 }
  '
}

# FIXME I don't know why tee with process substitution doesn't work here
group_entry "$1" | pretty_print owner
group_entry "$1" | pretty_print member

1 个答案:

答案 0 :(得分:1)

您描述的行为非常类似于C程序中可能出现的情况,该程序在没有正确处理所有打开文件描述符的情况下分叉和执行另一个程序(如shell和xargs当然都这样)。您可能会因为进程 p1 没有终止而因为它等待在标准输入上观察EOF而处于离开状态,但它永远不会因为另一个进程 p2 为管道的写入端保存一个打开的文件描述符,提供 p1 的标准输入,而 p2 本身正在等待 p1 < / em>终止或执行其他一些操作。

尽管如此,在这方面我没有看到你的管道存在任何本质上的错误,而且我没有用这个更简单的模型重现这个问题......

echo "foo" | tee >(cat) | cat

...在bash的4.2.46版本中。可能是您的bash版本(即使是相同的版本)或xargs版本中存在相关错误,但这是一种推测性错误。我不认为你的管道应该像你说的那样挂起,但我还没准备开始指责。

无论如何,即使您的管道没有挂起,它也没有您想要的语义,正如@chepner在评论中指出的那样。 pretty_print member将在其标准输入上收到tee的输出,其中包括两者 group_entry的输出和pretty_print owner的输出。你可以考虑以不同的方式实现它:因为tee可以通过两种方式多路输入,你可以通过这样做一举两得:

group_entry "$1" | tee >(pretty_print owner) >(pretty_print member)

但这样就有可能两个pretty_print执行的输出混合在一起,并且还会回显group_entry输出。您可以想象过滤出group_entry输出,但为了避免混合,您需要确保两个pretty_print命令按顺序运行。这给基于tee的方法带来了问题,因为如果任何tee的输出阻塞,那么整个管道就会停止。

一种解决方案是将一个或两个pretty_print命令的输出重定向到文件。或者,如果两个输出都必须转到stdout,那么我认为没有好的选择,只能捕获group_entry输出,并将其单独提供给每个pretty_print作业。你可以将它捕获到一个文件,但这是不必要的,有点乱。请考虑一下:

entry_lines=$(group_entry "$1")
pretty_print owner  <<<"$entry_lines"
pretty_print member <<<"$entry_lines"

使用命令替换来捕获shell变量(包括换行符)中group_entry的输出,并使用here字符串将其重播到每个pretty_print进程中。