Есть ли способ, кроме чтения / эха, чтобы прочитать одну строку из дескриптора файла в стандартный вывод, не закрывая 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
но это спасает только двух персонажей.