bash – Backtick or Here String or Read Not Working in RHEL 8

bashhere-stringio-redirectionreadrhel

I am trying to redirect the output of a python script as an input into an interactive shell script.

test.py

print('Hello')
print('world')

Say test.py is as above prints "Hello world" which is feed to two variables using Here string redirection as below

Interactive script : read a b <<< `python3 test.py`

This is not working as expected in Rhel 8 server while it works fine in Rhel 7

Rhel 8:

tmp> read a  b  <<< `python3 test.py`
tmp> echo $a $b
Hello
tmp> cat /etc/redhat-release
Red Hat Enterprise Linux release 8.3 (Ootpa)

variable b is empty in rhel 8

Rhel 7:

tmp> read a  b  <<< `python3 test.py`
tmp> echo $a $b
Hello world
tmp> cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.8 (Maipo)

while the read & Here string works fine in both cases as below

tmp> read a b <<< Hello world"
tmp> echo $a $b
Hello world

Best Answer

read a b reads two words from one line (words delimited by $IFS characters and word and line delimiters escaped with \).

Your python script outputs 2 lines.

Older versions of bash had a bug in that cmd <<< $var or cmd <<< $(cmd2) was applying word splitting to the expansion of $var and $(cmd2) and joining the resulting elements back with spaces do make up the contents of the here-string (see for instance Why does cut fail with bash and not zsh?).

That was fixed in version 4.4 which explains why you don't get what you expect any longer.

To read the first two lines of the output of a command into the $a and $b variable in bash, use:

{
  IFS= read -r a
  IFS= read -r b
} < <(cmd)

Or (not in interactive shells):

shopt -s lastpipe
cmd | {
  IFS= read -r a
  IFS= read -r b
}

Or without lastpipe:

cmd | {
  IFS= read -r a
  IFS= read -r b
  something with "$a" and "$b"
}
# $a and $b will be lost after the `{...}` command group returns

To join lines of the output of a command with spaces, use: cmd | paste -sd ' ' -. Then you can do:

IFS=' ' read -r a b < <(cmd | paste -sd ' ' -)

If you like.

You can also read the lines into elements an array with:

readarray -t array < <(cmd)

And join the elements of the array with the first character of $IFS (space by default) with "${array[*]}".

Related Question