Как получить конкретную строку _и_ первую строку файла?

76

Предполагая простой grep, такой как:

$ 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" .

dotancohen
источник
9
Сложность, требуемая этими ответами, показывает, как философия Unix «делать что-то и делать это хорошо» иногда подводит нас, когда измеряется версией удобства использования: знание всех этих команд достаточно хорошо, чтобы применить их к этой общей проблеме (фильтрация информации процесса и все еще видя метки столбцов) показывает обратную сторону подхода: иногда вещи не очень хорошо сочетаются друг с другом. Поэтому такие инструменты , как ackтак полезны, и почему perlвзлетела в прошлом sed, awkи т.д. в популярности: это важно для части , чтобы подвести итог в единое целое.
иконоборчество
3
конечно, для этого конкретного примера вы можете использовать -Cаргумент to, 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 напечатает все строки, которые остались в буфере. Но мы удалили строки, которые не совпадают из буфера, поэтому будут напечатаны только совпадающие строки.

Подводя итог: мы печатаем 1-ю строку, затем удаляем из строки все строки, которые не соответствуют нашему шаблону. Остальные строки печатаются (так только те строки , которые делают соответствовать шаблону).

Проблема с первой линией

Как уже упоминалось в комментариях, существует проблема с этим подходом. Если указанный шаблон соответствует также первой строке, он будет напечатан дважды (один раз по 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реализации.

Сумасшедший путь

Вот, однако, довольно сумасшедший способ сделать это с помощью grep. Это определенно не оптимально, я публикую это только для целей обучения, но вы можете использовать его, например, если у вас нет другого инструмента в вашей системе:

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

Как это устроено

  1. Сначала мы используем -nопцию, чтобы добавить номера строк перед каждой строкой. Мы хотим пронумеровать все строки, которые нам соответствуют, .*что угодно, даже пустую строку. Как предлагается в комментариях, мы также можем сопоставить '^', результат тот же.

  2. Затем мы используем расширенные регулярные выражения, чтобы мы могли использовать \|специальный символ, который работает как OR. Таким образом, мы сопоставляем, если строка начинается с 1:(первая строка) или содержит наш шаблон (в данном случае его syslog).

Проблема с номерами строк

Теперь проблема в том, что мы получаем эти ужасные номера строк в нашем выводе. Если это проблема, мы можем удалить их cutследующим образом:

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

-dПараметр указывает разделитель, -fуказывает поля (или столбцы), которые мы хотим напечатать. Поэтому мы хотим разрезать каждую строку на каждом :символе и печатать только 2-й и все последующие столбцы. Это эффективно удаляет первый столбец с его разделителем, и это именно то, что нам нужно.

Кшиштоф Адамски
источник
4
Нумерация строк может быть выполнена с cat -nтаким же успехом и выглядит более понятной, чем с использованием grep для этого.
Alfe
1
nlне считает пустые строки (но печатает их без номера строки), cat -nформатирует нумерацию с предшествующими пробелами, grep -n .вообще удаляет пустые строки и добавляет двоеточие. У всех есть свои ... э ... особенности ;-)
Alfe
2
Очень познавательный хорошо написанный ответ. Я пытался заменить «Pretend» (в начале) на «Prepend» для вас, но он хотел больше изменений, и мне не хотелось менять случайную чушь в вашем посте, так что вы можете исправить это.
Билл К
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
Мне нужно научить меня немного awk. очень хорошо.
user606723
30
ps aux | { read line;echo "$line";grep someApp;}

РЕДАКТИРОВАТЬ: после комментариев

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
Это идея, изложенная непосредственно в bash. Я хотел бы дать больше одного пальца для этого. Я просто использовал бы, { IFS='' read line; ... }если заголовок начинается с пробелов.
Alfe
Это точно атакует проблему напрямую. Приятно!
dotancohen
3
Я бы просто использовал head -1вместо комбо чтения / эха.
Чепнер
1
Ну, это работает с head -n1моим Bash. Это, вероятно, может зависеть от реализации. Моя голова не читает весь ввод в этом случае, только первую строку, оставляя их в буфере ввода.
Кшиштоф Адамски,
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и т. Д.), А заголовок останется сверху.

antak
источник
5

Вы также можете использовать teeи head:

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

Однако обратите внимание, что до тех пор, пока teeон не может игнорировать SIGPIPEсигналы (см., Например, обсуждение здесь ), этот подход требует обходного пути, чтобы быть надежным. Обходной путь должен игнорировать сигналы SIGPIPE, например, это может быть сделано в bash как оболочки:

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 за это.
Alfe
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
emcconville
источник
2
Мне не нравится это решение, прежде всего потому, что ситуация может измениться между первым и вторым ps auxвызовом ... И если вы просто хотите эту статическую первую строку, почему бы не повторить его вручную?
Шадур
1
Изменения между двумя вызовами не должны беспокоить в этой ситуации. Первый будет обеспечивать только заголовок, который всегда будет соответствовать выходу второго.
Alfe
2
Я не понимаю, почему за это проголосовали, это, безусловно, приемлемый вариант. Upvoting.
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
Mikel
источник
Спасибо, некоторые из этих ответов обсуждают общий случай этого вопроса. Отлично!
Dotancohen
3

В основном благодаря Janis Papanagnou в comp.unix.shell, я использую следующую функцию:

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

Это имеет ряд преимуществ:

  • Работает с bash, zsh и, вероятно, ksh
  • Это замена для grep, поэтому вы можете продолжать использовать любые флаги, которые вам нужны: -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
bdesham
источник
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чтобы rперевести выходные данные ps auxв буфер, а затем удалить несовпадающие строки в 2,$диапазоне и вывести результат:

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

И так как sedкоманды в принятом ответе выводят также совпадающую строку, с sedподдержкой, которая поддерживает, -f-и оболочкой, которая поддерживает замену процесса, я бы запустил:

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

который в значительной степени делает то же самое, что и предыдущие edкоманды.

оборота дон_криссти
источник
1

Perl путь:

ps aux | perl -ne 'print if /pattern/ || $.==1'

Гораздо проще читать, чем sedбыстрее, без риска выбрать нежелательные строки.

emazep
источник
Perl?!?
dotancohen
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

Если вы хотите получить и другие строки, просто добавьте их номера в массив.

Dagelf
источник
1
Спасибо. Согласно ОП, я знаю часть текста в строке, но не номер строки.
dotancohen
Это появляется как ответ на Google при поиске этого варианта использования, тесно связанного с OP, поэтому стоит отметить здесь.
Дагельф
1
Если это так, то я настоятельно рекомендую вам начать новый вопрос и ответить на него этим ответом. На SE вы прекрасно отвечаете на свои вопросы, особенно в ситуации, которую вы упоминаете. Идите вперед и ссылку на ваш новый вопрос в комментарии к ОП.
dotancohen
Есть такие вопросы, но в настоящее время они не появляются в Google.
Дагельф
Дагельф, суть в том - твой ответ здесь не отвечает на вопрос. @dotancohen прав - если в Google это появляется как ответ на вопрос об этом случае использования, тесно связанном с ОП, задайте отдельный вопрос - подробно описав этот тесно связанный вариант использования - и ответьте на него.
don_crissti