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)
/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 '20 at 20:23