Тройник с непониманием процесса замены

Я пытаюсь написать симпатичный принтер для записей LDAP, который выбирает корневую запись LDAP только один раз, а затем направляет вывод в tee это вызывает симпатичный принтер для каждого раздела.

Ради иллюстрации скажем, мой group_entry Функция возвращает LDIF определенного LDAP DN. Детали которых не важны, так что, допустим, они всегда возвращаются:

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и 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 ответ

Решение

Поведение, которое вы описываете, очень похоже на ситуацию, которая может возникнуть в программе на C, которая разветвляется и выполняет другую программу exec (как, разумеется, оболочка и xargs) без надлежащей обработки всех дескрипторов открытого файла. Вы можете остаться в ситуации, когда процесс p1 не завершается, потому что он ожидает наблюдения EOF на своем стандартном входе, но этого никогда не произойдет, потому что другой процесс p2 содержит дескриптор открытого файла для конца записи канала, который предоставляет p1' s стандартный ввод, и p2 сам ожидает p1, чтобы завершить или выполнить какое-либо другое действие.

Тем не менее, я не вижу в этом ничего плохого в вашем конвейере, и я не воспроизводлю зависание с этой более простой моделью...

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

... в версии 4.2.46 из bash, Вполне возможно, что в вашей версии 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 команды в файл. В качестве альтернативы, если важно, чтобы оба выхода перешли на стандартный вывод, то я не вижу хорошей альтернативы, кроме как захватить group_entry выводить и подавать его отдельно каждому pretty_print работа. Вы можете записать его в файл, но это не нужно, и немного грязно. Рассмотрим это вместо этого:

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

Это использует подстановку команд, чтобы захватить вывод group_entry в переменной оболочки (включая символы новой строки) и использует строку здесь, чтобы воспроизвести ее в каждом pretty_print процесс.

Другие вопросы по тегам