Записать стандартный вывод в переменную, но по-прежнему отображать его в консоли

50

У меня есть 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.

Теги:
tee
process-substitution

4 ответа

58
Лучший ответ

Дублируйте & 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
  • 0
    Это сработало, спасибо! Таким образом, в этом случае, «5» должен быть дескриптором файла, я прочитаю и узнаю об этом.
  • 3
    Разве дескриптор не должен быть закрыт в родительской оболочке?
Показать ещё 5 комментариев
27

Ответ Op De Cirkel имеет правильную идею. Его можно упростить еще больше (избегая использования cat):

exec 5>&1
FF=$(echo aaa|tee /dev/fd/5)
echo $FF
  • 6
    Разве / dev / fd / 5 не будет зависеть от ОС?
  • 9
    Мне было интересно, если вы могли бы просто использовать tee /dev/fd/1 , но это не работает, потому что вывод по-прежнему захватывается $() . Таким образом , в случае , если кто - то будет интересно , то же самое, что необходимо использовать дополнительный дескриптор файла (например , 5).
Показать ещё 2 комментария
7

Здесь пример, содержащий как 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.

3

Вы можете использовать более трех дескрипторов файлов. Попробуйте здесь:

http://tldp.org/LDP/abs/html/io-redirection.html

"Каждому открытому файлу присваивается дескриптор файла. [2] Дескрипторы файла для stdin, stdout и stderr равны 0, 1 и 2. Соответственно, для открытия дополнительных файлов остаются дескрипторы с 3 по 9. Это иногда полезно назначить один из этих дополнительных файловых дескрипторов для stdin, stdout или stderr как временную дублируемую ссылку.

Суть в том, стоит ли сделать script более сложным только для достижения этого результата. На самом деле это не совсем так, как вы это делаете.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню