Як я можу прокласти трубу більш жорстко, а не прокладати?


981

У мене є програма, яка записує інформацію в stdoutі stderr, і мені потрібно grepпроаналізувати те, що стає більш складним , ігноруючи stdout .

Я, звичайно, можу це зробити в 2 етапи:

command > /dev/null 2> temp.file
grep 'something' temp.file

але я вважаю за краще мати можливість це робити без тимчасових файлів. Чи є якісь хитромудрі трубопроводи?


Подібне запитання, але зберігаючи stdout: unix.stackexchange.com/questions/3514/…
joeytwiddle

Це питання було для Баша, але варто згадати цю пов’язану статтю для оболонки Борна / Алквіста.
Stephen Niedzielski

10
Я що - щось на зразок цього очікував: command 2| othercommand. Bash настільки досконалий, що розвиток закінчився в 1982 році, тому я ніколи цього не побачимо в баші.
Рольф

Відповіді:


1189

Перший перенаправлення stderr на stdout - труба; потім перенаправляємо stdout на /dev/null(не змінюючи, куди йде stderr):

command 2>&1 >/dev/null | grep 'something'

Детальніше про перенаправлення вводу / виводу у всій його різноманітності див. У розділі " Перенаправлення" у довідковому посібнику Bash.

Зауважте, що послідовність переадресацій вводу / виводу інтерпретується зліва направо, але труби встановлюються перед інтерпретацією переадресацій вводу / виводу. Дескриптори файлів, такі як 1 і 2, - це посилання на відкриті описи файлів. Операція 2>&1змушує дескриптор файлу 2 aka stderr посилатися на той самий опис відкритого файлу, на який в даний час посилається дескриптор файлу 1 aka stdout (див. dup2()І open()). Потім операція >/dev/nullзмінює дескриптор 1 файлу таким чином, що він посилається на відкритий опис файлу для /dev/null, але це не змінює факту, що дескриптор 2 файлу посилається на відкритий опис файлу, на який дескриптор 1 файлу спочатку вказував, а саме - на трубу.


44
я просто наткнувся / dev / stdout / dev / stderr / dev / stdin днями, і мені було цікаво, чи це хороші способи зробити те саме? Я завжди думав, що 2> і 1 трохи заплутані. Тож на кшталт: command 2> /dev/stdout 1> /dev/null | grep 'something'
Майк Ліонс

17
Ви можете використовувати /dev/stdoutet al, або використовувати /dev/fd/N. Вони будуть незначно менш ефективними, якщо оболонка не розглядає їх як особливі випадки; чисто числове позначення не передбачає доступ до файлів по імені, але використання пристроїв означає пошук імені файлу. Чи можете ви міряти, що це дискусійно. Мені подобається лаконічність числових позначень - але я використовую її так довго (більше чверті століття; ой!), Що я не кваліфікований, щоб судити про її достоїнства в сучасному світі.
Джонатан Леффлер

23
@ Джонатан Леффлер: Я маю невелику проблему з вашим простим поясненням тексту "Перенаправити stderr на stdout, а потім stdout на / dev / null" - Оскільки треба читати ланцюжки перенаправлення справа наліво (а не зліва направо), ми слід також адаптувати наше пояснення до цього тексту до цього: "Перенаправляти stdout на / dev / null, а потім stderr до місця, яким раніше був stdout" .
Курт Пфайфле

116
@KurtPfeifle: au contraire! Треба зчитувати ланцюги перенаправлення зліва направо, оскільки саме так оболонка обробляє їх. Перша операція - це 2>&1, що означає "підключити stderr до дескриптора файлу, до якого зараз йде stdout ". Друга операція - "зміни stdout, так що вона переходить до /dev/null", залишаючи stderr перехід до початкової stdout, труби. Оболонка розбиває речі на символі труби спочатку, тож перенаправлення труби відбувається перед перенаправленнями 2>&1або >/dev/nullперенаправленнями, але це все; інші операції - зліва направо. (Праворуч ліворуч не працюватиме.)
Джонатан Леффлер

