Як зібрати певний рядок _і_ перший рядок файлу?


76

Припускаючи простий греп, такий як:

$ psa aux | grep someApp
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

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

$ psa aux | someMagic someApp
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

Звичайно, я можу додати регулярний вираз до grep спеціально для ps:

$ ps aux | grep -E "COMMAND|someApp"

Однак я вважаю за краще більш загальне рішення, оскільки є й інші випадки, коли я також хотів би мати перший рядок.

Схоже, це було б корисним випадком для дескриптора файлу "stdmeta" .


9
Складність, що вимагається цими відповідями, показує, як філософія Unix "робити одну справу і робити це добре" іноді не вдається нам, коли вимірюється ярликом зручності використання: знаючи всі ці команди досить добре, щоб застосувати їх до цієї поширеної проблеми (фільтруючи інформацію про процес і все ще бачити мітки стовпців) показує зворотний бік підходу: іноді речі не дуже добре поєднуються. Ось чому такі інструменти, як ackнастільки корисні, і чому популярність perlминулого sedі awkт. Ін. Популярна: деталі важливо скласти в цілісне ціле.
іконоборство

3
Звичайно, для цього конкретного прикладу ви можете використовувати -Cаргумент, psі вам не потрібно буде передавати його в grep. наприклад, ps u -C someAppабо навітьps u -C app1 -C app2 -C app3
cas

1
@iconoclast: звичайно, рішення Unixy було б інструментом, який може мультиплексувати кілька ліній, кожен з яких відфільтрувати через різний набір фільтрів. ps aux | { head -1; grep foo; }Ніби узагальнена версія згадуваної нижче @Nahuel Fouilleul (його, мабуть, єдине рішення, яке я зможу пригадати на місці, якщо потрібно)
Lie Ryan

@iconoclast: Відсутність досвіду та знань про інструменти, те, що інструменти справді добре справляються, завжди буде видаватися марним. Добре знати команду - це не те, де на дворі палиця зручності користування, це на дворі палиця читати прекрасний посібник і практикувати. Ці інструменти існують десятиліттями. Вони працюють і гарно поєднуються разом (і чисто).
Ярослав Рахматуллін

@ ЯрославРахматуллін: Я думаю, ви, можливо, повністю зрозуміли те, що я сказав. (Можливо, тому, що англійська мова не є вашою першою мовою?) "Корисність" пов'язана з UX ("користувацьким досвідом"), а не корисністю (або "корисністю"). Вказуючи, що ця проста операція є такою складною, це шкодить юзабіліті, це не те саме, що говорити, що інструменти марні. Цілком очевидно, що вони не марні. Ніхто з розуму не сказав, що вони марні.
іконоборство

Відповіді:


67

Хороший спосіб

Зазвичай ви не можете зробити це з grep, але ви можете використовувати інші інструменти. Про AWK вже згадувалося, але ви також можете користуватися sedтаким чином:

sed -e '1p' -e '/youpattern/!d'

Як це працює:

  1. Утиліта Sed працює на кожному рядку індивідуально, виконуючи вказані команди на кожному з них. Ви можете мати кілька команд, вказавши кілька -eваріантів. Ми можемо додати кожній команді параметр діапазону, який визначає, чи слід застосовувати цю команду до певного рядка чи ні.

  2. "1p" - перша команда. Він використовує pкоманду, яка зазвичай друкує всі рядки. Але ми додаємо його до числового значення, яке визначає діапазон, до якого він повинен застосовуватися. Тут ми використовуємо, 1що означає перший рядок. Якщо ви хочете надрукувати більше рядків, ви можете використовувати x,ypтам, де xдрукується перший рядок, y- це останній рядок для друку. Наприклад, щоб надрукувати перші 3 рядки, ви б використовували1,3p

  3. Наступна команда, dяка зазвичай видаляє всі рядки з буфера. Перед цією командою ми ставимо yourpatternміж двома /символами. Це інший спосіб (спочатку було вказати, у яких рядках, як ми це робили з pкомандою) адресних рядків, над якими повинна працювати команда. Це означає, що команда працюватиме лише для рядків, які відповідають yourpattern. За винятком того, що ми використовуємо !символ перед dкомандою, яка інвертує його логіку. Тож тепер він видалить усі рядки, які не відповідають заданому шаблону.

  4. Наприкінці sed буде надрукувати всі рядки, які залишилися в буфері. Але ми видалили рядки, які не збігаються з буфера, тому будуть надруковані лише відповідні рядки.

Підводячи підсумок: ми друкуємо перший рядок, потім видаляємо всі рядки, які не відповідають нашому шаблону, із введення. Решта рядків друкуються (тому лише рядки, які відповідають шаблону).

