1

I have a program that outputs several lines. I want to assign each line to a different variable. I have tried to pipe the output to a block:

my_program | {
    read -r foo
    read -r bar
    read -r baz
}

It seems that I can only access those variables inside the block but I don't want to put everything inside the block.

I have also tried to use the -d option with a character that is unlikely to show up in the output as it's argument.

IFS=$'\n' read -r -d$'\a' foo bar baz < <(my_program)

It seems to work except that if there are more than 3 lines, the variable 'baz' get the third line and all the extra lines. It's not really a problem since I know the exact number of lines that my program is going to output.

I wonder if there is a better way to do this.

clpgr
  • 107
  • 7
  • 2
    Any particular reason for not using an array? – oguz ismail Apr 11 '20 at 06:07
  • *there are more than 3 lines, the variable 'third' get the third line and all the extra lines* That's how `read` works, yes. – Shawn Apr 11 '20 at 06:14
  • 1
    Maybe `printf "1\n2\n3\n4\n" | { mapfile -t arr; declare -p arr; }` for example? (just copy and paste to terminal to test) or `mapfile -t arr < <(my_program)` is fine for bash. – David C. Rankin Apr 11 '20 at 06:27
  • @oguzismail Because I want to refer to them in the subsequent program with decriptive names. Maybe I can use an associative array with the elements of an normal array as keys. Something like ```arr=(first second third); ((cnt = 0)); while read line; do map[${arr[cnt]}]=$line; ((cnt++)); done < <(my_program)``` – clpgr Apr 11 '20 at 06:52
  • @Shawn I know that. I'm just saying that it may not be intended. – clpgr Apr 11 '20 at 06:55
  • It's intended behavior. See the documentation. – Shawn Apr 11 '20 at 06:56
  • @Shawn Ah, I didn't get my point clear. Maybe that's because I'm still on my English learning path. I know it's designed that way. I mean it's may not be intended by the programmer. In the first example, three variables get first three lines, while in the second example it's not necessarily so. – clpgr Apr 11 '20 at 07:02

2 Answers2

1

Here is my take on that.

#!/usr/bin/env bash

i=0
n=3

while (( i < n )); do
  read -r first
  read -r second
  read -r third
  (( i++ )) || break
done < <(printf '%s\n' one two three four five six)

printf '%s\n' "$first" "$second" "$third"
  • In your case replace the printf with my_program
  • Also consider using an array like what have suggested in the comments by @ogus ismail.

Using mapfile aka readarray

mapfile -t -n 3 array < <(printf '%s\n' one two three four five six)

The whole array

printf '%s\n' "${array[@]}"

Output

one
two
three

The index starts at zero

printf '%s\n' "${array[0]}" "${array[1]}" "${array[2]}"

Output

one
two
three
Jetchisel
  • 7,493
  • 2
  • 19
  • 18
  • The variable names ```first``` and ```second``` in my example are kinda misleading. In my real script, names are something descriptive rather than something correspond to numbers. I'm considering using associative arrays mentioned in my comment to the question. ```while``` shall do the trick as well. Thanks for that. But I don't get what the point of ```i``` and ```n``` are. Why not just ```while true; do read -r foo; read -r bar; break; done < <(my_program)``` – clpgr Apr 11 '20 at 07:17
  • You can change the variable as you like. It just feels more readable to me to use a range of numbers, start, end using a counter is all. Of course you can do the `true` and `break`. Since that code snippet comes from my other script that simulates the utility `head` and `tail` – Jetchisel Apr 11 '20 at 07:26
  • 1
    I mentioned the variable names to explain why I don't want to use an array. I'm going to mark this as accepted answer to remind me that when I want to do something with pipe I can always do it with ```while``` with the benefit of preserving variables. – clpgr Apr 11 '20 at 07:35
0

Stupid me. I just realized that I can read lines into an array and then assign each element of the array to a descriptive name.

mapfile -t arr < <(my_program)
foo=${arr[0]}
bar=${arr[1]}
baz=${arr[2]}

I have explained why I don't want to use an array in my comments to the question and the accepted answer. Those variables are actually configuration options so I definitely don't want to refer to VIRTUAL_IP with something like ${arr[0]}.

clpgr
  • 107
  • 7