У сценарії bash, як захопити stdout рядок за рядком


26

У сценарії bash я хотів би зафіксувати стандартний висновок довгого командного рядка за рядком, щоб їх можна було проаналізувати та повідомити під час виконання початкової команди. Це складний спосіб, який я можу собі уявити:

# Start long command in a separated process and redirect stdout to temp file
longcommand > /tmp/tmp$$.out &

#loop until process completes
ps cax | grep longcommand > /dev/null
while [ $? -eq 0 ]
do
    #capture the last lines in temp file and determine if there is new content to analyse
    tail /tmp/tmp$$.out

    # ...

    sleep 1 s  # sleep in order not to clog cpu

    ps cax | grep longcommand > /dev/null
done

Мені хотілося б знати, чи існує простіший спосіб зробити це.

Редагувати:

Щоб уточнити своє запитання, я додам це. longcommandВідображає його статус через підрядник один раз в секунду. Я хотів би отримати результат до longcommandзавершення.

Таким чином я потенційно можу вбити, longcommandякщо це не дає очікуваних результатів.

Я намагався:

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

Але whatever(наприклад echo) виконується лише після longcommandзавершення.


Відповіді:


32

Просто переведіть команду в whileцикл. У цьому є ряд нюансів, але в основному (в bashабо будь-якій оболонці POSIX):

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

Інша головна проблема з цим (крім IFSречей нижче) - це коли ви намагаєтеся використовувати змінні зсередини циклу, коли він закінчиться. Це тому, що цикл насправді виконується в підколонці (просто інший процес оболонки), з якого ви не можете отримати доступ до змінних (також він закінчується, коли цикл робить, і в цей момент змінних повністю немає. Щоб обійти це, Ви можете зробити:

longcommand | {
  while IFS= read -r line
  do
    whatever "$line"
    lastline="$line"
  done

  # This won't work without the braces.
  echo "The last line was: $lastline"
}

Приклад Hauke по налаштуванню lastpipeв bashінше рішення.

Оновлення

Щоб переконатися, що ви обробляєте висновок команди "як це відбувається", ви можете використовувати stdbufдля встановлення процесу " stdoutбуферний рядок.

stdbuf -oL longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

Це налаштує процес запису по черзі по одному рядку замість внутрішньої буферизації його виводу в блоки. Будьте уважні, що програма може змінити цю настройку самостійно. Аналогічного ефекту можна досягти за допомогою unbuffer(частини expect) або script.

stdbufдоступний у системах GNU та FreeBSD, він впливає лише на stdioбуферизацію та працює лише для не встановлених, не встановлених програм, які динамічно пов'язані (оскільки використовується хитрість LD_PRELOAD).


@Stephane The IFS=не потрібен bash, я перевіряв це, коли останній раз.
Graeme

2
Так. Це не потрібно, якщо ви пропустите line(у такому випадку результат ставиться $REPLYбез обрізних провідних та кінцевих пробілів). Спробуйте: echo ' x ' | bash -c 'read line; echo "[$line]"'і порівняйте з echo ' x ' | bash -c 'IFS= read line; echo "[$line]"'абоecho ' x ' | bash -c 'read; echo "[$REPLY]"'
Stéphane Chazelas

@Stephane, добре, ніколи не зрозумів, що існує різниця між цією іменною змінною. Спасибі.
Graeme

@Graeme Мені, можливо, не було зрозуміло в моєму запитанні, але я хотів би обробити вихідний рядок за рядком до завершення longcommand (щоб швидко реагувати, якщо longcommands відображає повідомлення про помилку). Я відредагую своє питання, щоб зробити ясніше
gfrigon

@gfrigon, оновлено.
Graeme

2
#! /bin/bash
set +m # disable job control in order to allow lastpipe
shopt -s lastpipe
longcommand |
  while IFS= read -r line; do lines[i]="$line"; ((i++)); done
echo "${lines[1]}" # second line
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.