Проблема першого рядка

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

  1. Додавання 1dкоманди після 1p. Як я вже згадував, dкоманда видаляє рядки з буфера, і ми вказуємо його діапазон за номером 1, а це означає, що вона видалить лише 1-й рядок. Отже команда була бsed -e '1p' -e '1d' -e '/youpattern/!d'

  2. Використовуючи 1bкоманду замість 1p. Це хитрість. bкоманда дозволяє переходити до іншої команди, визначеної міткою (таким чином деякі команди можна опустити). Але якщо ця мітка не вказана (як у нашому прикладі), вона просто переходить до кінця команд, ігноруючи решту команд для нашого рядка. Тож у нашому випадку остання dкоманда не видалить цей рядок із буфера.

Повний приклад:

ps aux | sed -e '1b' -e '/syslog/!d'

Використання крапки з комою

Деякі sedреалізації можуть заощадити певний текст, використовуючи крапку з комою для розділення команд замість декількох -eваріантів. Тож якщо вам не байдуже бути портативними, команда була б ps aux | sed '1b;/syslog/!d'. Він працює принаймні в GNU sedта busyboxреалізаціях.

Божевільний шлях

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

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog'

Як це працює

  1. Спочатку ми використовуємо -nопцію для додавання номерів рядків перед кожним рядком. Ми хочемо нумерувати всі рядки, які ми співставляємо .*- що завгодно, навіть порожній рядок. Як запропоновано в коментарях, ми також можемо відповідати "^", результат такий же.

  2. Тоді ми використовуємо розширені регулярні вирази, щоб ми могли використовувати \|спеціальний символ, який працює як АБО. Тож ми співставляємось, якщо рядок починається з 1:(перший рядок) або містить наш шаблон (у даному випадку його syslog).

Проблема чисел рядків

Тепер проблема полягає в тому, що ми отримуємо ці потворні номери рядків у нашому виході. Якщо це проблема, ми можемо їх усунути cut, як-от так:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog' | cut -d ':' -f2-

-dопція вказує роздільник, -fвказує поля (або стовпці), які ми хочемо надрукувати. Отже, ми хочемо вирізати кожен рядок на кожному :символі та надрукувати лише 2-й та всі наступні стовпці. Це ефективно видаляє перший стовпчик з його роздільником, і саме це нам і потрібно.


4
Нумерація рядків може бути виконана і за допомогою cat -n, і вона буде виглядати чіткіше, як з грейпом, зловживаним для цього.
Альфе

1
nlне рахує порожніх рядків (але друкує їх без номера рядка), cat -nформатує нумерацію з попередніми пробілами, grep -n .зовсім знімає порожні рядки та додає двокрапку. Усі мають свої ... е ... функції ;-)
Alfe

2
Дуже виховна добре написана відповідь. Я спробував замінити "Прикинутись" (біля початку) на "Попередження" для вас, але я хотів більше змін, і мені не здалося, що я міняю випадкові лайно у своєму дописі, тож ви можете виправити це.
Білл К

2
ps aux | sed '1p;/pattern/!d'надрукує перший рядок двічі, якщо він відповідає шаблону . Найкраще використовував bкоманду: ps aux | sed -e 1b -e '/pattern/!d'. cat -nне є POSIX. grep -n '^'буде нумерувати кожен рядок (не проблема для виводу PS, у якого немає порожніх рядків). nl -ba -d $'\n'нумерує кожен рядок.
Стефан Шазелас

2
Зауважте, що 1b;...це не портативно, ні POSIX, після "b" не може бути жодної іншої команди, тому вам потрібен вираз нового рядка або інший вираз -e.
Стефан Шазелас

58

Як ви ставитесь до використання awkзамість grep?

chopper:~> ps aux | awk 'NR == 1 || /syslogd/'
USER              PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root               19   0.0  0.0  2518684   1160   ??  Ss   26Aug12   1:00.22 /usr/sbin/syslogd
mrb               574   0.0  0.0  2432852    696 s006  R+    8:04am   0:00.00 awk NR == 1 || /syslogd/
  • NR == 1: Кількість запису == 1; тобто. перший рядок
  • ||: або:
  • /syslogd/: Шаблон для пошуку

Можливо, варто також переглянути pgrep, хоча це більше стосується сценаріїв, а не виводу, орієнтованого на користувача. grepОднак це не дозволяє самій команді з'являтися у висновку.

chopper:~> pgrep -l syslogd
19 syslogd

Дуже приємно, дякую. Це також чудово написано для подальшого розширення.
dotancohen

