труба, {список; } працює лише з деякими програмами


13

Потрібні пояснення від енергетичних користувачів щодо такої непередбачуваної поведінки:

ps -eF | { head -n 1;grep worker; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root       441     2  0     0     0   2 paź15 ?       00:00:00 [kworker/2:1H]

все виглядає нормально, тоді як

ls -la / | { head -n 1;grep sbin; }

відображає лише вихід з head

... Я думав про це stdout 2>&1і не працює ні для мене це дивно, будь-які пояснення чи підказки, як з цим впоратися?


1
Останній повинен надрукувати все. headІ grepнічого не робити там.
Йорданм

так, ти правий. Але замість цього, чому ps -eF працює, коли ls -la / not?
ast

Відповіді:


9

Я провів деякі дослідження, використовуючи, straceі, здається, це пов'язано з тим, як програма з лівого боку трубопроводу робить його записом до терміналу. Коли lsкоманда виконується, вона записує всі дані в єдине ціле write(). Це призводить headдо споживання всього stdin.

З іншого боку, psзаписує дані партіями, тому write()споживається лише перший head, а потім він існує. Пізніші дзвінки до write()перейдуть до нещодавно породженого grepпроцесу.

Це означає, що він би не працював, якби процес, який ви намагаєтесь grep, не відбувся в першому write(), оскільки grepне отримує всі дані (він бачить навіть менше, ніж лише дані мінус перший рядок).

Ось приклад спроби виграти за pid 1 у моїй системі:

$ ps -eF | { head -n2; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | grep '/lib/systemd/systemd$'
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | { head -n1; grep '/lib/systemd/systemd$'; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD

Ваш ps -eFприклад працює лише випадково.


велике і всебічне розгортання велике спасибі
ast

1
Насправді це більше стан перегонів. Просто повільніше виконувати кілька write()дзвінків. Якщо headфункція read()виклику була повільною (таким, щоб буфер труби мав усі дані в ній), він проявляв би однакову поведінку в обох lsі ps.
Патрік

6

Це викликано буферизацією в glibc. У випадку lsвиведення знаходиться в одному внутрішньому буфері і як такий передається просто до head. Для ps -eF, вихід більший і тому, коли headзакінчується, наступне grepотримує решта частин (але не цілого) виводу ps.

Ви можете позбутися від неї, скасувавши трубу - наприклад, за допомогою sed -u(я не впевнений, що це не розширення GNU):

$ ls -al / | sed -u "#" | { head -n 1; grep bin; }
total 76
drwxr-xr-x   2 root root  4096 Oct  2 21:52 bin
drwxr-xr-x   2 root root  8192 Oct  3 01:54 sbin

4

Що відбувається, це те, що head -n 1читає більше 1 рядка. Для оптимальної пропускної здатності голова зчитує шматки байтів, тому вона може читати 1024 байти за один раз, а потім переглядати ці байти для першого розриву рядка. Оскільки розрив рядка може статися в середині 1024 байтів, решта даних втрачається. Його не можна повернути на трубу. Отже, наступний процес, який виконується, отримує лише байти 1025 і далі.

Ваша перша команда виявляється успішною, оскільки kworkerпроцес відбувається після цього першого фрагмента, який headчитається.

Для того, щоб це працювало, headдоведеться читати по 1 символу за один раз. Але це надзвичайно повільно, так що це не так.
Єдиний спосіб зробити щось подібне ефективно - це зробити єдиний процес, як "голова", так і "греп".

Ось два способи зробити це:

echo -e '1\n2\n3\n4\n5' | perl -ne 'print if $i++ == 0 || /4/'

або

echo -e '1\n2\n3\n4\n5' | awk '{if (NR == 1 || /4/) print }'

Є набагато більше ...


так, я знав "спосіб awk" для вирішення цього завдання, але мені було цікаво, чому поведінка настільки непередбачувана для {list; }. Дякуємо за уточнення, як це працює. Я вражений усім вище відповіді
аст

2

Якщо ви хочете лише перший чи два рядки, працює наступний тип хитрощів і уникає проблем буферизації, викликаних використанням двох різних команд для читання вихідного потоку:

$ ps -eF   | { IFS= read -r x ; echo "$x" ; grep worker; }
$ ls -la / | { IFS= read -r x ; echo "$x" ; grep sbin; }

readВбудований в оболонку і не споживає весь буфер введення тільки для виведення одна лінія, тому використання readзалишає все інше на виході для наступної команди.

Якщо ви хочете підкреслити проблеми буферизації, показані у ваших прикладах, які використовують дві різні команди, додайте sleepдо них, щоб усунути проблеми з тимчасовим терміном і дозволити команді зліва генерувати всі свої результати перед командами праворуч спробувати прочитати будь-яку з це:

$ ps -eF   | { sleep 5 ; head -n 1 ; grep worker; }
$ ls -la / | { sleep 5 ; head -n 1 ; grep sbin; }

Тепер обидва вищевказані приклади провалюються однаково - headзчитується цілий буфер виводу просто для створення одного рядка, і цей буфер недоступний для наступних grep.

Ви можете побачити проблему буферизації ще чіткіше, використовуючи кілька прикладів, які нумерують вихідні рядки, щоб ви могли сказати, які рядки відсутні:

$ ps -eF          | cat -n | { sleep 5 ; head -n 1 ; head ; }
$ ls -la /usr/bin | cat -n | { sleep 5 ; head -n 1 ; head ; }

Простий спосіб побачити проблему буферизації - це використання, seqщо генерує список чисел. Ми можемо легко визначити, які цифри пропущені:

$ seq 1 100000    | { sleep 5 ; head -n 1 ; head ; }
1

1861
1862
1863
1864
1865
1866
1867
1868
1869

Моє рішення про хитрість, що використовує оболонку для читання та повторення першого рядка, працює правильно навіть із доданим затримкою сну:

$ seq 1 100000 | { sleep 5 ; IFS= read -r x ; echo "$x" ; head ; }
1
2
3
4
5
6
7
8
9
10
11

Нижче наводиться повний приклад, що показує headпроблеми буферизації, показує, як headвитрачається цілий буфер виводу просто для отримання його п'яти рядків кожного разу. Цей спожитий буфер недоступний для наступної headкоманди в послідовності:

$ seq 1 100000 | { sleep 5 ; head -5 ; head -5 ; head -5 ; head -5 ; }
1
2
3
4
5

1861
1862
1863
1864
499
3500
3501
3502
3503
7
5138
5139
5140
5141

Переглядаючи число, 1861наведене вище, ми можемо обчислити розмір використовуваного буфера headшляхом підрахунку seqвиходу від 1до 1860:

$ seq 1 1860 | wc -c
8193

Ми бачимо, що headце буферизація, читаючи повний 8 КБ (8 * 1024 байт) вихідної труби за один раз, навіть для отримання лише декількох ліній власного виводу.

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