Як можна відрізняти два трубопроводи в Баші?


143

Як можна відрізняти два конвеєри без використання тимчасових файлів у Bash? Скажімо, у вас є два командні конвеєри:

foo | bar
baz | quux

І ви хочете знайти diffїхні результати. Очевидно, одним із рішень було б:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

Чи можна це зробити без використання тимчасових файлів у Bash? Ви можете позбутися від одного тимчасового файлу, проклавши в одному з конвеєрів, щоб розрізняти:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

Але ви не можете передати обидва трубопроводи в різні одночасно (принаймні, не очевидно). Чи є якась хитра хитрість, що /dev/fdстосується цього без використання тимчасових файлів?

Відповіді:


146

Однорядковий файл з 2 tmp-файлами (не тим, що потрібно):

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

З bash ви можете спробувати хоч:

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once

2-я версія більш чітко нагадає, який саме вхід був, показуючи
-- /dev/stdinпорівняно ++ /dev/fd/63чи щось замість двох пронумерованих fds.


Навіть названа труба не з’явиться у файловій системі, принаймні на ОС, де bash може реалізувати підстановку процесу, використовуючи імена файлів, як, /dev/fd/63щоб отримати ім'я файлу, з якого команда може відкривати та читати, щоб насправді читати з уже відкритого дескриптора файлу, який встановлено bash до виконання команди. (тобто bash використовує pipe(2)до fork, а потім dup2для переадресації з виводу в quuxдескриптор вхідного файлу для diff, на fd 63.)

У системі, де немає "магічних" /dev/fdабо /proc/self/fd, bash, можливо, використовуються названі канали для здійснення підстановки процесів, але вона принаймні керує ними сама, на відміну від тимчасових файлів, і ваші дані не записуються у файлову систему.

Ви можете перевірити, як bash реалізує заміну процесу, echo <(true)щоб надрукувати ім'я файлу, а не читати з нього. Друкується /dev/fd/63на типовій системі Linux. Або для отримання більш детальної інформації про те, що саме система викликів використовує bash, ця команда в системі Linux буде відстежувати виклики файлів і файлових дескрипторів

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

Без удару ви можете зробити названу трубу . Використовуйте, -щоб сказати, diffщоб прочитати один вхід зі STDIN, а також використаний названий канал як інший:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

Зауважте, що за допомогою команди tee можна подати лише один вихід на кілька входів :

ls *.txt | tee /dev/tty txtlist.txt 

Вищевказана команда відображає висновок ls * .txt до терміналу і виводить його в текстовий файл txtlist.txt.

Але із заміною процесу ви можете використовувати teeдля подачі одних і тих же даних у кілька конвеєрів:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar

5
навіть без bash, ви можете використовувати тимчасові mkfifo a; cmd >a& cmd2|diff a -; rm a
фіфо

Ви можете використовувати звичайну трубу для одного з аргументів: pipeline1 | diff -u - <(pipeline2). Тоді вихід буде чіткіше нагадувати, який саме вхід був, показуючи -- /dev/stdinпорівняно ++ /dev/fd/67чи щось, замість двох пронумерованих fds.
Пітер Кордес

Заміна процесу ( foo <( pipe )) не змінює файлову систему. Труба анонімна ; він не має імені у файловій системі . Оболонка використовує pipeсистемний виклик для його створення, не mkfifo. Використовуйте strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'для відстеження викликів файлів і файлових дескрипторів, якщо ви хочете бачити самі. В Linux /dev/fd/63є частиною /procвіртуальної файлової системи; він автоматично містить записи для кожного дескриптора файлу, і це не копія вмісту. Таким чином, ви не можете назвати це "тимчасовим файлом", якщо не foo 3<bar.txtрахується
Пітер Кордес

@PeterCordes Добрі моменти. Я включив ваш коментар у відповідь для більшої наочності.
VonC

1
@PeterCordes Я залишу вам будь-яку редакцію: саме це робить Stack Overflow цікавим: кожен може "виправити" відповідь.
VonC

127

У bash ви можете використовувати підзаголовки, щоб виконувати командні конвеєри окремо, додаючи конвеєр у дужки. Потім ви можете встановити їх за допомогою <, щоб створити анонімні іменовані труби, які ви можете передати до розл.

Наприклад:

diff <(foo | bar) <(baz | quux)

Анонімними названими трубами керує bash, тому вони створюються та знищуються автоматично (на відміну від тимчасових файлів).


1
Набагато детальніше, ніж моя редакція щодо того ж рішення - анонімної партії -. +1
VonC

4
Це називається заміщенням процесу в Bash.
Франклін Ю

5

Деякі люди, які приїжджають на цю сторінку, можуть шукати різницю за рядком, для якої commабо grep -fслід використовувати її замість.

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

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

Якщо це проблема, ви можете спробувати sd (stream diff), який не потребує сортування (як commце робиться), ані підміна процесу, як у наведених вище прикладах, наказує чи збільшується швидше, ніж grep -f і підтримує нескінченні потоки.

Тестовий приклад, який я пропоную, був би записаний так sd:

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

Але різниця полягає в тому, що це seq 100було б seq 10негайно. Зауважте, що якщо одним із потоків є a tail -f, розміна не може бути виконана із заміною процесу.

Ось блогпост я писав про порівнюють потоки на термінал, який вводить sd.

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