Мені потрібно навчитися мені якийсь див. дуже хороша.
user606723

30
ps aux | { read line;echo "$line";grep someApp;}

EDIT: після коментарів

ps aux | { head -1;grep someApp;}

Я хоч head -1би прочитав усі дані, але після тестування він також працює.

{ head -1;grep ok;} <<END
this is a test
this line should be ok
not this one
END

вихід є

this is a test
this line should be ok

2
Ось ідея прописана безпосередньо в баш. Я хотів би зробити кілька пальців для цього. Я просто, можливо, використовую, { IFS='' read line; ... }якщо заголовок починається з пробілів.
Альфе

Це точно атакує проблему безпосередньо. Приємно!
dotancohen

3
Я б просто використав head -1замість комбінації read / echo.
чепнер

1
Ну, це працює з head -n1моїм баш. Можливо, це може бути специфічним для реалізації. У цьому випадку моя голова не читає цілі дані, лише перший рядок, залишаючи їх у вхідному буфері.
Кшиштоф Адамський

2
head -n1коротше, але, здається, навіть специфіка POSIX мовчить про те, яку частину його входу дозволено читати, тому, можливо, вона read line; echo $lineє більш портативною.
чепнер

14

Підтримка внутрішнього фільтра Ps

Припустимо, ви шукаєте процес bash:

ps -C bash -f

Буде перелічено весь процес, який назвав bash.


Дякую, що приємно знати. Однак, серед іншого, він не знайде сценарії, розпочаті з python.
dotancohen

6

Я схильний надсилати заголовок до stderr :

ps | (IFS= read -r HEADER; echo "$HEADER" >&2; cat) | grep ps

Зазвичай цього достатньо для цілей людського читання. наприклад:

  PID TTY          TIME CMD
 4738 pts/0    00:00:00 ps

Закріплена частина може перейти до власного сценарію для загального використання.

Є додаткова зручність у тому, що вихід може бути надалі переданий (і sortт. Д.), А заголовок залишатиметься зверху.


5

Ви також можете використовувати teeта head:

ps aux | tee >(head -n1) | grep syslog

Однак зауважте, що поки не в teeзмозі ігнорувати SIGPIPEсигнали (див. Наприклад, обговорення тут ), цей підхід потребує надійного вирішення. Вирішення проблеми полягає в тому, щоб ігнорувати сигнали SIGPIPE, це, наприклад, можна зробити так, як у боші, як оболонки:

trap '' PIPE    # ignore SIGPIPE
ps aux | tee >(head -n1) 2> /dev/null | grep syslog
trap - PIPE     # restore SIGPIPE handling

Також зауважте, що порядок виводу не гарантується .


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

1
Я не бачив цього, але один з способів підвищення надійності є вставити невелику затримку в трубопроводі до grep: | { sleep .5; cat }.
Тор

2
Додавання режиму сну, щоб уникнути проблем з одночасністю, - це завжди хакер. Хоча це може спрацювати, це крок до темної сторони. -1 для цього.
Альфе

1
У мене виникли кілька інших дивних питань, намагаючись відповісти на цю відповідь, я поставив питання, щоб перевірити
Rqomey

Це цікаве використання трійника, але я вважаю його ненадійним і часто друкує лише вихідний рядок, але не рядок заголовка.
dotancohen

4

Можливо, дві psкоманди були б найпростішими.

$ ps aux | head -1 && ps aux | grep someApp
USER             PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
100         3304   0.0  0.2  2466308   6476   ??  Ss    2Sep12   0:01.75 /usr/bin/someApp

2
Мені це рішення не подобається, перш за все тому, що ситуація може змінитися між першим та другим ps auxдзвінками ... І якщо ви просто хочете цього статичного першого рядка, чому б не повторити його вручну?
Шадур

1
Зміни між двома дзвінками не турбуватимуться в цій ситуації. Перший забезпечить лише заголовок, який завжди відповідатиме висновку другого.
Альфе

2
Я не бачу, чому це було знято, це, безумовно, є життєздатним варіантом. Оголошення.
dotancohen

4

Ви можете використовувати pidstat з:

pidstat -C someApp
or
pidstat -p <PID>

Приклад:

# pidstat -C java
Linux 3.0.26-0.7-default (hostname)    09/12/12        _x86_64_

13:41:21          PID    %usr %system  %guest    %CPU   CPU  Command
13:41:21         3671    0.07    0.02    0.00    0.09     1  java

Детальна інформація: http://linux.die.net/man/1/pidstat


Дякую, що приємно знати. Однак, серед іншого, він не знайде сценарії, розпочаті з python.
dotancohen

