Есть ли способ, кроме чтения / эха, чтобы прочитать одну строку из дескриптора файла в стандартный вывод, не закрывая fd?

Я столкнулся с ситуацией, когда я делал:

outputStuff |
filterStuff |
transformStuff |
doMoreStuff |
endStuff > endFile

Я хочу иметь возможность вставлять некоторые вещи отладки таким образом, как:

tee debugFile.$((debugNum++)) |

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

exec 5< <(seq 1 100)
outputStuff |
tee debugFile.$(read -u 5;echo $REPLY;) |
filterStuff |
tee debugFile.$(read -u 5;echo $REPLY;) |
transformStuff |
tee debugFile.$(read -u 5;echo $REPLY;) |
doMoreStuff |
endStuff > endFile

то есть я хочу, чтобы строка отладки, которую я вставлял, была идентична, поэтому мне не нужно беспокоиться о том, чтобы наступать на разные вещи. Эхо чтения /REPLY кажется действительно уродливым... Полагаю, я мог бы обернуть функцию... но есть ли способ прочитать одну строку из дескриптора файла в стандартный вывод, не закрывая fd (как голова -1 закрыла бы fd Я сделал голову -1 <& 3)

4 ответа

Решение

Я попробовал несколько вещей, но в итоге @Etan Reisner доказал мне (непреднамеренно), что даже если есть способ сделать то, что вы просили (умный, Etan), это не то, что вы на самом деле хотите. Если вы хотите быть уверены, что читаете числа последовательно, то чтения должны быть сериализованы, а команды в конвейере - нет.

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

debugum=1
eval "
    outputStuff |
    tee debugFile.$((debugNum++)) |
    filterStuff |
    transformStuff |
    doMoreStuff |
    tee debugFile.$((debugNum++)) |
    endStuff > endFile
"

Таким образом, все замены выполняются родительской оболочкой в ​​строке перед запуском любой из команд.

Поместите все чтения внутри одного составного оператора и перенаправьте ввод из дескриптора 5 для этого.

{ outputStuff    | tee debugFile.$(read -u 5;echo $REPLY;) |
  filterStuff    | tee debugFile.$(read -u 5;echo $REPLY;) |
  transformStuff | tee debugFile.$(read -u 5;echo $REPLY;) |
  doMoreStuff    |
  endStuff
} 5< <(seq 1 100) > endFile

Теперь файловый дескриптор 5 открывается один раз (и закрывается один раз), и каждый вызов read получает последовательные строки из этого дескриптора.

(Вы также можете немного упростить это; если вы не предоставляете outputStuff при вводе с клавиатуры, кажется, нет необходимости использовать дескриптор файла 5 вместо стандартного ввода, так как только outputStuff читает со стандартного ввода. Все остальные программы читают свой стандартный ввод через конвейер.)

Вот подход, который не нужен seq совсем:

Определите функцию, которая рекурсивно создает ваш конвейер.

buildWithDebugging() {
  local -a nextCmd=( )
  local debugNum=$1; shift
  while (( $# )) && [[ $1 != '|' ]]; do
    nextCmd+=( "$1" )
    shift
  done
  if (( ${#nextCmd[@]} )); then
    "${nextCmd[@]}" \
      | tee "debugFile.$debugNum" \
      | buildWithDebugging "$((debugNum + 1))" "$@"
  else
    cat # noop
  fi
}

... и, чтобы использовать это:

buildWithDebugging 0 \
  outputStuff '|'
  filterStuff '|'
  transformStuff '|'
  doMoreStuff '|'
  endStuff > endFile

Более безопасная версия будет использовать компоненты конвейера, выполненные в стиле строк Pascal, а не строк C - то есть вместо использования литерала |s, предшествующий каждой строке команд с ее длиной:

buildWithDebugging 0 \
  1 outputStuff
  3 filterStuff filterArg filterSecondArg
  2 transformStuff filterArg
  1 doMoreStuff
  endStuff > endFile

Построение этого должно быть совершенно тривиальным упражнением для читателя.:)

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

exec 5< <(seq -w 10 -1 1)
echo -n |
{ echo "d 1:$(dd bs=1 count=3 2>/dev/null <&5)"; cat; } |
{ echo "d 2:$(dd bs=1 count=3 2>/dev/null <&5)"; cat; } |
{ echo "d 3:$(dd bs=1 count=3 2>/dev/null <&5)"; cat; } |
{ echo "d 4:$(dd bs=1 count=3 2>/dev/null <&5)"; cat; } |
cat

Вы также можете просто использовать более короткую переменную с read: read -u 5 a;echo $a но это спасает только двух персонажей.

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