Read command acting erratically

I have been trying to use read in a script with issues so I tried some things on the command line.

$ echo "testing 123" | read x ; echo $x

and

$ echo "testing 123" | read -r x ; echo $x

are only producing any output after being invoked the first time after rebooting the machine. I also got into some state where echo was producing "unbound variable" errors.

Can anyone point me in the right direction?

Mike

PS. This has something to do with subshells but I'm not exactly clear what is going on.

Consider what happens when you do a |

echo a b c | some program

If "some program" were cat, would it change any memory or variables in your current shell? Of course not, it doesn't have access to them -- it's a separate process. All commands run after a pipe, including shell built-ins, have to run in a new process.

So read is run in a brand-new, independent shell, which happily sets the variable x in that shell -- then immediately dies, leaving the original shell unchanged.

Try this:

echo A | ( read X ; echo $X )

Here it will work because the read and the echo are grouped in the same shell.

ksh does pipes in the opposite order so "echo | read" will actually work -- but this isn't something you can count on unless you know you have KSH.

1 Like

Thanks. I had no idea built-ins also did this!

Mike

It has to, to avoid deadlocks. Which runs first, the echo or the read? Will either of them hang, waiting for the other? Make them independent and it doesn't matter.

1 Like

read is a little unpredictable

$ var1=
$ var2=
$ var3=

$ echo "a:b:c" | { IFS=":"; read var1 var2 var3 ; echo "var1 is: ""$var1"; echo "var2 is: ""$var2"; echo "var3 is: ""$var3"; }
var1 is: a
var2 is: b
var3 is: c

$ var1=
$ var2=
$ var3=

$ IFS=":"; read var1 var2 var3 <<<$(echo "a:b:c"); echo "var1 is: ""$var1"; echo "var2 is: ""$var2"; echo "var3 is: ""$var3"
var1 is: a b c
var2 is:
var3 is:

Mike

That is not because of read, but because the right hand side of the pipe is executed in a subshell in bash and some other bourne type shells. It works in ksh though...

--
Note that IFS=":"; read ..... permanently changes IFS to a colon. To use IFS local to the read command (in bash / ksh93 / zsh ) you can use:

IFS=":" read var1 var2 var3 <<< "a:b:c"

After execution of the command the IFS remains unchanged..

1 Like

I just figured it out. You need to create a temporary FIFO which < will treat as a file.

$ IFS=":" read var1 var2 var3 < <(echo "a:b:c"); echo "var1 is: ""$var1"; echo "var2 is: ""$var2"; echo "var3 is: ""$var3"
var1 is: a
var2 is: b
var3 is: c

Passed this way rather than on a pipe, read does not create a new subshell.

Thanks for the temporary IFS tip. Are there other commands you can combine without ; ?

Mike

Figure what out? What, exactly, are you trying to do?

We could show you far less convoluted ways if we knew your intent!

The <<< way works in BASH and only BASH, this should work in any shell:

read A B C <<EOF
string1 string2 string3
EOF

This is called a "here document" and allows you to put variables and text inside it.

This works for any variable. VARIABLE=whatever command temporarily sets a variable for the duration of the line.

I figured out what you meant by "without ;"

Again though, this isn't something special -- this works for any command. It sets and exports a variable, any variable, only for a single line. You can do

HTTP_PROXY="proxy:port" wget ...

if you want wget to use a certain proxy, for example.

I am trying to parse a string like "one:two:three:four" into 4 variables. Should be super simple but I ran into that subshell issue. It needs to be robust so that for "::three:" the first two and 4th variable are null.
I think I have it now.

Is this still convoluted?

IFS=":" read var1 var2 var3 var4 < <(echo "$string")

As an added benefit it is robust against missing delimiters.

Mike

---------- Post updated at 10:58 AM ---------- Previous update was at 10:51 AM ----------

I guess I never knew that variable assignments don't need ; to be on the same line:

$ a=1 b=2; echo $a; echo $b
1
2

That was a gap in my knowledge.

Mike

And where do you get this string? Perhaps it could be parsed in the first place instead of later.

Actually, I didn't know that either.

That's something totally different from what I was showing you -- temporary assignments:

VAR="a" echo asdf
echo $VAR

The second 'echo' doesn't find the value of VAR because it was set for only that line.

(You can't echo $VAR in that line, either, since the line gets evaluated before the value of VAR exists. It's useful for built-in variables, and things you want to export.)

1 Like

I had never hears of temporary assignments. Thanks again.

It is a common way to set specify a variable local to a command. But of course it only works if a command actually uses the variable

Here is list of environment variables that the read command can use:
read: environment variables

The LANG/LC locale environment variables are used by more utilities...

since you're using bash I'd say <<< "$string" is a much better option than < <(echo "$string") .

$ IFS=: read -raa <<< "$PATH"
$ declare -p a
declare -a a='([0]="/home/mute/bin" [1]="/usr/local/bin" [2]="/usr/bin" [3]="/bin" [4]="/usr/local/games" [5]="/usr/games")'
1 Like

Thanks. I have always used here documents and here strings as constant text so I never realized they were expanded first.

Mike