Як я можу виконати трубу?


27

Я хочу до timeкоманди, яка складається з двох окремих команд з одним висновком трубопроводу до іншого. Наприклад, розглянемо два сценарії нижче:

$ cat foo.sh
#!/bin/sh
sleep 4

$ cat bar.sh
#!/bin/sh
sleep 2

Тепер, як я можу змусити timeповідомити про витрачений час foo.sh | bar.sh(і так, я знаю, що тут немає сенсу, але це лише приклад)? Це працює так, як очікувалося, якщо я запускаю їх послідовно в підпакеті без трубопроводів:

$ time ( foo.sh; bar.sh )

real    0m6.020s
user    0m0.010s
sys     0m0.003s

Але я не можу змусити його працювати при трубопроводі:

$ time ( foo.sh | bar.sh )

real    0m4.009s
user    0m0.007s
sys     0m0.003s

$ time ( { foo.sh | bar.sh; } )

real    0m4.008s
user    0m0.007s
sys     0m0.000s

$ time sh -c "foo.sh | bar.sh "

real    0m4.006s
user    0m0.000s
sys     0m0.000s

Я читав подібне запитання ( Як запустити час на декілька команд ТА записати час на виведення у файл? ), А також спробував автономний timeвиконуваний файл:

$ /usr/bin/time -p sh -c "foo.sh | bar.sh"
real 4.01
user 0.00
sys 0.00

Він навіть не працює, якщо я створити третій сценарій, який запускає лише трубу:

$ cat baz.sh
#!/bin/sh
foo.sh | bar.sh

А потім час, що:

$ time baz.sh

real    0m4.009s
user    0m0.003s
sys     0m0.000s

Цікаво, що він не видається, timeяк тільки виходить перша команда. Якщо я перейду bar.shна:

#!/bin/sh
sleep 2
seq 1 5

І timeзнову, я очікував, що timeвихід буде надрукований раніше, seqале це не так:

$ time ( { foo.sh | bar.sh; } )
1
2
3
4
5

real    0m4.005s
user    0m0.003s
sys     0m0.000s

Схоже time, не рахується часу, необхідного для його виконання, bar.shнезважаючи на очікування закінчення до друку звіту 1 .

Всі тести проводили за системою Arch з використанням bash 4.4.12 (1) -випуск. Я можу використовувати лише bash для проекту, це частина, тому навіть якщо zshабо якась інша потужна оболонка може обійти її, це не буде життєздатним рішенням для мене.

Отже, як я можу отримати час для запуску набору команд з трубопроводами? І хоча ми це робимо, чому це не працює? Схоже, що timeмиттєво завершуємо роботу, як тільки перша команда закінчена. Чому?

Я знаю, що я можу отримати окремий час приблизно таким чином:

( time foo.sh ) 2>foo.time | ( time bar.sh ) 2> bar.time

Але я все-таки хотів би знати, чи можна присвоїти цю справу як одну операцію.


1 Це, здається, не є проблемою з буфером, я спробував запустити сценарії з, unbufferedі stdbuf -i0 -o0 -e0цифри все ще надрукувались до timeвиводу.


Ви спробували це з фізичним секундоміром?
перицинтіон

@pericynthion yep врешті-решт я. І це також показало, що пояснює відповіді: час насправді працює, але (очевидно, достатньо і, як я мав би зрозуміти) команди в трубопроводі працюють одночасно, тому час, що займається, є часом найповільнішим.
тердон

Відповіді:


33

Він буде працювати.

Різні частини трубопроводу виконуються одночасно. Єдине, що синхронізує / серіалізує процеси в трубопроводі, це IO, тобто один процес запису до наступного процесу в трубопроводі і наступний процес, що читає те, що пише перший. Крім того, вони виконують незалежно один від одного.

Оскільки між процесами у вашому трубопроводі не відбувається читання чи запису, час, який потрібен для виконання конвеєра, - це найдовший sleepвиклик.

Ви, можливо, також написали

time ( foo.sh & bar.sh &; wait )

Тердон розмістив у чаті пару трохи змінених прикладних сценаріїв :

#!/bin/sh
# This is "foo.sh"
echo 1; sleep 1
echo 2; sleep 1
echo 3; sleep 1
echo 4

і

#!/bin/sh
# This is "bar.sh"
sleep 2
while read line; do
  echo "LL $line"
done
sleep 1

Запит був "чому time ( sh foo.sh | sh bar.sh )повертається 4 секунди, а не 3 + 3 = 6 секунд?"

Щоб побачити, що відбувається, включаючи приблизний час виконання кожної команди, можна зробити це (висновок містить мої анотації):

