10

I am trying to make ffcast screen casting tool bash 4.1 backwards compatible.

and in this ffcast.bash script, there is oneline

shopt -s extglob lastpipe

lastpipe option is only available after bash 4.3, what can I do to emulate its effect?

Shuman
  • 3,914
  • 8
  • 42
  • 65

2 Answers2

10

lastpipe (introduced in bash 4.2, by the way) can only be simulated by not using a pipe. You need to explicitly run the last command of the pipe line in the current shell, and redirect its input from either a process substitution

# foo | bar | baz becomes ...
baz < <(foo | bar)

or a named pipe (which is POSIX-compliant as well)

# foo | bar | baz becomes ...
mkfifo baz_input
foo | bar > baz_input &
baz < baz_input
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    Those aren't equivalent. This code shows a difference. `i=0; f() { echo "$1: $((i++))"; }; shopt -u lastpipe; true | f piped; true | f piped; shopt -s lastpipe; true | f lastpiped; true | f lastpiped; f redirected < <(true); f redirected < <(true)` . Only the redirected input correctly saves the incremented counter, regardless of `lastpipe` status. – Mark Haferkamp Aug 08 '19 at 00:19
  • 3
    That's because you are in an interactive shell with job control active; job control needs to be deactivated (for reasons I've never quite understood) for `lastpipe` to take effect. If you put that code in a script, you'll see `lastpiped` version does increment `i` across calls. (You can also use `set +m` to disable job control in an interactive shell.) – chepner Aug 08 '19 at 01:09
  • Thanks! It works in a script. Mind adding the job control and script vs interactive bit to your answer? – Mark Haferkamp Aug 08 '19 at 02:24
9

Usual behavior without lastpipe and with job control enabled is to run each element of the pipeline in a sub-shell.

echo asd | var=$(cat) ; echo $var

var contains nothing, but user may expect that var will contain asd. That is because last pipeline element sets var in a sub-shell which has no access to current shell's environment.

From man bash:

Each command in a pipeline is executed as a separate process (i.e., in a subshell). See COMMAND EXECUTION ENVIRONMENT for a description of a subshell environment. If the lastpipe option is enabled using the shopt builtin (see the description of shopt below), the last element of a pipeline may be run by the shell process.

I don't know what is may be... Here is better description:

lastpipe
If set, and job control is not active, the shell runs the last command of a pipeline not executed in the background in the current shell environment.

So

set +m  # To disable job control
shopt -s lastpipe
echo asd | var=$(cat)
echo $var

and now var contains asd.

Thanks @chepner.


Earlier I used to write like:

{ while read;do var="$REPLY";done; } < <(command | filter)

in cases when

var=$(command | filter)

is not suitable.

kyb
  • 7,233
  • 5
  • 52
  • 105