Have you ever wanted to construct a long pipeline with a while read loop or a mapfile at the end of it? It’s so common that it’s practically a shell idiom.

Have you then (re)discovered that all pipeline components are run in separate shell environments?

#!/bin/bash seq 20 | mapfile -t results declare -p results # => bash: declare: results: not found (WTF!)

Then someone told you to use a process substitution to get around it?

#!/bin/bash mapfile -t results < <(seq 20) declare -p results # => declare -a results=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10" [10]="11" [11]="12" [12]="13" [13]="14" [14]="15" [15]="16" [16]="17" [17]="18" [18]="19" [19]="20")

And you go away unsatisfied, because:

mapfile -t results < <(gargantuan | pipeline | that | stretches | into | infinity | and | beyond)

just looks bass-ackward?

That’s what the lastpipe shell option solves. From the bash man page:

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 this works just fine:

#!/bin/bash shopt -s lastpipe seq 20 | mapfile -t results declare -p results # => declare -a results=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10" [10]="11" [11]="12" [12]="13" [13]="14" [14]="15" [15]="16" [16]="17" [17]="18" [18]="19" [19]="20")

There’s a catch: You can’t do this on the command line, because job control (a.k.a. suspend/resume) is active in interactive mode:

$ shopt -s lastpipe $ seq 20 | mapfile -t results $ declare -p results # => bash: declare: results: not found

But in your scripts, you can now write your while read / mapfile pipelines the logical way.

Conversely, if you really want the code in your while read consumer loop to not pollute your main shell environment, you can explicitly turn off lastpipe just in case: