Полностью буферизировать вывод команды перед передачей в другую команду?

10

Есть ли способ выполнить команду только после выполнения другой без временного файла? У меня есть одна более длинная команда и другая команда, которая форматирует вывод и отправляет его на HTTP-сервер с помощью curl. Если я просто выполню commandA | commandB, commandBзапустится curl, подключусь к серверу и начну отправку данных. Потому что commandAзанимает так много времени, HTTP-сервер будет тайм-аут. Я могу делать то, что я хочу сcommandA > /tmp/file && commandB </tmp/file && rm -f /tmp/file

Из любопытства я хочу знать, есть ли способ сделать это без временного файла. Я пытался, mbuffer -m 20M -q -P 100но процесс скручивания все еще начинается в самом начале. Mbuffer ждет, пока commandAне закончится фактическая отправка данных. (Данные сами по себе не более нескольких сотен килобайт)

Джозеф говорит восстановить Монику
источник
Как насчет commandA && commandB?
eyoung100
1
что не передает вывод commandAв commandB, не так ли?
Джозеф говорит восстановить Монику
Она запускает команду B, если команда A завершается успешно, что означает, что curl не запускается рано.
eyoung100
2
@ eyoung100, но он не передает стандартный вывод из команды A в стандартный ввод для команды B, а это то, что нужно Джозефу!
roaima
Если он хочет, чтобы вывод прошел, он должен использовать файл. Смотрите ответ cuonglm.
eyoung100

Ответы:

14

Это похоже на пару других ответов. Если у вас есть пакет «moreutils», у вас должна быть spongeкоманда. Пытаться

commandA | sponge | { IFS= read -r x; { printf "%s\n" "$x"; cat; } | commandB; }

Команда spongeв основном является сквозным фильтром (например cat), за исключением того, что она не начинает записывать вывод, пока не прочитает весь ввод. То есть он «впитывает» данные, а затем освобождает их, когда вы сжимаете их (как губка). Так что, в определенной степени, это «обман» - если есть нетривиальный объем данных, spongeпочти наверняка используется временный файл. Но это невидимо для вас; Вам не нужно беспокоиться о таких вещах, как выбор уникального имени файла и его очистка.

{ IFS= read -r x; { printf "%s\n" "$x"; cat; } | commandB; } Считывает первую линию выхода из sponge. Помните, это не появится, пока commandAне закончится.  Затем он запускается commandB, записывает первую строку в канал и вызывает catчтение остальной части вывода и запись его в канал.

G-Man говорит: «Восстанови Монику»
источник
Спасибо! То spongeже самое, что я использовал mbufferдля, но, кажется, лучше подходит здесь. Использование чтения - это умно. Определенно запомним это на будущее.
Джозеф говорит восстановить Монику
@Josef: я никогда не слышал mbufferраньше; на самом деле это может быть так же хорошо, как sponge. Я согласен, что использование readэто умный трюк. Я не могу взять полный кредит на это; время от времени он появляется в ответах в Stack Exchange (U & L, Super User, Ask Ubuntu и т. д.). На самом деле ответ roaima на этот вопрос очень похож на мой, за исключением того, что он не использует sponge(или что-то эквивалентное), поэтому, как я упоминал в комментарии, он не задерживает запуск commandBстолько, сколько вам нужно (в моем понимании твоя проблема).
G-Man говорит: «Восстановите Монику»
github.com/ildar-shaimordanov/perl-utils#sponge содержит версию сценария «губка», заключенную в функцию bash.
Маринара
5

Команды в конвейере запускаются одновременно, вам нужно хранить commandAвыходные данные где-то, чтобы использовать позже. Вы можете избежать временного файла, используя переменную:

output=$(command A; echo A)
printf '%s' "${output%%A}" | commandB
cuonglm
источник
Какова цель echo Aв процессе замены?
Цифровая травма
3
@DigitalTrauma: предотвращает замещение командных строк в конце новых строк.
cuonglm
Ах, я вижу, да - хороший улов возможного углового случая
Digital Trauma
Я понял, что мой ответ был неправильным, поэтому я удалил его. Я думаю, что это выглядит правильно. +1
цифровая травма
Большое спасибо! Это здорово, особенно эхо A / %% A для сохранения новой строки (даже если мне это не нужно),
говорит Йозеф. Восстановите Монику
1

Я не знаю ни одной стандартной утилиты UNIX, которая могла бы решить эту проблему. Одним из вариантов будет использование awkдля накопления commandAвыходного сигнала и сброса его commandBв один выстрел, например, так

commandA  | awk '{x = x ORS $0}; END{printf "%s", x | "commandB"}'

Помните, что это может занять много памяти, так awkкак строит строку из своего ввода.

Iruvar
источник
1
Это снимает последний перевод строки. Предполагая, что последний символ является новой строкой, вам нужен тот \nили другой ORSв конце.
G-Man говорит «Восстановить Монику»
0

Вы можете решить требование с помощью небольшого сценария. Этот конкретный вариант позволяет избежать временного файла и потенциального сбоя памяти за счет дополнительных процессов.

#!/bin/bash
#
IFS= read LINE

if test -n "$LINE"
then
    test -t 2 && echo "Starting $*" >&2
    (
        echo "$LINE"
        cat

    ) | "$@"
else
    exit 0
fi

Если бы вы вызывали скрипт waituntil(и делали его исполняемым, помещали его в и PATHт. Д.), Вы бы использовали его следующим образом

commandA... | waituntil commandB...

пример

( sleep 3 ; date ; id ) | waituntil nl
roaima
источник
1
Все это делает задержку запуска commandBдо commandAтех пор, пока не будет написана первая строка вывода . Это, вероятно, не достаточно хорошо.
G-Man говорит «Восстановить Монику»
@ G-Man, но мы не знаем. Мне нравится твой sponge. Прочь, чтобы прочитать об этом сейчас.
Роайма