4

Помістіть наступне у свій .bashrc файл або скопіюйте / вставте в оболонку спочатку для тестування.

function psls { 
ps aux|head -1 && ps aux|grep "$1"|grep -v grep;
}

Використання: psls [grep pattern]

$ psls someApp
USER             PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root              21   0.0  0.0  2467312   1116   ??  Ss   Tue07PM   0:00.17 /sbin/someApp

Обов’язково вкажіть свій .bashrc (або .bash_profile, якщо ви його замість нього помістите):

source ~/.bashrc

Функція навіть автоматично завершиться в командному рядку оболонки. Як ви заявили в іншій відповіді, ви можете передавати перший рядок у файл, щоб зберегти один дзвінок на ps.


1
Приємно, я вже багато років використовую таку функцію. Я називаю свою версіюpsl , яка тільки дзвонити psі grepодин раз (і не потрібно head).
Адам Кац

3

сортувати, але тримати рядок заголовка вгорі

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

І використовувати це так

$ ps aux | body grep someApp
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

Дякую, деякі з цих відповідей обговорюють загальний випадок цього питання. Ідеально!
dotancohen

3

В основному завдяки Джаніс Папананоу у comp.unix.shell, я використовую таку функцію:

function grep1 {
    IFS= read -r header && printf "%s\n" "$header"; grep "$@"
}

Це має ряд переваг:

  • Працює з bash, zsh та, ймовірно, ksh
  • Це заміна, що випадає, на греп, тому ви можете продовжувати використовувати будь-які прапори: -iдля відповідності не залежно від регістру, -Eдля розширених реджексів тощо.
  • Завжди видає той же код виходу, що і grep, на випадок, якщо ви хочете програмно визначити, чи збігаються будь-які рядки
  • Нічого не друкується, якщо введення було порожнім

Приклад використання:

$ ps -rcA | grep1 databases
  PID TTY           TIME CMD

$ ps -rcA | grep1 -i databases
  PID TTY           TIME CMD
62891 ??         0:00.33 com.apple.WebKit.Databases

2

Ще один спосіб gnu ed:

ed -s '!ps aux' <<< $'2,$v/PATTERN/d\n,p\nq\n'

або, якщо оболонка підтримує процес заміщення:

printf '%s\n' '2,$v/PATTERN/d' ,p q | ed -s <(ps aux)

це:

2,$v/PATTERN/d  - remove all lines not matching pattern (ignore the header)
,p              - print the remaining lines
q               - quit

Більш портативний, без gnu '!' або оболонки заміни - використовуючи тільки edвбудовані в системі, rщоб rEAD виведення ps auxв буфер , а потім видалити незбіжні лінії в 2,$діапазоні і роздрукувати результат:

printf '%s\n' 'r !ps aux' '2,$v/PATTERN/d' ,p q | ed -s

А оскільки sedкоманди у прийнятому відповіді виводять також рядок, що відповідає самій собі, з a, sedщо підтримує, -f-і оболонкою, яка підтримує процес заміщення, я би запустив:

printf '%s\n' '2,${' '/PATTERN/!d' '}' | sed -f - <(ps aux)

що в значній мірі робить те саме, що і попередні edкоманди.



0

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

$ ps -f -p $(pgrep bash)
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
nasha     2810  2771  0  2014 pts/6    Ss+    0:00 bash
...

pgrep bash | xargs ps -fpотримає той самий результат, але без передплати. Якщо потрібне інше форматування:

$ pgrep bash | xargs ps fo uid,pid,stime,cmd -p
  UID   PID STIME CMD
    0  3599  2014 -bash
 1000  3286  2014 /bin/bash
 ...

-2

Якщо ви знаєте точні номери рядків, з Perl це легко! Якщо ви хочете отримати рядки 1 і 5 з файлу, скажіть / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,5]){print}}' < /etc/passwd

Якщо ви хочете отримати й інші рядки, просто додайте їхні масиви до масиву.


1
Дякую. Згідно з ОП, я знаю частину тексту в рядку, але не номер рядка.
dotancohen

Це з’являється як відповідь у Google, коли шукаємо цей випадок використання, тісно пов’язаний з ОП, тому тут варто зазначити
Дагельф

1
Якщо це так, то я настійно пропоную вам почати нове запитання і відповісти на нього цією відповіддю. Цілком чудово відповідати на власні запитання щодо SE, особливо в ситуації, яку ви згадуєте. Вперед та посилання на ваше нове запитання у коментарі до ОП.
dotancohen

Є такі питання, але наразі вони не з’являються в Google.
Дагельф

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