167

Does there exist in Linux bash something similar to the following code in PHP:

list($var1, $var2, $var3) = function_that_returns_a_three_element_array();

i.e. you assign in one sentence a corresponding value to 3 different variables.

Let's say I have the bash function myBashFunction that writes to stdout the string qwert asdfg zxcvb. Is it possible to do something like:

(var1 var2 var3)=(`myBashFunction param1 param2`)

The part at the left of the equal sign is not valid syntax of course. I'm just trying to explain what I'm asking for.

What does work, though, is the following:

array=(`myBashFunction param1 param2`)
echo "${array[0]}" "${array[1]}" "${array[2]}"

But an indexed array is not as descriptive as plain variable names.
However, I could just do:

var1=${array[0]}; var2=${array[1]}; var3=${array[2]}

But those are 3 more statements that I'd prefer to avoid.

I'm just looking for a shortcut syntax. Is it possible?

1
  • "But an indexed array is not as descriptive as plain variable names." right, so best to just avoid this sort of silliness altogether. Commented Sep 10, 2024 at 10:19

8 Answers 8

304

First thing that comes into my mind:

read -r a b c <<<$(echo 1 2 3) ; echo "$a|$b|$c"

output is, unsurprisingly

1|2|3
Sign up to request clarification or add additional context in comments.

10 Comments

Is there anyway to get this to work if the first variable contains a space?
@Michael Using read -d "\n" v1 v2 <<<$(cmd) works perfectly. Thank you!
@LeeNetherton, good point, though I'm not sure if one needs the return status of echo command :-) I think at the time of writing the answer bash supporting this syntax was less common (as in installed by default), though I'm not 100% sure.
@MichaelKrelin-hacker sure, the return status of echo is pointless, but I was using this technique to return multiple values from a script that I did care about the return status. I thought I would share my findings.
For safety you should use: read -r: do not allow backslashes to escape any characters
|
25

I wanted to assign the values to an array. So, extending Michael Krelin's approach, I did:

read a[{1..3}] <<< $(echo 2 4 6); echo "${a[1]}|${a[2]}|${a[3]}"

which yields:

2|4|6 

as expected.

4 Comments

To put the values in an array there is already a simple solution which I mentioned in the question: a=( $(echo 2 4 6) ) ; echo ${a[0]} ${a[1]} ${a[2]}
Yes, I had overlooked that. I would argue, though, that my suggestion is better suited to assigning larger arrays.
@soundray Your solution uses expansion and a herestring, bash being what it is I doubt it will perform well in that scenario (but I didn't check).
For safety you should use: read -r: do not allow backslashes to escape any characters
5

Sometimes you have to do something funky. Let's say you want to read from a command (the date example by SDGuero for example) but you want to avoid multiple forks.

read month day year << DATE_COMMAND
 $(date "+%m %d %Y")
DATE_COMMAND
echo $month $day $year

You could also pipe into the read command, but then you'd have to use the variables within a subshell:

day=n/a; month=n/a; year=n/a
date "+%d %m %Y" | { read day month year ; echo $day $month $year; }
echo $day $month $year

results in...

13 08 2013
n/a n/a n/a

4 Comments

The read command does not happen in a subshell because of the braces, it's because you've got the read command on the right side of the pipe. You need to run the read command in the current shell, which you can do like read day month year <<< `date "+%d %m %Y"`
No -- the read happens but the scope of the variables it reads into falls out of scope when the subshell of the pipeline ends.
My comment was about the reason why the read happens in a subshell, and I realize now I misread what you wrote. I thought you meant that the subshell was created because you used the curly braces around the compound statement. But! The reason you gave that example was to avoid forking, and but won't the subshell fork, too?
The first example requires exactly one fork: so that bash can launch the date command. In the second example, you set up a pipeline between date and your sub-shell. I think bash these days is clever enough not to actually fork into the subshell, but I'm not sure about that. At any rate, it looks like it would :)
3

I think this might help...

In order to break down user inputted dates (mm/dd/yyyy) in my scripts, I store the day, month, and year into an array, and then put the values into separate variables as follows:

DATE_ARRAY=(`echo $2 | sed -e 's/\// /g'`)
MONTH=(`echo ${DATE_ARRAY[0]}`)
DAY=(`echo ${DATE_ARRAY[1]}`)
YEAR=(`echo ${DATE_ARRAY[2]}`)

2 Comments

Why not avoid 4 subshells plus an extra sed process, and just do all that in one line: IFS=/ read -r m d y < <(echo 12/29/2009)
A var=`echo $otherVar` is a bizarre anti-pattern -- and an automatic downvote.
2

If you want to control the delimiting character (i.e. single character), you can use IFS in conjunction with read -r <vars> <<< '<list>':

IFS=',' read -r foo bar baz <<< 'foo,bar,baz'
echo "foo=$foo bar=$bar baz=$baz"
# => foo=foo bar=bar baz=baz

Comments

1

Shell scripts best handle multiple items as lines, because that's what most CLI programs use to delineate i/o (unless they use null bytes). This ensures preservation of output that contains shell meta-characters (*, ?, [, etc.), and characters present in IFS.

myfunction () {
    # output as lines
    printf '%s\n' 'Whatever, mate.' 'The "second" thing' "$(a-command-we-need-the-top-line-from | head -n 1)"
}

# read as an array
readarray -t myarray < <(myfunction)

# read individual variables
{
    read -r var1
    read -r var2
    read -r var3
} < <(myfunction)

# output surrounded by ~ to show boundaries
printf '~%s~\n' "${myarray[@]}" "${#myarray[@]}" "$var1" "$var2" "$var3"

Yields something like this:

~Whatever, mate.~
~The "second" thing~
~[2024-10-10T19:42Z] A thing happened that we logged!~
~3~
~Whatever, mate.~
~The "second" thing~
~[2024-10-10T19:42Z] A thing happened that we logged!~

Comments

0

Chapter 5 of the Bash Cookbook by O'Reilly, discusses (at some length) the reasons for the requirement in a variable assignment that there be no spaces around the '=' sign

MYVAR="something"

The explanation has something to do with distinguishing between the name of a command and a variable (where '=' may be a valid argument).

This all seems a little like justifying after the event, but in any case there is no mention of a method of assigning to a list of variables.

2 Comments

Yes, I know. I just added extra spaces here and there for the sake of readability
Indeed that's a poor motivation: What if ';' is a valid argument? When I write ls ; cd it still calls ls and cd despite the spaces. If I want to list directories called ; and cd I can just type ls ';' cd.
-3

let var1=var2=var3=0
or
var1=var2=var3="Default value"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.