14
Що мене справді дивує з цього приводу, це те, що він працює і в Windows (після перейменування /dev/nullна еквівалент Windows nul).
Майкл Берр

364

Або для заміни результатів зі стандартної помилки та стандартного виводу на, використовуйте:

command 3>&1 1>&2 2>&3

Це створює новий дескриптор файлу (3) і призначає його тому ж самому місцю, що і 1 (стандартний вихід), потім призначає fd 1 (стандартний вихід) тому самому місці, що і fd 2 (стандартна помилка) і, нарешті, призначає fd 2 (стандартна помилка ) до того самого місця, що і fd 3 (стандартний вихід).

Стандартна помилка тепер доступна як стандартний вихід, а старий стандартний вихід зберігається в стандартній помилці. Це може бути надмірним, але, сподіваємось, це дає більше деталей щодо дескрипторів файлів Bash (для кожного процесу доступно дев'ять).


100
Остаточним підключенням стане 3>&-закриття запасного дескриптора, який ви створили з stdout
Джонатан Леффлер

1
Чи можемо ми створити дескриптор файлів, який має stderrта інший, який має комбінацію stderrта stdout? Іншими словами, можна stderrпереходити до двох різних файлів одночасно?
Стюарт

Далі все ще друкуються помилки для stdout. Що я пропускаю? ls -l not_a_file 3> & 1 1> & 2 2> & 3> error.txt
user48956

1
@ JonasDahlbæk: виправлення - це насамперед питання охайності. У справді затаємничих ситуаціях це може змінити різницю між процесом виявлення і не виявленням EOF, але це вимагає дуже особливих обставин.
Джонатан Леффлер

1
Увага : це передбачає, що FD 3 ще не використовується, не закриває його і не скасовує заміну дескрипторів файлів 1 і 2, тому ви не можете перейти до іншої команди. Дивіться цю відповідь, щоб отримати докладнішу інформацію. Щоб отримати більш чистий синтаксис для {ba, z} sh, дивіться цю відповідь .
Том Хейл

218

У Bash ви також можете перенаправити на підзарядку, використовуючи підстановку процесу :

command > >(stdlog pipe)  2> >(stderr pipe)

Для конкретного випадку:

command 2> >(grep 'something') >/dev/null

1
Дуже добре працює для виведення на екран. Чи маєте ви якесь уявлення, чому незахищений вміст з’являється знову, якщо я перенаправляю греп-вихід у файл? Після command 2> >(grep 'something' > grep.log)grep.log міститься такий же вихід, що і ungrepped.log відcommand 2> ungrepped.log
Тим

9
Використовуйте 2> >(stderr pipe >&2). Інакше вихід "труби більш жорсткого" пройде через "трубу stdlog".
припинення

так !, 2> >(...)працює, я спробував, 2>&1 > >(...)але не вдалося
datdinhquoc

Ось невеликий приклад, який може допомогти мені наступного разу, коли я шукаю, як це зробити. Розглянемо наступне ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) У цьому випадку я хотів також побачити, що з'являється як помилки на моїй консолі. Але STDOUT збирався у вихідний файл. Отже, всередині підрозділу вам потрібно перенаправити STDOUT назад до STDERR всередині дужок. Поки це працює, вихід STDOUT з teeкоманди завершується в кінці out-content.txtфайлу. Мені це здається непостійним.
буде

@datdinhquoc Я це зробив якось так2>&1 1> >(dest pipe)
Аліреза Мохамаді

195

Поєднуючи найкращі з цих відповідей, якщо ви робите:

command 2> >(grep -v something 1>&2)

... тоді вся stdout зберігається як stdout і весь stderr зберігається як stderr, але ви не побачите жодних рядків у stderr, що містять рядок "щось".

Це має унікальну перевагу: не відміняти та не відкидати stdout та stderr, а також не змішувати їх разом, а також не використовувати жодних тимчасових файлів.


Чи не command 2> >(grep -v something)(без 1>&2) те саме?
Francesc Rosas

