はじめに

ps -ef | grepを行うと、psのヘッダが出てくれない。ヘッダを出す方法は主に2通りで、ps -efにhead -1を付けるか、grepのかわりのコマンドを使うか。

head -1をつける

head -1をつける方法その1

ps -ef | head -n 1と本来書きたかったps -ef | grep;でつなげて書く。

$ ps -ef | head -n 1; ps -ef | grep python;
UID        PID  PPID  C STIME TTY          TIME CMD
user      2374  2365  0 00:16 ?            00:00:00 python /usr/share/ibus/ui/gtk/main.py
user      2382  2365  0 00:16 ?            00:00:00 python /usr/share/ibus-anthy/engine/main.py --ibus
user      3063  2483  0 00:34 pts/1        00:00:00 grep python

head -1をつける方法その2(グループコマンド)

方法その1だとps -efを二度書かなければならない。グループコマンド(group command) とよばれる{ }を利用すれば、ps -efを1度だけ書けばいい。グループコマンドはリストとの間が空白またはシェルのメタ文字で分かれている必要がある。

$ ps -ef | { head -n 1; grep python; }
UID        PID  PPID  C STIME TTY          TIME CMD
user      2374  2365  0 00:16 ?        00:00:00 python /usr/share/ibus/ui/gtk/main.py
user      2382  2365  0 00:16 ?        00:00:00 python /usr/share/ibus-anthy/engine/main.py --ibus

そして、方法その2にはもう一つメリットがある。通常ps -ef | grep XXを行うと、標準出力に自分自身(つまりgrep XX)が表示されてしまう。何度か実行したが、グループコマンドを利用すると、ps -ef | grep の結果に自分自身は表示されなかった。

2018/11/02追記

Why does a pipe to a command group only work for some commands?でこの方法をとることが非推奨であることが分かった。

Apparently ps -e is sending the header line first, then not sending anything, then buffering the rest of the output. head must think the stream is closed after the first line, so it exits, leaving grep to see the rest.

本来であればpsの出力は一番目のheadで処理されてgrepには渡ってこないはず。標準出力を複数のプロセスにパイプで渡したければ、moreutilspeeコマンドを使用しなければいけない。

引用したように、たまたま-eオプションの挙動のおかげでうまく動いていただけである。

2021/04/27追記(readコマンドとの組み合わせ)

awkで列番号ではなく列名でカラムを抜き出すを考えているときに思いついたが、readコマンドとグループコマンドをあわせて使えばヘッダを表示することができる。

$ ps -ef | { read; echo "$REPLY"; grep python; }
UID        PID  PPID  C STIME TTY          TIME CMD
user      2374  2365  0 00:16 ?        00:00:00 python /usr/share/ibus/ui/gtk/main.py
user      2382  2365  0 00:16 ?        00:00:00 python /usr/share/ibus-anthy/engine/main.py --ibus

read 変数あるいはreadを実行すると、1行だけ読み取って変数に格納してくれる(変数を指定しない場合は$REPLYに格納される)。

そのあとにgrepを実行すると、readで読み込まなかった残りをgrepが読み込んで処理する。

grepを使用しない

grepのかわりにps -f -C cmdlistを使う方法

実はps -ef | grepと同等の機能が、grepを使わずにpsのオプションだけで実現できる。

そもそもps -ef | grepを行うとヘッダが表示されない原因はヘッダの文字列がgrepの正規表現に合致しないためなので、grepを使わずにpsだけで実現できれば、ヘッダも表示されたままである。ただしgrepはコマンドの引数もマッチの対象にできるため、grepの方が柔軟ではある。

$ ps -f -C cmdlist

※全て表示を示す-eはつけない。

例:

$ ps -f -C python
UID        PID  PPID  C STIME TTY          TIME CMD
user      2374  2365  0 00:16 ?        00:00:00 python /usr/share/ibus/ui/gtk/main.py
user      2382  2365  0 00:16 ?        00:00:00 python /usr/share/ibus-anthy/engine/main.py --ibus

またps -f -U userlist等も同様の使い方ができるが、psのオプションは条件がorでつながっていくので、-C-Uを同時に指定はできない。柔軟さを求めるならgrepで正規表現等を使うのが一番いい。

grepのかわりにsedかawkを使う

ps -ef | sed -n '1p;/python/p'あるいはps -ef | awk 'NR==1 || /python/'とすると、以下のようにgrepを使用したときと同じ出力が得られる。

UID        PID  PPID  C STIME TTY          TIME CMD
user      2374  2365  0 00:16 ?        00:00:00 python /usr/share/ibus/ui/gtk/main.py
user      2382  2365  0 00:16 ?        00:00:00 python /usr/share/ibus-anthy/engine/main.py --ibus
user      3063  2483  0 00:34 pts/1    00:00:00 sed -n 1p;/zabbix/p
UID        PID  PPID  C STIME TTY          TIME CMD
user      2374  2365  0 00:16 ?        00:00:00 python /usr/share/ibus/ui/gtk/main.py
user      2382  2365  0 00:16 ?        00:00:00 python /usr/share/ibus-anthy/engine/main.py --ibus
user      3063  2483  0 00:34 pts/1    00:00:00 awk NR==1 || /zabbix/

sedでは1行目を表示し、次は正規表現にマッチするものを表示するというロジック。awkでは1行目かあるいは正規表現にマッチするものを表示するというロジック。微妙に細部は異なるものの、どちらもgrepの代用ができる。