Arvandor
Arvandor

Reputation: 191

How to put two values from a single line into two variables in bash?

For example, instead of doing

start=`commandforstdout | awk {'print $2'}`
end=`commandforstdout | awk {'print $5'}`

Is there a way to store

`commandforstdout | awk {'print $2, $5'}`

into two different variables without having to run the command a second time? Just for the sake of efficiency...

Upvotes: 2

Views: 129

Answers (3)

mklement0
mklement0

Reputation: 437448

To complement the helpful existing answers:

The reason a straightforward pipeline solution (commandforstdout | read ...) doesn't work is that all commands in a pipeline are by default run in a subshell, so that the variables that a read command in a pipeline creates will not be visible after the pipeline.

However, on bash 4.2+, turning on shell option lastpipe causes the last pipeline segment to run in the current shell, allowing read to create variables visible to the current shell.

Example:

# bash 4.2+: Turn on shell option that runs the *last* pipeline segment in the
#            *current shell* rather than in a *subshell*.
shopt -s lastpipe

# Read the two words piped via stdin into two variables.
# Since option `lastpipe` is on, the variables are created in the *current* shell.
echo 'START END' | read -r start end  

echo "[$start] [$end]"  # -> '[START] [END]'

Note: lastpipe only works when job control is turned off, which is true by default in non-interactive shells (e.g., in scripts), but not true in interactive shells; to test the example above interactively, you must turn job control (temporarily) off:
set +m; shopt -s lastpipe && echo 'START END' | read -r start end; echo "[$start] [$end]"; set -m

Upvotes: 2

Jens
Jens

Reputation: 72639

The posixly portable way to do this without coprocesses or bashisms:

set -- $(commandforstdout | awk '{print $2, $5}')
start=$1 end=$2

This could be even easier if you tell us the output of commandforstdout. It might be that

set -- $(commandforstdout)
start=$2 end=$5

would even save the expensive fork and pipe. As gniourf_gniourf rightfully noted, pathname expansion may get in the way if the result contains glob characters. Should this be a problem, use set -f before the first set and set +f if you need pathname expansion later on again.

Upvotes: 2

anubhava
anubhava

Reputation: 785068

Use read builtin with process substitution:

read start end < <(commandforstdout | awk {'print $2, $5'})

Or even without awk (thanks to @gniourf_gniourf):

read -r _ start _ _ end _ < <(commandforstdout)

Upvotes: 5

Related Questions