1

我正在尝试为 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我可以通过一些'ing 和'ing轻松地分别提取所有者和成员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
4

1 回答 1

1

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

尽管如此,我认为您的管道在这方面没有任何本质上的问题,而且我不会用这个更简单的模型重现问题......

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

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

无论如何,即使您的管道没有挂起,它也没有您想要的语义,正如@chepner 在评论中指出的那样。将在其标准输入上pretty_print member接收 的输出,这将包括的输出和 的输出。您可以考虑以不同的方式实现它:由于 tee 可以多路复用输入两种以上的方式,因此您可以通过以下方式用一块石头杀死两只鸟:teegroup_entrypretty_print owner

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

但这留下了两个pretty_print执行的输出将混合在一起的可能性,并且也会回显group_entry输出。您可以想象过滤掉group_entry输出,但为了避免混合,您需要确保两个pretty_print命令按顺序运行。这对基于 - 的方法提出了一个问题tee,因为如果tee' 的任何输出阻塞,则整个管道可能会停止。

一种解决方案是将一个或两个pretty_print命令的输出重定向到文件。group_entry或者,如果两个输出都必须进入标准输出,那么我认为除了捕获输出并将其分别提供给每个pretty_print作业之外别无选择。您可以将其捕获到文件中,但这是不必要的,而且有点混乱。考虑一下:

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

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

于 2017-03-13T15:05:52.993 回答