1

I have a command that outputs 2 lines,

% ./run
one
two
%

so the output is one\ntwo\n

How can I assign variables first and second to the first and second lines respectively?

I know how I could do both by running the command twice:

% first="$(./run | head -n 1)"
% second="$(./run | tail -n 1)"

I don't want to run ./run twice, I only want to call it once (output might be different each time)

I could just store ./run's output in a variable, then operate on the variable, but can I avoid having this temporary variable?

More and more high-level languages are adding destructuring assignments:

first, second = (./run).split('\n')

Can I accomplish something similar in zsh?

minseong
  • 817

1 Answers1

3

Read each line with a separate call to read.

IFS= read -r first
IFS= read -r second

For example:

$ unset first second
$ printf 'one\ntwo\n' | { IFS= read -r first; IFS= read -r second; }
$ print $first
one
$ print $second
two

The above works only because zsh does not run the right hand side of the pipeline in a separate subshell.

Or, with a process substitution:

$ unset first second
$ { IFS= read -r first; IFS= read -r second; } < <( printf 'one\ntwo\n' )
$ print $first
one
$ print $second
two

See also Understanding "IFS= read -r line"

Kusalananda
  • 333,661
  • How bad is this code? I'm learning zsh: vars=(first second);c=1;for i in $(./cmd); do typeset "${vars[$c]}"="$i"; ((c++)); done. – schrodingerscatcuriosity Feb 08 '21 at 20:04
  • 1
    @schrodigerscatcuriosity I would call it mostly unreadable, and therefore unmaintainable, at least in a professional setting. I can see what it does, but I can immediately say that it's correct. Seeing as we loop over read all the time (while IFS= read -r line; do ...; done), I see no reason to not do an "unrolled loop" of two read calls when we know that we want to read exactly two values. – Kusalananda Feb 08 '21 at 20:19