Bash 具有过程替换的Tee

Bash 具有过程替换的Tee,bash,tee,process-substitution,Bash,Tee,Process Substitution,我正在尝试为LDAP条目编写一个漂亮的打印机,它只获取根LDAP记录一次,然后将输出导入tee,为每个部分调用漂亮的打印机 为了便于说明,假设mygroup\u 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=ex

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

为了便于说明,假设my
group\u 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
'ing和
cut
'ing轻松地分别提取所有者和成员。然后,我可以将这些辅助DNs导入另一个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
这些单独工作很好,但当我尝试将它们结合在一起时,什么也没有发生:

$ 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

您所描述的行为看起来非常类似于C程序中可能出现的情况,即fork和exec的另一个程序(正如shell和xargs所做的那样)没有正确处理所有打开的文件描述符。您可能会遇到这样的情况:进程p1没有终止,因为它正在等待观察其标准输入上的EOF,但它永远不会终止,因为另一个进程p2持有提供p1标准输入的管道写入端的打开文件描述符,而p2本身正在等待p1终止或执行某些其他操作

然而,在这方面,我看不出您的管道有任何内在的错误,我也不想用这个更简单的模型重现问题

echo "foo" | tee >(cat) | cat
。。。在
bash
的4.2.46版中。尽管如此,您的
bash
版本(即使是同一版本)或
xargs
中可能存在相关的bug,但这只是推测。我不认为你们的管道应该像你们说的那个样悬挂,但我不准备开始指责你们

无论如何,即使您的管道没有挂起,它也没有您想要的语义,正如@chepner在评论中指出的那样。
pretty\u print成员将在其标准输入上接收
tee
的输出,这将包括
group\u entry
的输出和
pretty\u print owner
的输出。你可以考虑用不同的方式实现:因为TEE可以复用两种以上的输入方式,你可以一举两得:

group_entry "$1" | tee >(pretty_print owner) >(pretty_print member)
但这就留下了两个
pretty_print
执行的输出将混合在一起的可能性,并且还与
group_条目的输出相呼应。可以想象,您可以过滤掉
group\u条目
输出,但为了避免混淆,您需要确保两个
pretty\u print
命令按顺序运行。这给基于
tee
的方法带来了一个问题,因为如果
tee
的任何输出阻塞,那么整个管道都可能停止

一种解决方案是将一个或两个
pretty\u print
命令的输出重定向到一个文件。或者,如果两个输出都必须转到标准输出,那么我认为除了捕获
group\u条目
输出,并将其分别馈送到每个
pretty\u print
作业之外,没有什么好的选择。您可以将其捕获到文件中,但这是不必要的,而且有点混乱。考虑这个问题:

entry_lines=$(group_entry "$1")
pretty_print owner  <<<"$entry_lines"
pretty_print member <<<"$entry_lines"
entry\u line=$(组\u条目“$1”)

pretty_print owner如果您可以共享您正在尝试的实际代码+您错误的输出+您预期的输出,我们将很容易为您提供更好的帮助。另外一个问题是,对
pretty_print owner
的调用从
tee
的同一位置继承其标准输出,这意味着
pretty_print成员
将获得一些额外的、意外的输入。对
pretty_print
的两个调用异步运行也存在问题,因此,如果它们写入同一文件,它们的输出可能会交错。(我现在添加了脚本)