4

I would like to write a small wrapper around ps so that no matter what the options are or if I grep the result, I could have the first line of the output of ps which tells me what the columns are. For example, the output of ps_wrapper -aux | grep thing would be something like:

USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
name       33925  1.0  0.0   9972  5528 pts/0    Ss   19:34   0:00 /bin/thing

I tried to see if tee could be of use, and tried something like

function ps_wrapper {
    tmpfile=$(mktemp)
    ps $@ > $tmpfile
    head -n 1 $tmpfile >> /dev/stdout
    cat $tmpfile
}

but I soon realized that of course, even if I wrote "manually" to /dev/stdout, it would still be piped to grep.

I want to print the first line of the actual command I am running because the columns printed by ps can change depending on which options are used.

I am using zsh but I don't mind using another tool if there is one more appropriate.

Thank you for reading.

1 Answer 1

5

It's impossible to generically “bypass the pipe”, as in, send output to wherever the output would go if there wasn't a pipe. However, it's possible to bypass the pipe in the sense of redirecting output to another place of your choice, such as the terminal. The special file /dev/tty always represents the current terminal.

function ps_wrapper {
    tmpfile=$(mktemp)
    ps "$@" > $tmpfile
    head -n 1 $tmpfile >/dev/tty
    cat $tmpfile
    rm $tmpfile
}

It's also possible to bypass the pipe if you “save” the original location and pass it down via a file descriptor. But you can't do that from the ps_wrapper function.

function ps_wrapper {
    tmpfile=$(mktemp)
    ps "$@" > $tmpfile
    head -n 1 $tmpfile >&3
    cat $tmpfile
    rm $tmpfile
}
{ ps_wrapper … | grep …; } 3>&1

There are many ways to avoid creating a temporary file. I'll mention a few. Unless otherwise indicated, the solutions in this answer work in bash if you add double quotes around variable and command substitutions.

If you're willing to change the way you call the function, you can call head and grep successively on the right-hand side of the pipe. head will read and print the first line, and leave the rest for its successor to consume.

ps … | { head -n 1; grep …; }

You can a process substitution with either tee or zsh's built-in tee-like feature (multios) to duplicate the output, sending one stream to head -n 1 and another to the command of your choice. However, if you just pipe each stream to a command, there'll be a race between the two, and if head isn't quick enough, the first line may not end up at the top. It'll probably often work because head is pretty quick, but there's no guarantee, for example if grep is in the disk cache but head isn't.

ps | tee >(head -n 1 >/dev/tty) | grep …
ps >(head -n 1 >/dev/tty) | grep …         # zsh only, only if multios is not disabled

You can use awk to display the first line, then pass the rest through.

function ps_wrapper {
  ps "$@" | awk 'NR == 1 {print >"/dev/tty"} NR != 1 {print}'
}

Explanations:

  • awk processes the input line by line.
  • CONDITION { CODE } executes CODE on the lines that fulfill CONDITION.
  • NR is the line number.
  • Redirection in awk works not exactly like the shell, but close enough here.
  • print with no argument prints the input line.

Another approach would be to build the filter functionality into a single command. Pick a string that won't appear in the arguments to ps to use as a separator, for example |.

function pipe_preserving_first_line {
  local lhs
  lhs=()
  while [[ $1 != '|' ]]; do
    lhs+=($1)
    shift
  done
  shift
  "${lhs[@]}" | {
    head -n 1;
    "$@"
  }
}
pipe_preserving_first_line ps u \| grep foo

Instead of using grep, you can use ps's matching facilities such as -C to match a process by its command name.

ps uww -C mycommand

Instead of using grep, you can use pgrep's matching facilities.

ps -p $(pgrep -d, mycommand)
1
  • Thank you very much for the thorough answer. I quickly tested the solution which used /dev/tty and it seems to work exactly like I wanted; I'll take the time to study more closely the other approaches to find the one which suits my needs the most. Thank you again.
    – hforrat
    Jun 16, 2020 at 20:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .