Записать стандартный вывод в переменную, но по-прежнему отображать его в консоли
У меня есть bash script, который вызывает несколько длительных процессов. Я хочу записать вывод этих вызовов в переменные для обработки. Однако, поскольку это длительные процессы, я хотел бы, чтобы вывод вызовов rsync отображался в консоли в режиме реального времени, а не после факта.
С этой целью я нашел способ сделать это, но он полагается на вывод текста в /dev/stderr. Я чувствую, что вывод в /dev/stderr - не лучший способ сделать что-то.
VAR1=$(for i in {1..5}; do sleep 1; echo $i; done | tee /dev/stderr)
VAR2=$(rsync -r -t --out-format='%n%L' --delete -s /path/source1/ /path/target1 | tee /dev/stderr)
VAR3=$(rsync -r -t --out-format='%n%L' --delete -s /path/source2/ /path/target2 | tee /dev/stderr)
В приведенном выше примере я вызываю rsync несколько раз, и я хочу видеть имена файлов по мере их обработки, но в конце я все еще хочу получить результат в переменной, потому что я буду разбирать его позже.
Есть ли "более чистый" способ сделать это?
Если это имеет значение, я использую Ubuntu 12.04, bash 4.2.24.
Ответы
Ответ 1
Дублируйте & 1 в своей оболочке (в моем примере до 5) и используйте & 5 в подоболочке (так что вы будете писать в stdout (& 1) родительской оболочки):
exec 5>&1
FF=$(echo aaa|tee >(cat - >&5))
echo $FF
Будет печатать aaa два раза, из-за эха в подоболочке и второй раз печатать значение переменной.
В вашем коде:
exec 5>&1
VAR1=$(for i in {1..5}; do sleep 1; echo $i; done | tee >(cat - >&5))
# use the value of VAR1
Ответ 2
Ответ Op De Cirkel имеет правильную идею. Его можно упростить еще больше (избегая использования cat
):
exec 5>&1
FF=$(echo aaa|tee /dev/fd/5)
echo $FF
Ответ 3
Здесь пример, содержащий как stderr
, так и код выхода команды. Это строится на ответе Рассела Дэвиса.
exec 5>&1
FF=$(ls /taco/ 2>&1 |tee /dev/fd/5; exit ${PIPESTATUS[0]})
exit_code=$?
echo "$FF"
echo "Exit Code: $exit_code"
Если папка /taco/
существует, это будет захватывать ее содержимое. Если папка не существует, она будет отображать сообщение об ошибке, а код выхода будет равен 2.
Если вы опустите 2>&1
, тогда будет снято только stdout
.
Ответ 4
Вы можете использовать более трех файловых дескрипторов. Попробуйте здесь:
http://tldp.org/LDP/abs/html/io-redirection.html
"Каждому открытому файлу присваивается дескриптор файла. [2] Дескрипторы файлов для stdin, stdout и stderr равны 0, 1 и 2 соответственно. Для открытия дополнительных файлов остаются дескрипторы с 3 по 9. Иногда полезно назначьте один из этих дополнительных файловых дескрипторов stdin, stdout или stderr как временную дублирующую ссылку. "
Дело в том, стоит ли усложнять сценарий только для достижения этого результата. На самом деле это не совсем так, как вы это делаете.
Ответ 5
Если под "консолью" вы подразумеваете свой текущий TTY, попробуйте
variable=$(command with options | tee /dev/tty)
Это немного сомнительная практика, потому что люди, которые пытаются использовать это, иногда удивляются, когда результат попадает куда-то неожиданно, когда у них нет TTY (задания cron и т.д.).
Ответ 6
В качестве альтернативы использованию /dev/tty
или дополнительного файлового дескриптора, как предлагают другие ответы, вы также можете перевернуть его и просто использовать временный файл. Это, возможно, легче читать и более переносимо в определенных ситуациях.
tmpFile=$(mktemp) # mak-a de temp
rsync /a /b | tee $tmpFile # sync my b*tch up
if grep "U F'd up" $tmpFile; then
rm -rf / #Seppuku
fi