$ time ( env PS4='$SECONDS foo: ' sh -x foo.sh | PS4='$SECONDS bar: ' sh -x bar.sh )
0 bar: sleep 2
0 foo: echo 1     ; The output is buffered
0 foo: sleep 1
1 foo: echo 2     ; The output is buffered
1 foo: sleep 1
2 bar: read line  ; "bar" wakes up and reads the two first echoes
2 bar: echo LL 1
LL 1
2 bar: read line
2 bar: echo LL 2
LL 2
2 bar: read line  ; "bar" waits for more
2 foo: echo 3     ; "foo" wakes up from its second sleep
2 bar: echo LL 3
LL 3
2 bar: read line
2 foo: sleep 1
3 foo: echo 4     ; "foo" does the last echo and exits
3 bar: echo LL 4
LL 4
3 bar: read line  ; "bar" fails to read more
3 bar: sleep 1    ; ... and goes to sleep for one second

real    0m4.14s
user    0m0.00s
sys     0m0.10s

Отже, підсумовуючи, трубопровід займає 4 секунди, а не 6, через буферизацію виходу перших двох дзвінків echoв foo.sh.


1
@terdon значень - це суми, але сценарії займають дуже мало часу користувача та системи - вони просто чекають, що не враховується (за винятком часу настінного часу).
Стівен Кітт

2
Зауважте, що деякі снаряди, як оболонка Борна, або ksh93лише чекають останнього компонента трубопроводу ( sleep 3 | sleep 1триватиме 1 секунду). оболонка Bourne не має timeключового слова, але в процесі ksh93запуску timeвсі компоненти чекають.
Стефан Шазелас

3
Я просто кажу, що можна здивуватися, виявивши, що ця sleep 10 | sleep 1секунда time sleep 10 | sleep 1займає 10 секунд в ksh93. У оболонці Борна time sleep 10 | sleep 1знадобиться одна секунда, але ви отримаєте час ( sleep 10лише та з /usr/bin/time) із синього кольору через 9 секунд.
Стефан Шазелас

1
Це не про охорону нічого. timeправильно розраховує конвеєр, але змінює поведінку оболонки в ksh93. (sleep 10 | sleep 1)займає 1 секунду, time (sleep 10 | sleep 1)займає 10 секунд. { (sleep 10 | sleep 1); echo x; }виводиться xчерез 1 секунду, time { (sleep 10 | sleep 1); echo x; }виходить xчерез 10 секунд. Те саме, якщо ви введете цей код у функцію та час функціонування.
Стефан Шазелас

1
Зауважте, що ksh93як у zsh( -o promptsubstтут), ви можете зробити, typeset -F SECONDSщоб отримати менш приблизну кількість секунд (POSIX shне має SECONDS)
Stéphane Chazelas

10

Це був би кращий приклад?

$ time perl -e 'alarm(3); 1 while 1;' | perl -e 'alarm(4); 1 while 1;'
Alarm clock

real    0m4.004s
user    0m6.992s
sys     0m0.004s

Сценарії займають 3 та 4 секунди (відповідно), займаючи в режимі реального часу 4 секунди через паралельне виконання та 7 секунд часу процесора. (принаймні приблизно.)

Або це:

$ time ( sleep 2; echo) | ( read x; sleep 3 )

real    0m5.004s
user    0m0.000s
sys     0m0.000s

Вони не працюють паралельно, тому загальний витрачений час становить 5 секунд. Все це просинало спати, тому жоден час процесора не використовується.


3

Якщо у вас є, sysdigви можете вставити трасери в довільних точках, припускаючи, що ви можете змінити код, щоб додати необхідні записи/dev/null

echo '>::blah::' >/dev/null
foo.sh | bar.sh
echo '<::blah::' >/dev/null

(але це не відповідає вашій вимозі "однієї операції"), а потім записуйте речі через

$ sudo sysdig -w blalog "span.tags contains blah"

і тоді вам, ймовірно, знадобиться долото sysdig, щоб експортувати лише тривалість

description = "Exports sysdig span tag durations";
short_description = "Export span tag durations.";
category = "Tracers";

args = {}

function on_init()
    ftags = chisel.request_field("span.tags")
    flatency = chisel.request_field("span.duration")
    chisel.set_filter("evt.type=tracer and evt.dir=<")
    return true
end

function on_event()
    local tags = evt.field(ftags)
    local latency = evt.field(flatency)
    if latency then
        print(tostring(tags) .. "\t" .. tonumber(latency) / 1e9)
    end
    return true
end

яка колись збережена у вашому sysdig/chiselsкаталозі як файл, spantagduration.luaможе використовуватися як

$ sysdig -r blalog -c spantagduration
...

Або ви можете пограти з csysdigабо на виході JSON.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.