Is it POSIX-compliant to use a shell function in a pipeline?

1

Is it possible to use a shell function in a pipeline?

Say I had a function that acted like grep, called mygrep. Is there any way, using only POSIX shell features, to be able to invoke mygrep like this:

if ps | mygrep foo ; then
   echo "process foo is running"
fi

Or is piping limited to external commands only?

Tim Bird

Posted 2018-09-19T04:56:56.443

Reputation: 113

1What keeps you from trying? – Kamil Maciorowski – 2018-09-19T05:02:26.460

1@KamilMaciorowski: Perhaps the uncertainity in whether it works due to being a standard POSIX feature, or whether it works due to being one of the million ksh/bash/zsh extensions. When OP mentions "using only ...", it's best to assume "Is it supposed to work?" – user1686 – 2018-09-19T07:55:02.343

@grawity A reasonable guess, I get your point. If so, providing results of own tests in few shells would be a nice research effort. – Kamil Maciorowski – 2018-09-19T08:04:31.633

@grawity My answer now addresses this issue. Thank you. – Kamil Maciorowski – 2018-09-19T08:48:53.410

Answers

3

Yes. See this document.

1. Introduction

The Shell and Utilities volume of POSIX.1-2017 describes the commands and utilities offered to application programs by POSIX-conformant systems.

From 2.9.2 Pipelines:

A pipeline is a sequence of one or more commands separated by the control operator '|'.

From 2.9.5 Function Definition Command:

A function is a user-defined name that is used as a simple command [...]

The format of a function definition command is as follows:

fname ( ) compound-command [io-redirect ...]

From 2.9 Shell Commands:

A command is one of the following:

  • Simple command [...]
  • [...]

So | separates commands; a command may be a simple command; function name is used as a simple command. The answer to your question is: yes, this syntax

some_command | some_function

is defined by POSIX.


It's quite easy to try (tested with sh provided by dash in Debian 9):

mygrep() { grep "$@"; }

if ps | mygrep foo ; then
   echo "process foo is running"
fi

if ps | mygrep ps ; then
   echo "process ps is running"
fi

(ps | mygrep foo will detect a process foobar as well, if any. I understand this issue is outside the scope of your question and we can ignore it).

In this example mygrep is trivial. It returns exit status of its last (and only) command. While building functions with more complex logic you may need special builtin return to return desired exit status.

Also note the output from grep is not suppressed. Not only a shell function can be used in pipes, its output can be redirected too. Example:

if ps | mygrep ps >/dev/null ; then
   echo "process ps is running"
fi

Kamil Maciorowski

Posted 2018-09-19T04:56:56.443

Reputation: 38 429

replaced with comment below. SE was acting weird and it looked like my comment didn't get saved. – Tim Bird – 2018-09-20T16:05:12.210

I'm embarrassed that I didn't try this myself. I thought that handling the stdin would be more involved, but in hindsight it seems obvious it just "goes" to the function (if that's the way to put it). That you can redirect function output is also very interesting. Thanks! – Tim Bird – 2018-09-20T16:09:09.317

-2

You have to use oneliner form, or pack your expression to ${}. In that form you wrote as bash script it is not possible.

Yurij

Posted 2018-09-19T04:56:56.443

Reputation: 187

1I don't really understand what you mean. Can you post an example? a working piece of code? As far as I know ${} works with variables and makes things like echo "${variable}_suffix" possible. – Kamil Maciorowski – 2018-09-19T06:28:07.887

just above example mygrep() { grep "$@"; } – Yurij – 2018-09-19T07:25:04.953