Замена и синхронизация процесса Bash - PullRequest
18 голосов
/ 20 декабря 2010

(возможно, связано с Некоторые программы не допускают подстановку процессов для входных файлов? )

В некоторых скриптах модульного тестирования Bash я использую следующий трюк для регистрации и отображения stdout и stderr команды:

command > >(tee "${stdoutF}") 2> >(tee "${stderrF}" >&2)

Этот процесс производит вывод в stdout, поэтому файл $stdoutF получает некоторые данные. Затем я запускаю другую команду, которая не выводит никаких данных:

diff -r "$source" "$target" > >(tee "${stdoutF}") 2> >(tee "${stderrF}" >&2)

Однако не похоже, что этот процесс всегда успешно завершается до запуска теста на пустоту (с помощью shunit-ng ):

assertNull 'Unexpected output to stdout' "$(<"$stdoutF")"

В тесте с 100 прогонами это не удавалось 25 раз.

Достаточно ли позвонить sync перед проверкой файла на пустоту:

sync
assertNull 'Unexpected output to stdout' "$(<"$stdoutF")"

... и / или должен ли он работать, форсируя последовательность команд:

diff -r "$source" "$target" \
> >(tee "${stdoutF}"; assertNull 'Unexpected output to stdout' "$(<"$stdoutF")")
2> >(tee "${stderrF}" >&2)

... и / или возможно ли tee это как-то assertNull напрямую вместо файла?

Обновление : sync не ответ - см. Ответ Жиля ниже.

Обновление 2 : обсуждение продолжено до Сохранение stdout, stderr и stdout + stderr синхронно . Спасибо за ответы!

Ответы [ 3 ]

27 голосов
/ 20 декабря 2010

В bash команда замены процесса подстановки foo > >(bar) завершается, как только заканчивается foo. (Это не обсуждается в документации.) Вы можете проверить это с помощью

: > >(sleep 1; echo a)

Эта команда возвращается немедленно, а затем печатает a асинхронно через одну секунду.

В вашем случае команде tee требуется всего лишь один небольшой промежуток времени для завершения после завершения command. Добавление sync дало tee достаточно времени для завершения, но это не снимает условия гонки, равно как и добавление sleep, это только делает гонку менее вероятной для проявления.

В целом, sync не имеет какого-либо внутреннего наблюдаемого эффекта: он имеет значение, только если вы хотите получить доступ к устройству, где ваши файловые системы хранятся в другом экземпляре операционной системы. Проще говоря, если ваша система теряет мощность, после перезагрузки гарантированно будут доступны только данные, записанные до последнего sync.

Что касается удаления состояния гонки, вот несколько возможных подходов:

  • Явно синхронизировать все замещенные процессы.

    mkfifo sync.pipe
    command > >(tee -- "$stdoutF"; echo >sync.pipe)
           2> >(tee -- "$stderrF"; echo >sync.pipe)
    read line < sync.pipe; read line < sync.pipe
    
  • Используйте другое временное имя файла для каждой команды вместо повторного использования $stdoutF и $stderrF и следите за тем, чтобы временный файл всегда создавался заново.

  • Откажитесь от замены процесса и используйте вместо этого трубы.

    { { command | tee -- "$stdoutF" 1>&3; } 2>&1 \
                | tee -- "$stderrF" 1>&2; } 3>&1
    

    Если вам нужен статус возврата команды, bash помещает его в ${PIPESTATUS[0]}.

    { { command | tee -- "$stdoutF" 1>&3; exit ${PIPESTATUS[0]}; } 2>&1 \
                | tee -- "$stderrF" 1>&2; } 3>&1
    if [ ${PIPESTATUS[0]} -ne 0 ]; then echo command failed; fi
    
1 голос
/ 12 мая 2016

Я иногда ставлю охрану:

: > >(sleep 1; echo a; touch guard) \
  && while true; do
    [ -f "guard" ] && { rm guard; break; }
     sleep 0.2
  done    
0 голосов
/ 20 декабря 2010

Вставьте sleep 5 или еще много чего вместо sync, чтобы ответить на ваш последний вопрос

...