11
Ні, без цього відфільтрований stderr закінчується перенаправленням до stdout.
Пінько

1
Це те, що мені було потрібно - файл виходів "файл змінювався, коли ми його читали" для каталогу завжди, тому просто хочуть відфільтрувати один рядок, але побачити, чи виникають інші помилки. Так tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)має працювати.
здивований

неправильно говорити "починаючи з рядка". Немає нічого про представлений синтаксис grep, що дозволить виключити лише рядки, що починаються з даного рядка. Рядки будуть виключені, якщо вони містять заданий рядок де-небудь в них.
Майк Накіс

@MikeNakis спасибі - виправлено! (Це залишилось від моєї оригінальної
чергової

102

Набагато легше візуалізувати речі, якщо подумати про те, що насправді відбувається з "переадресаціями" та "трубами". Перенаправлення та труби в bash роблять одне: змінюють, де дескриптори файлів процесу 0, 1 і 2 вказують на (див. / Proc / [pid] / fd / *).

Коли труба або "|" Оператор присутній у командному рядку, перше, що має відбутися, це те, що bash створює FIFO і вказує FD 1 лівої команди на цей FIFO і вказує FD 0 правої бічної команди на ту саму fifo.

Далі, оператори переадресації для кожної сторони оцінюються зліва направо , і поточні параметри використовуються, коли відбувається дублювання дескриптора. Це важливо, тому що, оскільки труба була встановлена ​​спочатку, FD1 (ліва сторона) та FD0 (права сторона) вже змінені від того, що вони могли бути, і будь-яке дублювання цих даних відображатиме цей факт.

Тому, коли ви набираєте щось подібне:

command 2>&1 >/dev/null | grep 'something'

Ось що відбувається, щоб:

  1. створюється труба (фіфо). "команда FD1" вказується на цю трубу. "grep FD0" також вказується на цю трубу
  2. "команда FD2" вказується на те, куди в даний момент вказує "команда FD1" (труба)
  3. "команда FD1" вказується на / dev / null

Отже, весь вихід, який "команда" записує до свого FD 2 (stderr), пробивається до труби і читається "grep" з іншого боку. Весь вихід, який "команда" записує до свого FD 1 (stdout), пробивається до / dev / null.

Якщо замість цього, виконайте такі дії:

command >/dev/null 2>&1 | grep 'something'

Ось що відбувається:

  1. створюється труба і на неї вказуються "команди FD 1" і "grep FD 0"
  2. "команда FD 1" вказується на / dev / null
  3. "команда FD 2" вказується на те, де FD 1 в даний момент вказує (/ dev / null)

Отже, всі stdout та stderr з "команди" переходять до / dev / null. Нічого не йде в трубу, і, таким чином, "греп" закриється, не відображаючи нічого на екрані.

Також зауважте, що переспрямовування (дескриптори файлів) можуть бути лише для читання (<), лише для запису (>) або для читання-запису (<>).

Заключна примітка. Чи програма пише щось на FD1 чи FD2, повністю залежить від програміста. Хороша практика програмування диктує, що повідомлення про помилки повинні переходити до FD 2 і нормального виводу в FD 1, але ви часто зустрінете неохайне програмування, яке змішує два або інакше ігнорує умову.


6
Дійсно приємна відповідь. Моя одна пропозиція - замінити ваше перше використання "fifo" на "fifo (названа труба)". Я деякий час використовував Linux, але якось так і не вдалося дізнатися, що це ще один термін для імені pipe. Це врятувало б мене від того, щоб роздивитись його, але знову ж таки я б не дізнався інших речей, які я бачив, коли знайшов це!
Марк Едінгтон

3
@MarkEdington Зверніть увагу, що FIFO - це лише інший термін для названої труби в контексті труб та IPC . У більш загальному контексті FIFO означає First in, first out, який описує вставку та видалення з структури даних черги.
Ломчільд

5
@Loomchild Звичайно. Суть мого коментаря полягала в тому, що навіть будучи досвідченим розробником, я ніколи не бачив, щоб FIFO використовувався як синонім названого pipe. Іншими словами, я цього не знав: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Уточнення, що у відповіді врятувало б мені час.
Марк Едінгтон

39

Якщо ви використовуєте Bash, тоді використовуйте:

command >/dev/null |& grep "something"

http://www.gnu.org/software/bash/manual/bashref.html#Pipelines


4
Ні, |&дорівнює тому, 2>&1що поєднує stdout і stderr. Питання явно запитувало вихід без stdout.
Profpatsch

3
„Якщо використовується“ | & ”, стандартна помилка команди1 підключається до стандартного вводу command2 через трубу; це скорочення для 2> & 1 | " Знято дослівно з четвертого пункту за вашим посиланням.
Profpatsch

9
@Profpatsch: Відповідь Кена правильна, дивіться, що він перенаправляє stdout до нуля, перш ніж поєднувати stdout і stderr, тому ви отримаєте в трубі тільки stderr, оскільки stdout раніше був відмінений до / dev / null.
Лучано

3
Але я все-таки виявив, що ваша відповідь неправильна, >/dev/null |&розгорніть >/dev/null 2>&1 | і означає, що stodout inode порожній для труби, тому що ніхто (№1 # 2 як прив’язаний до / dev / null inode) не прив'язаний до stdout inode (наприклад ls -R /tmp/* >/dev/null 2>&1 | grep i, дасть порожній, але ls -R /tmp/* 2>&1 >/dev/null | grep iдозволить # 2, який прив'язаний до stdout inode, буде труба).
Фрукти

3
Кен Шарп, я тестував, і ( echo out; echo err >&2 ) >/dev/null |& grep "."не дає результатів (де ми хочемо "помилятися"). man bashкаже, що якщо | & використовується ..., це скорочення для 2> & 1 |. Це неявне перенаправлення стандартної помилки на стандартний висновок виконується після будь-яких перенаправлень, визначених командою. Отже, спочатку ми перенаправляємо FD1 команди на null, потім перенаправляємо FD2 команди на ту, куди вказав FD1, тобто. null, тому FD0 grep не отримує жодного вводу. Дивіться stackoverflow.com/a/18342079/69663 для більш поглибленого пояснення.
зняти молот

11

Для тих, хто хоче назавжди перенаправляти stdout та stderr у файли, перейдіть на stderr, але зберігайте stdout, щоб писати повідомлення в tty:

# save tty-stdout to fd 3
exec 3>&1
# switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# goes to the std.out
echo "my first message" >&1
# goes to the std.err
echo "a error message" >&2
# goes nowhere
echo "this nasty_msg won't appear anywhere" >&2
# goes to the tty
echo "a message on the terminal" >&3

6

Це перенаправить command1 stderr на command2 stdin, при цьому залишить command1 stdout як є.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Взято з ЛДП


2

Я щойно придумав рішення для відправки stdoutв одну команду і stderrв іншу, використовуючи названі труби.

Ось іде.

mkfifo stdout-target
mkfifo stderr-target
cat < stdout-target | command-for-stdout &
cat < stderr-target | command-for-stderr &
main-command 1>stdout-target 2>stderr-target

Мабуть, хороша ідея згодом видалити названі труби.


0

Можна використовувати оболонку rc .

Спочатку встановіть пакет (це менше 1 МБ).

Це приклад того , як ви могли б відмовитися від стандартного виводу і стандартної помилки труби до Grep в rc:

find /proc/ >[1] /dev/null |[2] grep task

Ви можете це зробити, не залишаючи Bash:

rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

Як ви, можливо, помітили, ви можете вказати, який дескриптор файлу ви хочете прокласти, скориставшись дужками після труби.

Стандартні дескриптори файлів нумеруються як такі:

  • 0: Стандартний вхід
  • 1: Стандартний вихід
  • 2: Стандартна помилка

-3

Я намагаюсь слідкувати, щоб він також працював,

command > /dev/null 2>&1 | grep 'something'

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